【问题标题】:Android 8.0: java.lang.IllegalStateException: Not allowed to start service IntentAndroid 8.0:java.lang.IllegalStateException:不允许启动服务 Intent
【发布时间】:2018-03-08 18:54:19
【问题描述】:

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

原因:java.lang.IllegalStateException: 不允许启动 服务意图{ cmp=my.app.tt/com.my.service }:应用处于后台 uid UidRecord{90372b1 u0a136 CEM idle procs:1 seq(0,0,0)}

据我了解,它与: Background execution limits

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

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

【问题讨论】:

  • 表示你的应用在后台时无法启动服务
  • 这与运行时权限无关
  • 使用startForegroundService() 而不是startService()
  • 您可以尝试使用 targetSdkVersion 25 但使用 compileSdkVersion 26 进行编译。这样您可以使用来自 Android 8 和最新支持库的新类,但您的应用将不受后台执行限制。
  • @KacperDziubek 这应该可行,但只是一个临时解决方案,因为它需要在 2018 年秋季以 SDK26 为目标。

标签: android android-service android-8.0-oreo


【解决方案1】:

我得到了解决方案。对于 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);

【讨论】:

  • 前台服务是用户会意识到并需要通知的东西。如果运行时间过长,它也会 ANR。因此,如果应用程序已经在后台运行,这并不是一个真正合适的答案。
  • 支持库中有一个ContextCompat.startForegroundService(...) 可以替代使用。
  • 这不是解决方案。
  • 我也同意这不是一个解决方案。这是一种解决方法,它有帮助,但引入 Oreo 中的背景限制是有原因的。以这种方式绕过这些限制绝对不是正确的方法(即使它有效)。最好的方法是使用 JobScheduler(参考接受的答案)。
  • 如果必须显示一个空的前台通知,我认为这不会是一个好的用户体验。考虑到你必须的事实。 -- Android 8.0 引入了新方法 startForegroundService() 在前台启动新服务。系统创建服务后,应用程序有五秒钟的时间调用服务的 startForeground() 方法以显示新服务的用户可见通知。如果应用在时限内没有调用startForeground(),系统会停止服务并声明应用为ANR。
【解决方案2】:

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

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

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

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

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

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

【讨论】:

  • 在收到“高”优先级的 GCM 消息后,我想启动服务后出现崩溃。我仍然使用 GCM:“com.google.android.gms:play-services-gcm:11.4.2”,而不是“com.google.firebase:firebase-messaging:11.4.2”。不过不确定这是否重要..
  • "它与后台服务基本相同,但它会定期调用,而不是在后台连续运行。" - 不知道你的意思是什么,因为 Android 服务从未连续执行。它们启动、运行,然后关闭。
  • FirebaseInstanceIdService 及其 onTokenRefresh 方法是高优先级 FCM 消息吗?
  • 您不应该使用 WorkManager(这里:developer.android.com/topic/libraries/architecture/workmanager)而不是 JobScheduler 或其他吗?我的意思是:youtu.be/IrKoBFLwTN0
  • SO 还不错,但我之前被误导了。我必须使用 JobScheduler 真的是真的吗?所有人都在谈论 startForegroundService/startForeground 吗?
【解决方案3】:

最好的方法是使用 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());

【讨论】:

  • 如何在静态方法中调用非静态方法?你能解释一下吗?
  • @Maddy enqueueWork(...) 也是一个静态方法。
  • 你会在哪里调用 YourService.enqueueWork(context, new Intent()); ?来自广播接收器?
  • 我不认为这是最简单的解决方案。请参阅下面关于 WorkManager 的评论。它在适当的时候使用 JobIntentService,但它的样板要少得多。
【解决方案4】:

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

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

JobScheduler 也可用于定期/按需作业。但是,请确保处理向后兼容性,因为 JobScheduler API 仅适用于 API 21

【讨论】:

  • JobIntentService 的问题在于,Android 可以相当随意地安排您的工作,并且与 IntentService 不同,它不能在没有一些修补的情况下隐式启动。见stackoverflow.com/questions/52479262/…
【解决方案5】:

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

你必须使用

ContextCompat.startForegroundService(...)

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

【讨论】:

  • OP 明确表示他不想作为前景。这应该作为评论或作为更完整答案的一部分。
【解决方案6】:

Oreo Android 中定义了limits to background services

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

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

后台服务限制:当应用处于空闲状态时,存在限制 对其使用后台服务。这不适用于前台 服务,对用户来说更明显。

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

一个解决方案如果 -

您不想收到有关服务的通知?

您可以进行周期性任务,1. 它启动您的服务,2. 服务将完成其工作,3. 自行停止。这样,您的应用将不会被视为耗电。

您可以通过Alarm ManagerJob SchedulerEvernote-JobsWork Manager 使用周期性任务。

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

【讨论】:

  • WorkManager 似乎是最好的方法,假设工作不必立即执行。它向后兼容 API 14,在 API 23+ 的设备上使用 JobScheduler,在 API 14-22 的设备上使用 BroadcastReceiver + AlarmManager
  • WorkManager 的关键在于 WorkManager 用于可延迟的任务,即不需要立即运行
【解决方案7】:

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

【讨论】:

  • 我喜欢你的 try catch 解决方案。对我来说这是一个解决方案,因为有时 context.startService 在后台工作 - 有时不是 - 这看起来是唯一最好的方法,否则你必须在你的主类 extending Applicationimplementing ActivityLifecycleCallbacks 中实现更多代码并跟踪应用程序是否在前台或后台,并相应地开始你的意图。
  • 这个异常能被捕获吗?
  • 存在一个潜在错误 - 虽然 context.startService(intent)try 块中被 try/catch 捕获,但同一代码行在 catch 块中又重复了一次,这可能导致崩溃。
【解决方案8】:

使用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

的广播接收器类之后
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());
  }
 }

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

[编辑]:您可以将工作管理器用于 Android 中的任何类型的后台任务。

【讨论】:

    【解决方案9】:

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

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

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

    如果需要,升级 google play 服务和 google 存储库。

    【讨论】:

    • 这并没有回答问题,问题也与firebase有任何关系。它应该作为评论。
    【解决方案10】:

    我看到很多建议只使用 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);
    

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

    【讨论】:

    • 目前 WorkManager 仍处于 alpha 阶段。
    • 2019 年 3 月 5 日 - WorkManager 1.0.0 稳定版。
    • 应该使用 WorkManager 而不是使用 interservice 或 JobIntentService
    • WorkManager is intended for tasks that are deferrable—that is, not required to run immediately... 这可能是最简单的,但是,我的应用需要一个后台服务来立即执行用户的请求!
    • 如果您需要立即完成任务,那么您应该使用前台服务。用户将看到通知并知道您正在工作。如果您需要帮助决定使用什么,请查看文档。他们有一个很好的后台处理指南。 developer.android.com/guide/background
    【解决方案11】:

    如果应用程序在后台运行时任何 Intent 之前都可以正常工作,那么从 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);
      }
      

    下面的类以前是扩展服务类

    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. JobIntentService 需要com.android.support:support-compat - 我使用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"
      

    希望这会有所帮助。

    【讨论】:

      【解决方案12】:

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

      【讨论】:

      • 建议仅在用户需要知道服务正在运行的情况下使用前台服务。典型的例子是在后台播放音乐。还有其他一些有意义的情况,但您不应该只是将所有服务都转换为前台服务。当您只需要在后台做一些工作并保证它会运行时,请考虑将您的服务转换为使用 Google 架构组件中的 WorkManager。
      • startForegroundService 需要权限,否则java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE。修复stackoverflow.com/a/52382711/550471
      【解决方案13】:

      由于对此答案的投票存在争议(截至本次编辑为 +4/-4),请先查看其他答案,并将其用作最后的手段。我只为a networking app that runs as root 使用了此一次,我同意一般情况下不应使用此解决方案的普遍意见。

      原答案如下:

      其他答案都是正确的,但我想指出解决此问题的另一种方法是要求用户为您的应用禁用电池优化(这通常不是一个好主意,除非您的应用是系统有关的)。请参阅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
      

      【讨论】:

      • 要求您的用户让您使用尽可能多的电池免费通行证并不是一个好的解决方案。考虑将您的代码转换为对电池更友好的解决方案。您的用户会感谢您的。
      • @TALE 并非每个后台服务都可以使用JobScheduler 等进行电池友好。一些应用程序需要在比典型同步应用程序更低的级别上运行。当这不起作用时,这是一种替代解决方案。
      【解决方案14】:

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

      【讨论】:

        【解决方案15】:

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

        【讨论】:

        【解决方案16】:

        我也有这个问题

        添加了这个库

        implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
        

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

        【讨论】:

          【解决方案17】:

          这实际上是因为手机在屏幕外,或者您在启动服务时按下了电源按钮。对我有用的解决方案是 启动一个活动,当它进入 onResume 然后启动服务。 就我而言,它正在启动并启动服务。

          【讨论】:

            猜你喜欢
            • 2019-11-21
            • 1970-01-01
            • 1970-01-01
            • 2018-12-23
            • 2018-10-02
            • 1970-01-01
            • 1970-01-01
            • 2021-12-13
            • 1970-01-01
            相关资源
            最近更新 更多