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

Android 8.0: java.lang.IllegalStateException: Not allowed to start service Intent

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

Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=my.app.tt/com.my.service }: app is in background uid UidRecord{90372b1 u0a136 CEM idle procs:1 seq(0,0,0)}

据我了解,它与: Background execution limits

The startService() method now throws an IllegalStateException if an app targeting Android 8.0 tries to use that method in a situation when it isn't permitted to create background services.

"在不允许的情况下" - 这到底是什么意思??以及如何修复它。我不想将我的服务设置为 "foreground"

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

Under certain circumstances, a background app is placed on a temporary whitelist for several minutes. While an app is on the whitelist, it can launch services without limitation, and its background services are permitted to run. An app is placed on the whitelist when it handles a task that's visible to the user, such as:

  • Handling a high-priority Firebase Cloud Messaging (FCM) message.
  • Receiving a broadcast, such as an SMS/MMS message.
  • Executing a PendingIntent from a notification.
  • Starting a VpnService before the VPN app promotes itself to the foreground.

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

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

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

如果您集成了 firebase 消息推送通知,

由于 Background Execution Limits.

,为 android O (Android 8.0) 添加 new/update firebase 消息传递依赖项
compile 'com.google.firebase:firebase-messaging:11.4.0'

根据需要升级 google 播放服务和 google 存储库。

更新:

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

我找到了解决方案。对于 8.0 之前的设备,您必须只使用 startService(),但对于 post-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));
    }

并在class服务中,请添加以下代码进行通知:

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

其中 O 是 Android 版本 26。

如果您不希望您的服务在前台 运行 而希望它在后台 运行,post 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 在后台线程中 运行 服务,您可以将 IntentService 替换为 JobIntentService,后者是 [=24= 的一部分] ] 支持库

使用 JobIntentService 的优点是,它在 O 之前的设备上表现得像 IntentService,在 O 及更高版本上,它作为作业调度

JobScheduler 也可用于 periodic/on 需求工作。但是,确保处理向后兼容性,因为 JobScheduler API 仅适用于 API 21

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

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

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

根据需要升级 google 播放服务和 google 存储库。

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

最好的方法是使用 JobIntentService,它使用新的 JobScheduler for 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());

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

上面启动ForegroundService

你必须使用

ContextCompat.startForegroundService(...)

和post处理泄漏时的通知。

由于对该答案的投票存在争议(截至本次编辑为 +4/-4),请先查看其他答案,并仅将其用作最后的手段我只用过一次 a networking app that runs as 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

如果您 运行 您的代码在 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 的回复。为了使用 ForegroundService,必须有与之关联的通知。用户将看到此通知。根据情况,他们可能会对您的应用感到厌烦并卸载它。

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

您只需定义扩展 Worker 的 worker class。

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

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

OreoAndroid中定义limits to background services.

To improve the user experience, Android 8.0 (API level 26) imposes limitations on what apps can do while running in the background.

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

Background Service Limitations: While an app is idle, there are limits to its use of background services. This does not apply to foreground services, which are more noticeable to the user.

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

一个解决方案如果-

您不想收到服务通知吗?

You can make periodic task, 1. it starts your service, 2. service will do its work and 3. stops itself. By this your app will not be considered battery draining.

您可以将定期任务与 Alarm Manager, Job Scheduler, Evernote-Jobs or Work Manager 一起使用。

  • 工作管理器是周期性任务的最佳解决方案。这是用 Android Architecture Component.
  • 引入的
  • 与 Job-Scheduler(仅 >21 API)不同,它适用于所有版本。
  • 它也在 Doze-Standby mode 之后开始工作。
  • 制作Android Boot Receiver 用于在设备启动后安排服务。

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

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

必须遵循以下步骤:

  1. 上面提到的意图应该使用 JobIntentService 而不是 IntentService.
  2. 扩展 JobIntentService 的 class 应该实现 - onHandleWork(@NonNull Intent intent) 方法并且应该在 方法,它将调用 onHandleWork 方法:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
    
  3. 从定义意图的 class 调用 enqueueWork(Context, intent)

    示例代码:

    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. 您的清单应具有服务的以下权限 class:

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

希望这对您有所帮助。

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

当我处理 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的替代解决方案,它可以在后台定期启动服务。

首先将class命名为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 class 命名为 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;
  }
 }

之后广播接收器 class 命名为 ServiceReceiver.java

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

使用服务和接收方 class 代码

更新清单文件
<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());
  }
 }

哇哦!!后台服务在没有前台服务的情况下启动

[编辑]:您可以使用 Work Manager 进行任何类型的后台任务在 Android.

我也有这个问题

添加了这个库

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

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

它确实发生了,因为 phone 在屏幕外,或者您在启动服务时按下了电源按钮。对我有用的解决方案是 启动一个 activity ,当它进入 onResume 时启动服务。 就我而言,它正在启动并启动服务。