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

在应用程序启动时,应用程序启动应该执行一些网络任务的服务。 在目标API级别26后,我的应用程序无法在Android 8.0后台启动服务。< / p >

原因:java.lang.IllegalStateException:不允许启动 服务意图{ cmp = my.app.tt / com.my.service }: app是在后台uid UidRecord{90372b1 u0a136 CEM空闲procs:1 seq (0, 0, 0)} < / p >

,我理解它与: 后台执行限制 < / p >

startService()方法现在抛出一个IllegalStateException异常 针对Android 8.0的应用程序尝试使用这种方法 不允许创建后台服务

"在不被允许的情况下" -它实际上是什么意思??以及如何修复它。我不想把我的服务设置为前台

285801 次浏览

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

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

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

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

换句话说,如果你的后台服务不满足白名单的要求,你必须使用新的JobScheduler。它基本上与后台服务相同,但它周期性地被调用,而不是持续地在后台运行。

如果你使用的是IntentService,你可以改成JobIntentService。参见@kosev的下面的回答

如果你有集成的firebase消息推送通知,

由于后台执行限制,为android O (android 8.0)添加新的/更新firebase消息依赖项。

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

如果需要,升级谷歌播放服务和谷歌存储库。

更新:

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

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

如果你不想让你的服务在前台运行,而想让它在后台运行,发布Android O,你必须将服务绑定到如下连接:

Intent serviceIntent = new Intent(context, ServedService.class);
context.startService(serviceIntent);
context.bindService(serviceIntent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//retrieve an instance of the service here from the IBinder returned
//from the onBind method to communicate with
}


@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);

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

使用JobIntentService的优点是,它在pre-O设备上表现为IntentService,在O或更高级别设备上,它将其作为作业分派

JobScheduler也可以用于定期/随需应变作业。但是,确保处理向后兼容性,因为JobScheduler API仅从API 21可用

Firebase发布说明中,他们声明对Android O的支持首次发布于10.2.1(尽管我建议使用最新版本)。

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

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

如果需要,升级谷歌播放服务和谷歌存储库。

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

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

在舱单上申报:

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

是的,那是因为在API 26上你不能在后台启动服务了。所以你可以在API 26之上启动ForegroundService。

你必须使用

ContextCompat.startForegroundService(...)

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

由于这个答案有争议的投票(+4/-4编辑),请先看看其他答案,最后再用这个。我只对以根用户身份运行的网络应用程序使用了这个一次,我同意一般的意见,这个解决方案不应该在正常情况下使用。

原答案如下:

其他答案都是正确的,但我想指出的是,另一种解决这个问题的方法是要求用户禁用应用程序的电池优化(这通常不是一个好主意,除非你的应用程序与系统相关)。关于如何请求退出电池优化而不让你的应用程序在谷歌播放被禁止,请参阅这个答案

你也应该检查电池优化是否在你的接收器关闭,以防止崩溃通过:

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

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

我看到很多回复都建议只使用前台服务。为了使用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);

简单!有很多方法可以配置worker。它支持重复作业,如果需要的话,你甚至可以做一些复杂的事情,比如链接。希望这能有所帮助。

如果任何意图之前工作正常时,应用程序是在后台,这将不再是Android 8及以上的情况下。只引用意图,当app在后台时,它必须做一些处理。

必须遵循以下步骤:

    上面提到的意图应该使用JobIntentService而不是 李IntentService。< / > 扩展JobIntentService的类应该实现- onHandleWork(@NonNull Intent intent)方法,并且应该在 方法,该方法将调用onHandleWork方法
    public static void enqueueWork(Context context, Intent work) {
    enqueueWork(context, xyz.class, 123, work);
    }
    
  1. Call enqueueWork(Context, intent) from the class where your intent is defined.

    Sample code:

    Public class A {
    ...
    ...
    Intent intent = new Intent(Context, B.class);
    //startService(intent);
    B.enqueueWork(Context, intent);
    }
    

The below class was previously extending the Service class

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. 你的manifest应该对Service类有以下权限:

    service android:name=".B"
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"
    

Hope this helps.

正如@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()
}
}

备选方案使用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());
}
}

WOOAAH ! !后台服务启动时没有前台服务

(编辑):你可以在Android中为任何类型的后台任务使用< /强> < >强工作经理

我也有这个问题

添加了这个库

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

重新安装了应用程序帮我解决了这个问题

它实际上发生了,因为手机在屏幕外,或者你在启动服务时按了电源键。解决这个问题的方法对我来说是 启动一个活动,当它将进入onResume然后启动服务。 在我的例子中,它是引导和启动一个服务

我对这里的答案非常不满意。如果前台服务和WorkManager都适合用例呢? 我已经找到了一个解决方案,我使用流程作用域,并确保在日志逻辑中不包括作用域取消异常。 像这样:< / p >
with(ProcessLifecycleOwner.get()) {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
try {
context.startService(context, Service::class.java)
} catch (ex: CancellationException) {
// app minimized, scope cancelled, do not log as error
} catch (ex: IllegalStateException) {
logToFirebase(ex)
}
}
}
}

更详细的内容在本文https://medium.com/@lepicekmichal/android-background-service-without-hiccup-501e4479110f

您可以尝试此代码以避免崩溃。正如谷歌开发者在问题跟踪器中所说。

private val activityManager by lazy { getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }


//due to https://issuetracker.google.com/issues/113122354
private fun isInForegroundByImportance(): Boolean {
val importanceState = activityManager.runningAppProcesses.find {
it.pid == android.os.Process.myPid()
}?.importance ?: return false
return importanceState >= RunningAppProcessInfo.IMPORTANCE_FOREGROUND
}

和使用

override fun onResume() {
super.onResume()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || isInForegroundByImportance()) {
val intent = Intent(this, BluetoothScannerService::class.java)
this.startService(intent)
}
}