导读
今天在用户的错误列表上看到这么个bug
重现情景
由于我们的桌面小控件在onUpdate()方法里用Context.startService()启动了Service.当app的进程没有启动时,把桌面部件拉到Launcher桌面上就会报这个错误.
Android官网在8.0时的后台服务启动优化的一些措施
后台服务限制:处于空闲状态时,应用可以使用的后台服务存在限制。 这些限制不适用于前台服务,因为前台服务更容易引起用户注意。 在 Android 8.0 之前,创建前台服务的方式通常是先创建一个后台服务,然后将该服务推到前台。 Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。 在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground()方法以显示新服务的用户可见通知。 如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
解决方案
我总结一下就是8.0后,如果一个处于后台的应用想要启动Service就必须调用Context.startForegroundService()并且5秒内在该Service内调用startForeground()
下面看看源码的变动情况
源码解析
首先是后台应用调用Context.startService()启动Service为什么会报错 启动Service的入口ContextImpl.startService()
讯享网
1处就是我们bug抛出异常的地方Not allowed to start service Intent... 我们先看看ActivityManager.getService().startService()的返回逻辑
启动Service会调用ActiveServices.startServiceLocked()
讯享网
这里的fgRequired是从ContextImpl.startServiceCommon(fgRequired:false)传进来的,为false. 2标记处是不是又看到相关bug信息了 "app is in background uid...",于是我们看看allowed返回值mAm.getAppStartModeLocked()
allowed的返回值就是startMode.这里alwaysRestrict是传入的参数false,这里的uidRec由于应用进程都未启动,于是uidRec.idle为true表示空闲进程,所以我们直接看appServicesRestrictedInBackgroundLocked()

这个方法会判断是否是Persistent app,白名单,电量白名单应用,很显然普通app都不是,于是进入appRestrictedInBackgroundLocked()看看
这里的packageTargetSdk刚好是O,所以返回ActivityManager.APP_START_MODE_DELAYED_RIGID了.由于返回值不是ActivityManager.APP_START_MODE_NORMAL.于是就return new ComponentName("?", "app is in background uid " + uidRec);然后就出现了开头的异常.
下面看下Context.startForegroundService启动Service的逻辑 入口依旧为ContextImpl.startForegroundService()
这里与startService的区别就在于传入的fgRequired为true.于是一路 ContextImpl.startServiceCommon()-->ActivityManagerService.startService()-->ActiveServices.startServiceLocked(),由于fgRequired为true,就跳过刚才那段逻辑下面就是正常的Service启动流程了. 那么还有一个问题,为什么还需要在5秒内调用Service.startForeground()呢? 在启动Service的过程中会调用到ActiveServices.bringUpServiceLocked()方法,然后会调用ActiveServices.sendServiceArgsLocked()
在3处会调用scheduleServiceForegroundTransitionTimeoutLocked()作用就是发送一个延时5秒的message
看下这个消息的处理
又来到ActiveServices
这里就是调用stopServiceLocked(r)把service关掉了.那么Service.startForeground()一定会有代码取消这个消息,来看:
mActivityManager就是调用AMS
来看setServiceForegroundInnerLocked()
这里就removeMessages(SERVICE_FOREGROUND_TIMEOUT_MSG)取消这个message了.
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/10533.html