使用 PendingIntent 的应用小部件中的 Oreo 服务未启动

Service not starting on Oreo in app widget using PendingIntent

我正在使用 Android 应用程序小部件。我正在创建一个 PendingIntent 对象并在方法 RemoteViews#setOnClickPendingIntent() 中使用它。这是待定意向:

// The below code is called in the onUpdate(Context, AppWidgetManager, int[]) method of the AppWidgetProvider class.

// Explicit intent
Intent intent = new Intent(context, MyService.class);
intent.setAction(MY_ACTION);
intent.putExtra(EXTRA_KEY, VALUE);

// Create the pending intent
PendingIntent pendingIntent = PendingIntent.getService(context, appWidgetId, intent, 0);

// 'views' is a RemoteViews object provided by onUpdate in the AppWidgetProvider class.
views.setOnClickPendingIntent(R.id.root_layout, pendingIntent);

以上代码在 Android Oreo 之前按预期工作。但是,在 Android Oreo 中,如果该应用程序从最近的应用程序中滑出,它不再启动该服务。 (不再活跃)。 Oreo 的后台执行限制中没有排除 PendingIntent 吗?

出于测试目的,我将 getService 替换为 getForegroundService,但 Service 仍未启动。两种方法都在日志中显示以下消息:

W/ActivityManager: Background start not allowed: service Intent { act=com.example.myapp.MY_ACTION flg=0x10000000 cmp=com.example.myapp/com.example.myapp.MyService bnds=[607,716][833,942] (has extras) } to com.example.myapp/com.example.myapp.MyService from pid=-1 uid=10105 pkg=com.example.myapp

为什么 Service 没有启动,即使使用 getForegroundService?我在 Nexus 6P 运行 Android 8.1.0.

上进行了测试

正如您所注意到的,应用程序小部件不计入 PendingIntent 背景白名单。我不知道为什么——它似乎与 Notification 开始的 PendingIntent 差不多。 Notification 是系统的东西,而应用程序小部件是主屏幕的东西,这可能是一个问题。无论如何,您可以:

  • 使用getForegroundService(),或者

  • 尝试使用 getBroadcast() 显式 Intent,并使用 BroadcastReceiver 开始 JobIntentService,如果您不想引发 Notification(作为前台服务的要求)

根据您的症状,您似乎被我认为是错误的东西绊倒了:应该有一种方法可以从 getService() 切换到 getForegroundService() 而无需重新启动。 :-) 我会尝试 运行 一些实验,如果我能重现问题,我会提出问题。

goasyc 可以作为一种选择吗?您可以更改 PendingIntent 以触发 BroadcastReceiver,然后在 OnRecieve 方法中,您可以调用 goasync() 然后您应该能够使用它生成的 PendingResult 来创建异步调用。仍然认为您无法启动服务。

How to use "goAsync" for broadcastReceiver?

您不能再在 8.0 中在后台启动服务,但您可以使用 JobScheduler 来获得类似的结果。还有一个 JobIntentService 帮助程序 class 允许您从服务切换到 JobScheduler 而无需进行太多修改。你不能使用 PendingIntent 指向服务,但你可以使用指向 ActivityBroadcastReceiver.

如果您有 8.0 之前的工作小部件,现在您需要使其在 android 8.0 上工作,只需执行以下简单步骤:

  1. 将您的 IntentService class 更改为 JobIntentService
  2. 将服务 onHandleIntent 方法重命名为 onHandleWork(相同参数)
  3. 在清单中为您的服务添加 BIND_JOB_SERVICE 权限:
    <service android:name=".widget.MyWidgetService"
           android:permission="android.permission.BIND_JOB_SERVICE">
    </service>
  1. 要启动此服务,您不能再使用 context.startService。而是使用 enqueueWork 静态方法(其中 JOB_ID 只是一个唯一的整数常量,对于为相同 class 排队的所有工作,必须是相同的值):
    enqueueWork(context, MyWidgetService.class, JOB_ID, intent);
  1. 对于点击,将指向服务的 pendingIntent 替换为指向 BroadcastReceiver 的 pendingIntent。由于你的 AppWidgetProvider 的 subclass 本身就是一个 BroadcastReceiver,你不妨使用它:
    Intent myIntent = new Intent(context, MyAppWidgetProvider.class);
    myIntent .setAction("SOME_UNIQUE_ACTION");
    pendingIntent = PendingIntent.getBroadcast(context, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  1. 在 onReceive 中使用 enqueueWork 启动服务(如果您的 PendingIntent 正在启动 activity,请保留它 - 它在 android 8.0+ 上运行良好):
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (intent.getAction().equals("SOME_UNIQUE_ACTION")) {
            MyWidgetService.enqueueWork(.....);
        }
    }
  1. 为确保 widget 可以在旧设备上运行,请确保您在清单中具有 WAKE_LOCK 权限(由 JobIntentService 在旧设备上使用)。

就是这样。 这个小部件现在可以在新旧设备上正常工作。唯一真正的区别是,如果您的 8.0 设备处于打瞌睡模式,它可能不会经常更新小部件,但这应该不是问题,因为如果它正在打瞌睡,这意味着用户现在无法看到您的小部件无论如何。