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


359

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

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

据我了解,它涉及: 后台执行限制

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

在不允许的情况下 ”-实际上是什么意思?以及如何解决。我不想将服务设置为“前台”


4
这意味着您的应用程序在后台运行时无法启动服务
Tim

22
这与运行时权限无关
蒂姆(Tim)

10
使用startForegroundService()代替startService()
frogatto

2
您可以尝试使用targetSdkVersion 25,但可以使用compileSdkVersion 26进行编译。通过这种方式,您可以使用Android 8中的新类和最新的suppport库,但您的应用将不受后台执行限制的限制。
Kacper Dziubek '18

2
@KacperDziubek应该可以,但是是一个临时解决方案,因为它将在2018
RightHandedMonkey

Answers:


194

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

在某些情况下,后台应用会被放置在临时白名单中几分钟。当应用程序进入白名单时,它可以不受限制地启动服务,并且其后台服务可以运行。当应用处理用户可见的任务时,该应用将被列入白名单,例如:

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

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

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

如果您使用的是IntentService,则可以更改为JobIntentService。请参阅下面的 @kosev 答案


我刚收到GCM Prio讯息后,想开始服务后便当机。我仍然使用GCM:“ com.google.android.gms:play-services-gcm:11.4.2”,而不是“ com.google.firebase:firebase-messaging:11.4.2”。不知道它的问题,但..
亚历Radzishevsky

“它基本上与后台服务相同,但是它被定期调用,而不是在后台连续运行。” -不确定您的意思是什么,因为Android服务从未连续执行。它们开始,运行然后关闭。
Melllvar '17

2
FirebaseInstanceIdService 和它的onTokenRefresh方法,一个高优先级FCM消息?
Cord Rehn

@phnmnn不,GCMTaskService并没有真正遵循FCM,因此它们不起作用。
Abhinav Upadhyay

4
您不应该使用WorkManager(在这里:developer.android.com/topic/libraries/architecture/workmanager)代替JobScheduler或其他工具吗?我的意思是:youtu.be/IrKoBFLwTN0
Android开发人员

254

我有解决办法。对于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。


9
前台服务是用户将意识到的并且需要通知的东西。如果运行时间过长,它也会ANR。因此,如果该应用程序已在后台运行,则不是一个合适的答案。
SimonH

80
有一个ContextCompat.startForegroundService(...)in support lib可以代替使用。
jayeffkay

37
那不是解决方案。
JacksOnF1re

17
我也同意这不是解决方案。这是一种变通办法,但很有帮助,但是引入Oreo的背景限制是有原因的。以此方式绕过这些限制绝对不是正确的方法(即使它可行)。最好的方法是使用JobScheduler(请参阅接受的答案)。
Vratislav Jindra

6
如果您必须显示一个空的前台通知,我认为这不会是一个很好的用户体验。考虑到你必须这样做的事实。-Android 8.0引入了新方法startForegroundService()以在前台启动新服务。系统创建服务后,应用程序将有五秒钟的时间调用服务的startForeground()方法,以显示新服务的用户可见通知。如果该应用程序未在该时限内调用startForeground(),则系统将停止服务并声明该应用程序为ANR。
footeeaz '18年

85

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

在清单中声明:

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

在您的服务中,您必须将onHandleIntent替换为onHandleWork:

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());


如何在静态方法中调用非静态方法?你能解释一下吗?
麦迪

@Maddy enqueueWork(...)也是静态方法。
hgoebl

2
您将在哪里调用YourService.enqueueWork(context,new Intent()); ?从广播接收器?
TheLearner '18 -10-29

我不认为这是最简单的解决方案。请参阅下面有关WorkManager的评论。它在适当的时候使用JobIntentService,但是它的样板要少得多。
TALE

36

如果该服务在后台线程通过延长运行IntentService,可以取代IntentServiceJobIntentService被设置为支持Android库的一部分

使用的优点JobIntentService是,它IntentService在O之前的设备上以及在O和更高版本上的行为都一样,将其作为作业进行分发

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


1
JobIntentService的问题在于Android可以相当随意地安排您的工作,并且与IntentService不同,它不能在不经过任何修改的情况下隐式启动。参见stackoverflow.com/questions/52479262/…–
千坎

15

Oreo中, Android 为后台服务定义了限制

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

如果仍然需要始终运行的服务,则可以使用前台服务。

后台服务限制:当应用处于空闲状态时,其后台服务的使用受到限制。这不适用于前台服务,而前台服务对用户而言更为明显。

这样您就可以提供前台服务了。服务运行时,您需要向用户显示通知查看此答案(还有很多其他答案

一个解决方案,如果-

您不希望收到服务通知吗?

您可以执行以下定期任务:1.启动您的服务; 2.服务将完成其工作; 3.使其自身停止。如此一来,您的应用程式将不会被视为耗电。

您可以将定期任务与Alarm ManagerJob SchedulerEvernote-JobsWork Manager一起使用

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


假设工作不必立即执行,WorkManager似乎是最好的方法。它向后兼容API 14,在具有API 23+的设备上使用JobScheduler,在具有API 14-22的设备上使用BroadcastReceiver + AlarmManager的组合
James Allen

关于WorkManager的关键是WorkManager适用于可
延期的

13

是的,那是因为您无法再在API 26的后台启动服务。因此,您可以在API 26之上启动ForegroundService。

您必须使用

ContextCompat.startForegroundService(...)

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


1
OP明确表示他不希望成为前台。这应该作为注释或作为更完整答案的一部分。
里卡多

7

正如@kosev在他的回答中所说,您可以使用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)
        }
    }
}

当我处理意图时,我会做这样的事情:

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()
    }
}

我喜欢您的尝试解决方案。对我来说,这是一个解决方案,因为有时context.startService在后台工作-有时不工作-这似乎是唯一的最佳方法,否则您必须在主类中实现更多代码extending Applicationimplementing ActivityLifecycleCallbacks并跟踪应用是在前台还是在后台并开始您的意图相应地。
Pierre

可以捕获此异常吗?
thecr0w

5

firebase发行说明中,他们指出对Android O的支持是在10.2.1中首次发布的(尽管我建议使用最新版本)。

请为Android O添加新的Firebase消息传递依赖项

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

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


这不能回答问题,也不能与firebase有关。它应该作为评论。
里卡多A.19年

5

如果之前在后台运行应用程序时任何意图都可以正常工作,那么从Android 8及更高版本开始就不会再有这种情况了。仅指在应用程序在后台时必须进行一些处理的意图。

必须遵循以下步骤:

  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"

希望这可以帮助。


4

我看到很多建议仅使用ForegroundService的响应。为了使用ForegroundService,必须有一个与其关联的通知。用户将看到此通知。根据情况的不同,他们可能会对您的应用程序感到烦恼并将其卸载。

最简单的解决方案是使用名为WorkManager的新体系结构组件。您可以在此处查看文档:https : //developer.android.com/topic/libraries/architecture/workmanager/

您只需定义扩展Worker的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);

简单!您可以通过多种方式配置工作程序。它支持周期性工作,如果需要,您甚至可以执行诸如链接之类的复杂工作。希望这可以帮助。


3
当前,WorkManager仍为Alpha。
pzulw

3
2019年3月5日-WorkManager 1.0.0稳定版。
phnmnn

应该使用WorkManager而不是使用interservice或JobIntentService
sivaBE35

1
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately...这可能是最简单的,但是,我的应用需要后台服务,该服务可立即执行用户的请求!
某处某人

如果需要立即完成任务,则应使用前台服务。用户将看到通知,并知道您正在工作。如果您需要决定使用什么的帮助,请查看文档。他们有很好的后台处理指南。 developer.android.com/guide/background
TALE,

4

通过使用JobScheduler的替代解决方案,它可以定期在后台启动服务。

首先将类命名为Util.java

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;

public class Util {
// schedule the start of the service every 10 - 30 seconds
public static void schedulerJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context,TestJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(0,serviceComponent);
    builder.setMinimumLatency(1*1000);    // wait at least
    builder.setOverrideDeadline(3*1000);  //delay time
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);  // require unmetered network
    builder.setRequiresCharging(false);  // we don't care if the device is charging or not
    builder.setRequiresDeviceIdle(true); // device should be idle
    System.out.println("(scheduler Job");

    JobScheduler jobScheduler = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        jobScheduler = context.getSystemService(JobScheduler.class);
    }
    jobScheduler.schedule(builder.build());
   }
  }

然后,将JobService类命名为TestJobService.java

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.widget.Toast;

  /**
   * JobService to be scheduled by the JobScheduler.
   * start another service
   */ 
public class TestJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
    Util.schedulerJob(getApplicationContext()); // reschedule the job
    Toast.makeText(this, "Bg Service", Toast.LENGTH_SHORT).show();
    return true;
}

@Override
public boolean onStopJob(JobParameters params) {
    return true;
  }
 }

之后,名为ServiceReceiver.java的 BroadCast Receiver类

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

 public class ServiceReceiver extends BroadcastReceiver {
 @Override
public void onReceive(Context context, Intent intent) {
    Util.schedulerJob(context);
 }
}

使用服务和接收器类代码更新清单文件

<receiver android:name=".ServiceReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <service
        android:name=".TestJobService"
        android:label="Word service"
        android:permission="android.permission.BIND_JOB_SERVICE" >

    </service>

将main_intent启动器留给默认创建的mainActivity.java文件,对MainActivity.java文件进行的更改为

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Util.schedulerJob(getApplicationContext());
  }
 }

哇!没有前台服务就启动后台服务


2

如果您在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 );

建议仅在需要用户知道服务正在运行的情况下使用Foreground服务。典型示例是在后台播放音乐。还有其他一些情况,但您不应该将所有服务都转换为Foreground服务。当您只需要在后台做一些工作并保证它可以运行时,请考虑将服务转换为使用Google建筑组件中的WorkManager。
故事

startForegroundService需要许可,否则需要java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE。修复stackoverflow.com/a/52382711/550471
某处某人

1

如果您集成了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'

0

使用startForegroundService()代替,startService() 不要忘记startForeground(1,new Notification());在启动服务后的5秒钟内在您的服务中创建。


2
似乎新的Notificaction()在Android 8.1上不起作用;您应该创建用于通知的渠道:stackoverflow.com/a/47533338/1048087
Prizoff

0

由于对此答案的争议性投票(截至本编辑,为+ 4 / -4),请先查看其他答案,并仅将其用作最后的手段。我只将此一次用于以root身份运行的网络应用程序并且我同意一般的意见,即在正常情况下不应使用此解决方案。

原始答案如下:

其他答案都是正确的,但我想指出的另一种解决方法是要求用户禁用应用程序的电池优化(除非您的应用程序与系统相关,否则通常不是一个好主意)。有关如何在不禁止您的应用在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

1
要求用户让您免费使用尽可能多的电池并不是一个好的解决方案。考虑将您的代码转换为更省电的解决方案。您的用户将感谢您。
故事

5
@TALE并不是所有的后台服务都可以通过电池JobScheduler和其他东西实现电池友好。与典型的同步应用程序相比,某些应用程序需要在较低的级别上工作。当它不起作用时,这是替代解决方案。
Mygod '18年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.