背景执行不允许接收BOOT_COMPLETED意图

20

我读过有关Android Oreo后台执行限制的内容,它明确指出BOOT_COMPLETED广播不受影响,但我在Android Oreo上无法使其工作。

首先,我正在使用SDK 27进行编译。其次,我已在清单文件中声明了接收器:

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <receiver
        android:name="helpers.StartDetectionAtBoot"
        android:label="StartDetectionAtBoot"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <category android:name="android.intent.category.DEFAULT"/>

            <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>

            <action android:name="android.intent.action.BOOT_COMPLETED"/>
            <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
            <!--For HTC devices-->
            <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            <!--For MIUI devices-->
            <action android:name="android.intent.action.REBOOT"/>
        </intent-filter>
    </receiver>

然后是接收方的实现,它也可以像这样简单:

public class StartDetectionAtBoot extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("test", "test");

        Intent intent0 = new Intent( context, ActivityRecognitionService.class );
        PendingIntent pendingIntent = PendingIntent.getService(context, 111, intent0, PendingIntent.FLAG_UPDATE_CURRENT);
        ActivityRecognitionClient activityRecognitionClient = ActivityRecognition.getClient(context);
        activityRecognitionClient.requestActivityUpdates(5000, pendingIntent);
    }
}

onReceive方法在Android Oreo设备/模拟器上未被调用,我会始终收到logcat错误:

W/BroadcastQueue: 后台执行被禁止:接收Intent { act=android.intent.action.BOOT_COMPLETED flg=0x400010 }

阅读其他答案时,他们说在清单中注册显式Intent时会出现一些问题,但这不适用于BOOT_COMPLETED

也没有这个能帮到我,因为接收器根本没有被调用。

在运行时注册广播意图可以使其工作(在模拟器上从adb shell触发意图),但我不确定这是正确的做法:

registerReceiver(new StartDetectionAtBoot(), new IntentFilter(Intent.ACTION_BOOT_COMPLETED));

这个有已知的bug吗?


1
当您启动前台服务时,您看到什么错误/行为? - Sagar
@Sagar 当应用程序正在运行时,我可以正确启动它,并且即使关闭应用程序,服务仍然保持活动状态,但是广播接收器的onReceive方法在其他情况下从未触发。 - fillobotto
1
只有在重新启动手机时,才会触发ACTION_BOOT_COMPLETED。您必须在清单中注册它。不要从BroadcastReceiver启动IntentService,而是启动前台服务,然后注册活动客户端。 - Sagar
@Sagar 我已经将它添加到清单中了,但正如我所说的,在启动设备时根本没有触发。 - fillobotto
看起来你的错误,似乎是你的引导接收器正在工作。只需删除ActivityRecognition的代码并检查日志是否被打印即可。 - Sagar
@Sagar 这就是关键所在。即使在 onReceive 内部放置一个简单的日志调用,它也永远不会被调用。 - fillobotto
3个回答

10

在需要使用隐式意图时,将接收者自注册到相应的代码中才是正确的开始接收意图的方式。这不需要任何服务(在大多数情况下,请见下文...)。但为了避免混淆测试结果和破坏早期实现,您需要注意以下内容:

  1. 每次应用程序运行都应进行一次自注册。就Java/Kotlin应用程序数据而言:每个静态字段生命周期只需自注册一次。因此,一个静态布尔字段就可以让您知道:是否需要自注册(例如,在重新启动后或在Android系统稍后杀死您的应用程序后...)或者不需要(我引用了此提交的工作代码:https://github.com/andstatus/todoagenda/commit/74ffc1495f2c4cbebe5c43aab13389ea0ea821fde):
    private static volatile boolean receiversRegistered = false;

    private static void registerReceivers(Context contextIn) {
        if (receiversRegistered) return;

        Context context = contextIn.getApplicationContext();
        EnvironmentChangedReceiver receiver = new EnvironmentChangedReceiver();

        IntentFilter providerChanged = new IntentFilter();
        providerChanged.addAction("android.intent.action.PROVIDER_CHANGED");
        providerChanged.addDataScheme("content");
        providerChanged.addDataAuthority("com.android.calendar", null);
        context.registerReceiver(receiver, providerChanged);

        IntentFilter userPresent = new IntentFilter();
        userPresent.addAction("android.intent.action.USER_PRESENT");
        context.registerReceiver(receiver, userPresent);

        Log.i(EventAppWidgetProvider.class.getName(), "Registered receivers from " + contextIn.getClass().getName());
        receiversRegistered = true;
    }
  1. 在您的应用程序的所有可能的入口点中插入registerReceivers方法的调用,以最大化接收器的注册机会,即使您的应用程序仅被Android系统启动一次,例如:
    @Override
    public void onUpdate(Context baseContext, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        registerReceivers(baseContext);

        ...
    }
  1. 在 AndroidManifest.xml 文件中注册接收器仍然适用于 Android 7 之前的版本,但是请注意,在这种情况下,您仍会在 logcat 中看到“不允许后台执行”的信息,并且会引用到您的接收器。这只意味着通过 AndroidManifest.xml 注册无法正常工作(对于 Android 8+ 是预期的),但自注册的接收器仍应该被调用!

  2. 如上所述,对于轻量级小部件通常不需要启动前台服务。此外,用户不喜欢不断看到通知,告诉他们你的“小部件”正在前台运行(因此不断消耗资源)。唯一可能真正需要这样做的情况是,当 Android 经常杀死您的应用程序并因此删除了重启后完成的自注册时。 我认为,使您的“小部件应用程序”尽可能轻巧(尽可能少地占用内存和 CPU 资源...)是确保您的小部件应用程序仅在设备关键情况下才会被杀死的正确方法... 也许您应该将大型应用程序拆分成两个部分,将小部件作为重量级应用程序的启动器,后者需要不时地工作...


1
"自注册接收器"。您的意思是与AndroidManifest相反吗? - IgorGanapolsky
1
@IgorGanapolsky 是的,自注册接收器,因为在Android清单中定义的接收器会被Android系统跳过/禁用。 - yvolk
@yvolk这是否意味着,如果应用程序在自注册后被杀死,我们将停止接收广播通知?这与在清单中注册广播事件相反。 - Kaps
@Kaps 是的,我的经验也表明了这一点。因此,应用程序需要尽早重新注册的机会...这就是为什么在我的小部件代码中,我从检查任何输入开始处理,如果应用程序已注册通知,则进行注册。如果尚未注册,则进行注册。 - yvolk

6

W/BroadcastQueue: 后台执行不允许:收到意图 { act=android.intent.action.BOOT_COMPLETED **flg=0x400010** }

您可以通过adb shell测试意图,例如:am broadcast -a android.intent.action.BOOT_COMPLETED 其中flg=0x400010与Android系统在开机时发送的flg=0x9000010不同。

您可以在frameworks/base/core/java/android/content/Intent.java中找到标志位的定义。 flg=0x400010没有FLAG_RECEIVER_FOREGROUND = 0x10000000位。

所以,如果您想从adb shell测试该意图,可以使用以下命令: am broadcast -a android.intent.action.BOOT_COMPLETED *--receiver-include-background.*


3
解决方案是我已经尝试过的两种方法的结合。
首先,我必须启动一个前台服务(即使是虚拟服务也可以),并带有粘性通知:
public class StartDetectionAtBoot extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Intent intent1 = new Intent(context.getApplicationContext(), DummyService.class);
            context.startForegroundService(intent1);
        }

        Intent intent0 = new Intent( context, ActivityRecognitionService.class );
        PendingIntent pendingIntent = PendingIntent.getService(context, 111, intent0, PendingIntent.FLAG_UPDATE_CURRENT);
        ActivityRecognitionClient activityRecognitionClient = ActivityRecognition.getClient(context);
        activityRecognitionClient.requestActivityUpdates(5000, pendingIntent);
    }
}

当然,在你启动服务的内部必须有一个 onCreate 方法,该方法创建通知并调用 startForeground

其次,我在 Android Studio 中进行了缓存失效,并清除了模拟器实例。这部分解决方案对于我来说是必要的,因为第一部分仍然无法工作。


你为什么使用了两个服务,而不是将前台服务和活动识别都实现在一个服务中? - dor506
@dor506 不,同一个服务 - fillobotto
1
@fillobotto 我有类似的情况,请你贴一下DummyService.class的内容。 [1]: https://dev59.com/rLDla4cB1Zd3GeqP4TPW - Biswajit Das
清除缓存和擦除模拟器数据对我的情况起了作用。我不必使用虚拟服务,但还是谢谢你。 - user -1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接