Android 9.0: 在 onResume() 后,不允许启动服务:应用处于后台。

80

我有一个音乐播放器,它试图在ActivityonResume()中启动一个Service。出于简洁起见,我删除了一些行,但代码实际上是:

@Override
protected void onResume() {
    super.onResume();

    startService(new Intent(this, MusicService.class));
}

根据崩溃日志,这个问题会在一些运行Android P系统的设备上引发异常:

Caused by java.lang.IllegalStateException: Not allowed to start service Intent { cmp=another.music.player/com.simplecity.amp_library.playback.MusicService }: app is in background uid UidRecord{6a4a9c6 u0a143 TPSL bg:+3m25s199ms idle change:cached procs:1 seq(1283,1283,1283)}
       at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
       at android.app.ContextImpl.startService(ContextImpl.java:1532)
       at android.content.ContextWrapper.startService(ContextWrapper.java:664)
       at android.content.ContextWrapper.startService(ContextWrapper.java:664)
       at com.simplecity.amp_library.utils.MusicServiceConnectionUtils.bindToService(SourceFile:36)
       at com.simplecity.amp_library.ui.activities.BaseActivity.bindService(SourceFile:129)
       at com.simplecity.amp_library.ui.activities.BaseActivity.onResume(SourceFile:96)

在调用onResume()(和super.onResume())之后,我的应用程序如何可能处于后台?

这对我来说毫无意义。这可能是平台的错误吗?所有受此崩溃影响的3500多个用户都在Android P上。


7
我对你没有一个好的答案,但我可以确认我们也看到了这个问题。我们从未在内部复现过它,但同样地,在onResume()启动服务时我们也看到了它。我怀疑这是Android P的错误。 - Kevin Coppock
9
好的,很高兴不只有我一个人遇到了这个问题。这个问题已经被报告了: https://issuetracker.google.com/issues/113122354 - Tim Malseed
2
可能是重复的问题:Android 8.0: java.lang.IllegalStateException: Not allowed to start service Intent。虽然它是后来提出的,但已经有答案了。 - Access Denied
3
同时,针对 Android 9 或更高版本并使用前台服务的应用程序必须请求 FOREGROUND_SERVICE 权限。这是一项普通权限,因此系统会自动授予请求该权限的应用程序。来源:https://developer.android.com/about/versions/pie/android-9.0-changes-28 - Access Denied
3
@iaindownie 是的,看起来已经生效了,它已经在生产环境中运行了一段时间,而我当时不知道的新实例开始出现,因为它已经进展更远了。 - Ben987654
显示剩余13条评论
8个回答

17

谷歌提供了一种解决方案:

这个问题已经在未来的Android版本中得到解决。

有一个解决方法可以避免应用程序崩溃。应用程序可以在Activity.onResume()中调用ActivityManager.getRunningAppProcesses()获取进程状态,并在重要性级别低于ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND时避免启动服务。如果设备没有完全唤醒,活动将立即暂停,最终在完全唤醒后恢复。

所以我认为应该像这样:

// hack for https://issuetracker.google.com/issues/113122354
    List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
    if (runningAppProcesses != null) {
        int importance = runningAppProcesses.get(0).importance;
        // higher importance has lower number (?)
        if (importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)
            URLPlayerService.startActionBroadcastServiceData(PlayerActivity.this);
    }

我使用了处理程序来解决问题,虽然它工作得很好,但并非100%可靠:

// hack for https://issuetracker.google.com/issues/113122354
   handler.postDelayed(() -> URLPlayerService.startService(PlayerActivity.this),200);

4
根据issuetracker上的说法:“活动将立即暂停,直到设备完全唤醒后才会恢复。”因此,我认为活动将会恢复,并且进程重要性将会是IMPORTANCE_FOREGROUND。所以不需要处理。这是正确的吗? - David
3
以上的“hack”对我有效(注:在我的情况下,所有报告此问题的崩溃均发生在三星平台上,运行Android 9+)。为了完整起见,在“hack”上方添加以下行以定义activityManager:“ActivityManager activityManager =(ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);”。 - gOnZo
@David 你试过不用处理程序吗? - Xi Wei
@XiWei 是的,我测试过了,没问题。只需在 onResume 中检查重要性后使用 startService 即可。 - David

6

更新:这个方法在我们的生产环境中运行良好,但是并不完美。在过去的一个半月中,我只收到了一个崩溃报告,否则可能会有超过一百个。在此得到适当修复之前,这似乎是我们目前最好的选择。也许如果我将时间延长到300以上,那个崩溃就永远不会发生了?

我们正在测试这个方法,目前看来它似乎是有效的。随着我们看到更多的结果,我们将进行更新。

class ResumingServiceManager(val lifecycle: Lifecycle) : LifecycleObserver {

    init {
        lifecycle.addObserver(this)
    }

    val disposable: CompositeDisposable = CompositeDisposable()

    fun startService(context: Context, intent: Intent) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            context.startService(intent)
        } else {
            Single.just(true)
                    .delaySubscription(300, TimeUnit.MILLISECONDS)
                    .subscribeOn(AndroidSchedulers.mainThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribeBy(
                            onSuccess = {
                                context.startService(intent)
                            }

                    ).addTo(disposable)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun stopped() {
        disposable.clear()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun destroy() {
        lifecycle.removeObserver(this)
    }
}

onCreate()中初始化它,然后在onResume中任何时候想要启动服务时,只需调用resumingServiceManager.startService(this, intent)
它是生命周期感知的,因此如果暂停,则会清除可处理的内容,从而防止在立即打开/关闭时触发onSuccess,可能正在前往后台。

1
对于 Oreo+ 情况,这是否相当于以下代码:handler.postDelayed({context.startService(intent)}, 300) 并在 stopped() 中调用 handler.removeCallbacksAndMessages(null) - Carmen
1
我相信它会起到同样的作用。可以通过添加一些额外的日志并将时间设置为一两秒来测试它,以确认延迟/正确删除和能够在反复打开/关闭应用程序时触发。 - Ben987654
有人能确认上面发布的非 Rx 版本使用 handler.postDelayed 是否有效吗? - Rinav
@rinav 我会考虑使用谷歌官方推荐的解决方案。我已经删除了上面的生命周期逻辑,并将他们的修复放在else分支中,而不是Rx中,这确实可以防止崩溃。唯一的问题是,它是否完全可靠,我还不知道,但我们很快就会发布,到时候就会发现。上面的Rx版本虽然大大减少了崩溃,但在我替换每个调用的时候,仍然每月有3-4次崩溃,而不是400-500次。好消息是,通过这个文件路由所有的调用,使得更换修复变得容易! - Ben987654

5

我们团队也遇到了同样的问题。我的日历显示的是2019年4月5日,但在我的三星Galaxy S9+上(安卓9.0 One UI),问题仍然存在。

我们在onResume()方法中启动服务,在onPause()方法中解绑服务。

如何复现

只需锁定您的设备,在具有此逻辑的活动屏幕上停留10-15分钟不操作。解锁屏幕后,应用程序将崩溃。

如何修复

我们发现了可行的解决方案。自安卓8以来,请使用android.os.Handler.post(...)来启动您的服务。

示例(Kotlin):

override fun onResume() {
    super.onResume()
    Handler().post {
        val serviceIntent = Intent(activity, SomeService::class.java)
        activity?.startService(serviceIntent)
        activity?.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

祝你好运!


我已经做出了这个更改,但是在Android 9上的SM-A750FN手机仍然会因为同样的错误而崩溃。 - Vajk Hermecz

5

这个问题已经在Android Issue Tracker中被标记为“fixed”:

据推测,修复将会在Android Q的一个版本中发布。

根据关闭该问题的谷歌员工所说,

有一种解决方法可以避免应用程序崩溃。应用程序可以在Activity.onResume()中通过调用ActivityManager.getRunningAppProcesses()来获取进程状态,并且如果重要性级别低于ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,则避免启动服务。如果设备没有完全唤醒,活动会立即暂停,并最终在完全唤醒后再次恢复。


2
我找到了一种重现问题的方法。
在Android模拟器上测试,版本为Android 10、11和12 DP2:
1. 启动应用程序,在Activity.onResume()中启动服务。 2. 按下Home按钮将活动状态更改为“已停止”。 3. 等待系统销毁服务(logcat日志将出现:“ActivityManager:由于应用程序空闲而停止服务”)。 4. 打开Android设置->系统->手势->系统导航。 5. 更改系统导航选项为其他选项。 6. 应用程序将崩溃。
我还在Android问题跟踪器中创建了一个新问题,并提供了这些步骤:https://issuetracker.google.com/issues/183716536

1
我已经找到了一个解决方案,其中我使用进程范围并确保在日志记录逻辑中不包括作用域取消异常。 就像这样:
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


0

看起来错误信息不太明确,我发现消息的最后一部分是指服务所在的应用程序,而不是试图启动该服务并绑定到它的应用程序。

如果服务的应用程序未在前台运行(即使服务本身是后台服务),我会收到以下错误:

W/System.err: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.mycompany.myserviceapp/.MyService }: app is in background uid null
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1505)
        at android.app.ContextImpl.startService(ContextImpl.java:1461)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)

现在,如果我运行具有服务的应用程序,因此在启动将尝试启动服务的“客户端”应用程序之前,它就立即处于前台,则对于服务启动,一切都正常。

如果服务是“后台服务”,并且服务的应用程序不在前台,则可能会终止该服务-因此,仅仅让服务启动并不足以使其保持运行。

这似乎是从Android 7到Android 8的更改结果,在Oreo中,他们开始有点限制。

Oreo的后台执行限制的Android文档中已经解释了所有内容。

文档中的建议是将您的逻辑迁移到计划的作业,或将服务设置为前台服务-其中将显示服务正在运行的视觉指示。


-3
也许android.arch.lifecycle可以作为解决Android 9 bug的一种变通方法?
public class MyActivity extends Activity implements LifecycleObserver {

    protected void onResume() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
                startService(intent);
            } else {
                ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
            }
        } else {
            startService(intent);
        }
    }


    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void onEnterForeground() {
        startService(intent);
        ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
    }
}

我在这里找到了它。


1
生命周期观察者只是响应活动触发的事件。因此,这实际上只是将startService()调用移动到onStart()中。正如您所说,这可能是一个方便的解决方法。 - Tim Malseed
1
不幸的是,我在使用Lifecycle.State.STARTED时遇到了相同的异常。我将尝试使用Lifecycle.State.RESUMED@OnLifecycleEvent(Lifecycle.Event.ON_RESUME),它会在onResume()之后被调用,看看是否可以解决这个问题。 - MarcinLe
我更新了我的应用程序,使用了State.RESUMED。几天来一切都正常,所以我希望这解决了问题,但我刚刚注意到在Android 9上又出现了另一个崩溃 :( - MarcinLe
我们已经实施了这个方案,但是我们仍然看到大约相同数量的崩溃。实际上,我们现在通过try/catch来防止这种崩溃,并记录一些细节到fabric answers中。我们看到与崩溃时相同频率的fabric answers事件。在fabric answers的细节中,我们包括进程生命周期所有者的生命周期状态。它始终处于恢复状态。我认为我们将尝试添加300毫秒的延迟,如@ben987654的答案建议的那样,并观察效果如何。 - Carmen
使用ProcessLifecycleOwner和Lifecycle.Event.ON_RESUME不能百分之百地工作,因为在ProcessLifecycleOwner的javadoc中指定:仅当第一个活动恢复或在ON_PAUSE之后恢复活动时才调用ON_RESUME,而ON_PAUSE是在最后一个活动暂停后延迟调度的... 谷歌员工说: “如果设备还没有完全唤醒,活动将立即暂停,并在完全唤醒后再次恢复。”因此,您无法确定活动是否“在完全唤醒后再次恢复”比调度ON_PAUSE的延迟时间长。 - Kélian
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接