始终接收广播(即使在后台)的广播接收器 API 级别 +26

Broadcast-receiver which always receives broadcast (even in background) for API Level +26

我将此设置为问答式,因为我发现这个想法可行。它解决了 Android.

初学者难以解决的难题

Google has deprecated registering Broadcast Receiver into manifest like this below from API Level 26+ ( Except Some )

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.net.wifi.STATE_CHANGE" />
    </intent-filter>
</receiver>

但是,如果有人想在应用程序处于后台时接收特定的设备状态更改,例如 Internet 连接更改(这是不允许的),并且它对任何功能都很重要他的申请,他应该做什么?

当我浏览文档时,我的眼睛卡在了这里:

Context-registered receivers receive broadcasts as long as their registering context is valid. For an example, if you register within an Activity context, you receive broadcasts as long as the activity is not destroyed. If you register with the Application context, you receive broadcasts as long as the app is running.

这实际上意味着如果我可以持有一个 Context,注册到它的广播接收器将在后台 运行。

为此,Service 将是最佳实践。

下面是 STICKY_SERVICE 的代码,它在被杀死后再次启动,因此上下文 仍然有效

AlwaysOnService.class

package app.exploitr.auto;

import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.support.annotation.Nullable;

public class AlwaysOnService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        registerReceiver(new ClickReceiver(), new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"));
        return Service.START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onLowMemory() {  // rem this if you want it always----
        stopSelf();
        super.onLowMemory();
    }
}

现在,实际做事的接收者:

ClickReceiver.class

package app.exploitr.auto;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.Objects;

public class ClickReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, Intent intent) {

        switch (Objects.requireNonNull(intent.getAction())) {

            case AutoJob.NOTIFICATION_CANCEL_TAG:
                System.out.println("Not related");
                break;

            case AutoJob.LOGIN_CANCEL_TAG:
                System.out.println("Not related");
                break;

            case "android.net.conn.CONNECTIVITY_CHANGE":
                System.out.println("Oops! It works...");
                break;
        }
    }
}

从任何 Activity Class

启动代码
private void setUpBackOffWork() {
    if (DataMan.getInstance(getBaseContext()).getPeriodic()) {
        AutoJob.schedulePeriodic();
        //Not related
    }
    if (DataMan.getInstance(getBaseContext()).getPureAutoLogin()) {
        startService(new Intent(this, AlwaysOnService.class));
    }
}

所以我的目标是当我打开 android 的 WiFi 时自动登录到我的 isp,并且代码运行流畅。它从来没有失败过(到现在为止它已经 运行 运行了 7 小时 37 分钟并且运行良好 | 不会在重新启动后出现 )。


要使接收器 运行在重新启动后保持稳定,请尝试清单可注册的 BOOT_COMPLETED 操作。它和旧的一样工作。

<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>


更新 1

现在,由于 Google 采取了一个步骤来限制后台执行,因此您还必须将该服务设为 foreground 服务。因此,程序如下。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, new Intent(THIS_SERVICE_CLASS_NAME.this, ACTIVITY_TO_TARGET.class), 0);

    /*Handle Android O Notifs as they need channel when targeting 28th SDK*/
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        NotificationChannel notificationChannel = new NotificationChannel(
        "download_check_channel_id", 
        "Channel name",
        NotificationManager.IMPORTANCE_LOW);

        if (notificationManager != null) {
            notificationManager.createNotificationChannel(notificationChannel);
        }

        builder = new Notification.Builder(this.getBaseContext(), notificationChannel.getId())
                .setContentTitle("Hi! I'm service")
                .setContentIntent(pendingIntent)
                .setOngoing(true);

        notification = builder.build();
        startForeground("Whosebug".length(), notification);
    }

    return START_STICKY;
}

这也适用于 Xamarin Android。 Play 商店要求将我的应用程序的 SDK 升级到 8.0 Oreo,然后一堆东西就停止工作了。

Microsoft 的 documentation on Broadcast Receivers 相当混乱:

Apps that target Android 8.0 (API level 26) or higher may not statically register for an implicit broadcast. Apps may still statically register for an explicit broadcast. There is a small list of implicit broadcasts that are exempt from this restriction.

甚至 Google 的 official docs 也非常难以理解。

在 Xamarin Android 上,遵循以下模式就足够了:

[BroadcastReceiver]
[IntentFilter(new string[] {MyReceiver.MyAction})]
public class MyReceiver : BroadcastReceiver
{
    public const String MyAction = "com.mytest.services.MyReceiver.MyAction";

    public override void OnReceive (Context context, Intent intent)
    {
        // ...
    }
}

IntentFilter 注释指示编译器在构建过程中将接收器和意图过滤器注册添加到清单文件。但是从目标 SDK v8.0 (Oreo/API 26) 及更高版本 Android 开始忽略清单上的这些配置(some system implicit actions 除外)。所以这意味着 IntentFilter 注释仅适用于那些异常,并且要使您的广播接收器接收广播,需要在执行时注册它们:

#if DEBUG
[Application(Debuggable=true)]
#else
[Application(Debuggable=false)]
#endif
public class MyApplication: Application
{
    public override void OnCreate ()
    {
        base.OnCreate ();

        Context.RegisterReceiver(new MyReceiver(), new IntentFilter(MyReceiver.MyAciton));
    }
}

也可以仅在 Activity 的生命周期内注册接收器,如@Toaster 所述。您可以继续正常发送广播:

// ...

ApplicationContext.SendBroadcast(new Intent(MyReceiver.MyAction));

// ...