Android 8.0:java.lang.IllegalStateException:不允许启动服务Intent

问题描述 投票:273回答:16

在应用程序启动时,应用程序启动应该执行某些网络任务的服务。在针对API级别26后,我的应用程序无法在后台启动Android 8.0上的服务。

引起:java.lang.IllegalStateException:不允许启动服务Intent {cmp = my.app.tt / com.my.service}:app在后台uid UidRecord {90372b1 u0a136 CEM空闲过程:1 seq(0,0) ,0)}

据我所知,它涉及:Background execution limits

如果针对Android 8.0的应用程序在不允许创建后台服务的情况下尝试使用该方法,则startService()方法现在会抛出IllegalStateException。

“在不允许的情况下” - 它实际意味着什么?以及如何解决它。我不想把我的服务设置为“前景”

android android-service android-8.0-oreo
16个回答
159
投票

允许的情况是临时白名单,其中后台服务的行为与Android O之前相同。

在某些情况下,后台应用程序会被放置在临时白名单上几分钟。虽然应用程序位于白名单中,但它可以无限制地启动服务,并允许其后台服务运行。当应用程序处理用户可见的任务时,应用程序将放置在白名单中,例如:

  • 处理高优先级的Firebase云消息传递(FCM)消息。
  • 接收广播,例如SMS / MMS消息。
  • 从通知执行PendingIntent。
  • 在VPN应用程序将自身提升到前台之前启动VpnService。

资料来源:https://developer.android.com/about/versions/oreo/background.html

换句话说,如果您的后台服务不符合白名单要求,您必须使用新的JobScheduler。它与后台服务基本相同,但它会定期调用,而不是连续在后台运行。

如果您使用的是IntentService,则可以更改为JobIntentService。见@ kosev的answer below


3
投票

如果您在8.0上运行代码,则应用程序将崩溃。所以在前台启动服务。如果低于8.0使用此:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
context.startService(serviceIntent);

如果高于或高于8.0则使用此:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
ContextCompat.startForegroundService(context, serviceIntent );

3
投票

如果在应用程序处于后台时,任何意图之前的工作正常,那么Android 8及更高版本将不再是这种情况。仅指当app在后台时必须进行某些处理的intent。

必须遵循以下步骤:

  1. 上面提到的意图应该使用JobIntentService而不是IntentService
  2. 扩展JobIntentService的类应该实现-onHandleWork(@NonNull Intent intent)方法,并且应该在方法之下,它将调用onHandleWork方法: public static void enqueueWork(Context context, Intent work) { enqueueWork(context, xyz.class, 123, work); }
  3. 从定义意图的类中调用enqueueWork(Context, intent)。 示例代码: Public class A { ... ... Intent intent = new Intent(Context, B.class); //startService(intent); B.enqueueWork(Context, intent); }

以下类以前扩展了Service类

Public Class B extends JobIntentService{
...

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, B.class, JobId, work);
    }

    protected void onHandleWork(@NonNull Intent intent) {
        ...
        ...
    }
}
  1. com.android.support:support-compat需要JobIntentService - 我使用26.1.0 V
  2. 最重要的是确保Firebase库版本至少是10.2.1,我遇到10.2.0问题 - 如果你有任何问题!
  3. 您的清单应具有以下Service类的权限: service android:name=".B" android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE"

希望这可以帮助。


2
投票

如果您已经集成了firebase消息传递推送通知,那么,

由于Background Execution Limits,为android O(Android 8.0)添加新的/更新firebase消息传递依赖项。

compile 'com.google.firebase:firebase-messaging:11.4.0'

如果需要,升级Google Play服务和Google存储库。

更新:

 compile 'com.google.firebase:firebase-messaging:11.4.2'

1
投票

使用startForegroundService()而不是startService(),不要忘记在服务开始后5秒内在服务中创建startForeground(1,new Notification());


0
投票

我认为最好的方法是在运行时检查构建版本,并根据它,如果它低于Api级别21,继续使用startService()。但如果它更高,你应该使用一个作业调度程序。我想你也可以使用工作经理,但它仍处于测试阶段


-2
投票

其他答案都是正确的,但我想指出另一种解决方法是要求用户为您的应用禁用电池优化(除非您的应用与系统相关,否则这通常不是一个好主意)。请参阅this answer,了解如何在不在Google Play中禁止您的应用的情况下请求退出电池优化。

您还应检查接收器中的电池优化是否已关闭以防止崩溃:

if (Build.VERSION.SDK_INT < 26 || getSystemService<PowerManager>()
        ?.isIgnoringBatteryOptimizations(packageName) != false) {
    startService(Intent(context, MyService::class.java))
} // else calling startService will result in crash

-14
投票

不要在onStartCommand中使用:

return START_NOT_STICKY

只需将其更改为:

return START_STICKY

它会起作用


191
投票

我得到了解决方案对于8.0之前的设备,您只需使用startService(),但对于7.0之后的设备,您必须使用startForgroundService()。以下是启动服务的代码示例。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(new Intent(context, ServedService.class));
    } else {
        context.startService(new Intent(context, ServedService.class));
    }

在服务类中,请添加以下代码以获取通知:

@Override
public void onCreate() {
    super.onCreate();
    startForeground(1,new Notification());
}

其中O是Android版本26。


68
投票

最好的方法是使用JobIntentService,它使用新的JobScheduler for Oreo或旧服务(如果不可用)。

在你的清单中声明:

<service android:name=".YourService"
         android:permission="android.permission.BIND_JOB_SERVICE"/>

在您的服务中,您必须使用onHandleWork替换onHandleIntent:

public class YourService extends JobIntentService {

    public static final int JOB_ID = 1;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, YourService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // your code
    }

}

然后你开始服务:

YourService.enqueueWork(context, new Intent());

34
投票

如果服务通过扩展IntentService在后台线程中运行,则可以将IntentService替换为JobIntentService,它是作为Android支持库的一部分提供的

使用JobIntentService的优点是,它在pre-O设备上表现为IntentService,在O和更高的设备上,它作为一项工作发送它

JobScheduler也可用于定期/随需工作。但是,请确保处理向后兼容性,因为JobScheduler API仅可从API 21获得


11
投票

是的,那是因为你无法在API 26中在后台启动服务。所以你可以在API 26之上启动ForegroundService。

你必须使用

ContextCompat.startForegroundService(...)

并在处理泄漏时发布通知。


8
投票

In Oreo Android defined limits to background services.

为了改善用户体验,Android 8.0(API级别26)对应用程序在后台运行时可以执行的操作施加了限制。

如果您需要始终运行服务,那么您可以使用前台服务。

后台服务限制:当应用程序处于空闲状态时,其后台服务的使用受到限制。这不适用于前景服务,这对用户来说更加明显。

所以你可以做一个前台服务。您需要在服务运行时向用户显示通知。 See this answer(还有很多其他的)

解决方案如果 -

you don't want a notification for your service?

你可以定期完成任务,1。它开始你的服务,2。服务将完成它的工作,3。停止自己。这样您的应用程序将不被视为电池耗尽。

你可以使用Alarm ManagerJob SchedulerEvernote-JobsWork Manager定期任务。

我已经使用Work-Manager测试了永远运行的服务。


6
投票

来自firebase release notes,他们表示对Android O的支持首次在10.2.1中发布(尽管我建议使用最新版本)。

请为android O添加新的firebase消息依赖项

compile 'com.google.firebase:firebase-messaging:11.6.2'

如果需要,升级Google Play服务和Google存储库。


6
投票

正如@kosev在his answer中所说,你可以使用JobIntentService。但我使用另一种解决方案 - 我捕获IllegalStateException并将服务作为前台启动。例如,此函数启动我的服务:

@JvmStatic
protected fun startService(intentAction: String, serviceType: Class<*>, intentExtraSetup: (Intent) -> Unit) {
    val context = App.context
    val intent = Intent(context, serviceType)
    intent.action = intentAction
    intentExtraSetup(intent)
    intent.putExtra(NEED_FOREGROUND_KEY, false)

    try {
        context.startService(intent)
    }
    catch (ex: IllegalStateException) {
        intent.putExtra(NEED_FOREGROUND_KEY, true)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent)
        }
        else {
            context.startService(intent)
        }
    }
}

当我处理Intent时,我做了这样的事情:

override fun onHandleIntent(intent: Intent?) {
    val needToMoveToForeground = intent?.getBooleanExtra(NEED_FOREGROUND_KEY, false) ?: false
    if(needToMoveToForeground) {
        val notification = notificationService.createSyncServiceNotification()
        startForeground(notification.second, notification.first)

        isInForeground = true
    }

    intent?.let {
        getTask(it)?.process()
    }
}

4
投票

我看到很多回复建议只使用ForegroundService。要使用ForegroundService,必须有与之关联的通知。用户将看到此通知。根据具体情况,他们可能会对您的应用程序感到恼火并将其卸载。

最简单的解决方案是使用名为WorkManager的新架构组件。你可以在这里查看文档:https://developer.android.com/topic/libraries/architecture/workmanager/

您只需定义扩展Worker的工作类。

public class CompressWorker extends Worker {

    public CompressWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

然后安排何时运行它。

    OneTimeWorkRequest compressionWork = 
        new OneTimeWorkRequest.Builder(CompressWorker.class)
            .build();
    WorkManager.getInstance().enqueue(compressionWork);

简单!您可以通过多种方式配置工作人员。它支持重复性工作,如果需要,您甚至可以执行链接等复杂操作。希望这可以帮助。

© www.soinside.com 2019 - 2024. All rights reserved.