Android O_GO后台启动服务改动
来源:互联网 发布:淘宝客点击数少 编辑:程序博客网 时间:2024/06/11 03:07
Android O_GO后台启动服务改动
1. 问题现象
应用在适配Android O/GO的系统时,会发现后台启动不了服务,会报出如下异常,并强退:
java.lang.RuntimeException: Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.android.test/com.android.test.TestService (has extras) }: app is in background uid UidRecord{255693 u0a26 RCVR idle procs:2 seq(0,0,0)} at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1506) at android.app.ContextImpl.startService(ContextImpl.java:1462) at android.content.ContextWrapper.startService(ContextWrapper.java:648) at android.content.ContextWrapper.startService(ContextWrapper.java:648)
异常状态是:”IllegalStateException”非法状态,
内容是:”Not allowed to start service”不允许启动服务,
原因是:”app is in background”应用在后台运行
为什么google会搞出这东西呢:
=> google对于app的权限释放得太多了,所以android手机卡顿、耗电快的问题一直都困扰着用户,
特别是国内很多流氓的apk,不仅自己占资源,还拉别的应用一起来。
google无奈,只能收回权限,目前收回了很多后台运行进程的权限(启动服务、接收广播等),如此处限制了后台启动服务。
这样做对于android系统性能、功耗确实会有提升,但是道高一尺魔高一丈,我们总是有办法的。
2. 问题原因分析
需要先找出问题的原因再讨论修复方案。
2.1 出错代码定位
在ContextImpl的startServiceCommon函数中爆出异常,
//frameworks/base/core/java/android/app/ContextImpl.java
private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); if (cn != null) { //... //此处就是曝出异常的地方,非法状态,不允许启动服务 } else if (cn.getPackageName().equals("?")) { throw new IllegalStateException( "Not allowed to start service " + service + ": " + cn.getClassName()); } } return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
startServiceCommon这个函数做的操作是AMS的startService,用于启动服务.
2.2 AMS的startService
接下去看AMS的startService,稍微注意一下传递的参数,里面有一个前台后台相关的requireForeground,可能跟问题有关系。
AMS代码位置
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, boolean requireForeground, String callingPackage, int userId) throws TransactionTooLargeException { //... //调用ActiveServices的startServiceLocked res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, requireForeground, callingPackage, userId); //... }
其最终会调用ActiveServices的startServiceLocked
2.3 ActiveServices的startServiceLocked
ActiveServices这里的代码很多都是很关键的,如果对Android组件Service感兴趣的最好把这个文件研究一下。
ActiveServices代码位置
//frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId) throws TransactionTooLargeException { //... // 启动服务之前有2个判断一个是startRequested,一个是fgRequired。 // startRequested代表的是:是否已经启动过服务,一般出现问题都是启动一个没有运行的服务, // 那么这个就是false。 // fgRequired这个就是启动服务传递的requireForeground, // 可以查看2.1章节的startServiceCommon函数。 if (!r.startRequested && !fgRequired) { // 这里面有个关键函数getAppStartModeLocked,判断是否运行启动服务 // 注意此处传递的最后2个参数:alwaysRestrict和disabledOnly都是false final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName, r.appInfo.targetSdkVersion, callingPid, false, false); // 如果不允许启动服务则会运行到里面 if (allowed != ActivityManager.APP_START_MODE_NORMAL) { //... UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid); // 此处就是不允许运行服务返回的原因"app is in background" // 和章节1中的问题现象的原因是一致的 return new ComponentName("?", "app is in background uid " + uidRec); } } //... }
这里面由于出现错误(问题现象具体可以查看:章节1),那么startRequested==false而且fgRequired==false,说明这个服务是第一次启动,而且是后台请求启动服务。
至于为什么不允许启动服务,我们还需要查看AMS的getAppStartModeLocked函数。
2.4 AMS判断并返回服务启动模式
AMS代码位置
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
1、getAppStartModeLocked返回值就是启动模式,其中此处传递的alwaysRestrict==false
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk, int callingPid, boolean alwaysRestrict, boolean disabledOnly) { UidRecord uidRec = mActiveUids.get(uid); //... // 此处alwaysRestrict==false,于是调用的是appServicesRestrictedInBackgroundLocked final int startMode = (alwaysRestrict) ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk) : appServicesRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk); //... return startMode; //... }
根据alwaysRestrict的值会调用appRestrictedInBackgroundLocked或者appServicesRestrictedInBackgroundLocked;
其中appRestrictedInBackgroundLocked是直接根据应用sdk进行判断,
appServicesRestrictedInBackgroundLocked会进行条件过滤,直接运行部分应用启动服务,其它的进行应用sdk的判断。
2、appServicesRestrictedInBackgroundLocked进行条件过滤,允许部分启动服务
int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) { // 如果是常驻内存的,可以直接启动服务 if (mPackageManagerInt.isPackagePersistent(packageName)) { //... return ActivityManager.APP_START_MODE_NORMAL; } // 如果是非常驻内存的话,但是在白名单列表里面的uid也是允许的 // 目前这个白名单里面就只有一个:蓝牙BLUETOOTH_UID = 1002 if (uidOnBackgroundWhitelist(uid)) { //... return ActivityManager.APP_START_MODE_NORMAL; } // 如果是在电源相关的白名单里面,也是允许启动服务的 if (isOnDeviceIdleWhitelistLocked(uid)) { //... return ActivityManager.APP_START_MODE_NORMAL; } // 默认的策略是appRestrictedInBackgroundLocked return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk); }
分别对于:
1) 是否常驻内存应用,常驻内存允许启动服务
2) 如果是蓝牙也是允许启动服务
3) 是在电源相关的DeviceIdle白名单里面,允许启动服务的
4) 如果都不是则执行默认策略appRestrictedInBackgroundLocked
3、appRestrictedInBackgroundLocked默认限制策略
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) { // 如果apk的sdk版本大于AndroidO的话,那么默认是不允许启动服务的 if (packageTargetSdk >= Build.VERSION_CODES.O) { //... return ActivityManager.APP_START_MODE_DELAYED_RIGID; } // 如果是之前版本的apk,会查看AppOps是否允许后台运行权限, // 由于我们sdk版本肯定会升级的,这个就暂时不考虑了 int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName); //... switch (appop) { case AppOpsManager.MODE_ALLOWED: return ActivityManager.APP_START_MODE_NORMAL; case AppOpsManager.MODE_IGNORED: return ActivityManager.APP_START_MODE_DELAYED; default: return ActivityManager.APP_START_MODE_DELAYED_RIGID; } }
如果apk的sdk版本大于AndroidO的话,那么默认是不允许启动服务的,那么要适配Android O/GO以后的版本,此处是绕不过去的坎,建议尽早处理。
3. 修改方案
有上面可知,问题原因主要是后台启动了服务,在这部分Android O/GO做了限制,
根据章节2.4的过滤条件可以提供如下修改方案:
1) 提升应用优先级到常驻内存级别 (不建议应用采纳这种方式,会导致手机出现很多性能问题)
=> 在AndroidManifest.xml添加android:persistent=”true”
并且签上系统签名
2) 类似与蓝牙BLUETOOTH_UID一样放在白名单里面(需要拥有源码修改权限,而且修改了源码,不利于apk的版本兼容,不建议采纳)
3) 添加在电源相关的DeviceIdle白名单(不建议添加,可能导致功耗增加)
按照上面的都说是不建议采取,是否没有办法了呢?
我们继续往源头找找看看是否有办法:
在章节2.1、章节2.3有一个参数requireForeground/fgRequired,是否前台请求,如果requireForeground/fgRequired为false才会进行后台请求判断,如果是true的话,是可以直接绕过去的
回到ActiveServices的startServiceLocked=>
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId) throws TransactionTooLargeException { //... // fgRequired是true可以直接绕过 if (!r.startRequested && !fgRequired) { //.. } //... }
那么方案4,我们可以采取如下方式
4) 通过Context(activity、service的this都是包含context的,故不用担心调用方式),将之前的startService,修改成ContextImpl的startForegroundService或者startForegroundServiceAsUser方法,启动一个前台服务。
//frameworks/base/core/java/android/app/ContextImpl.java
@Override public ComponentName startForegroundService(Intent service) { warnIfCallingFromSystemProcess(); return startServiceCommon(service, true, mUser); } @Override public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) { return startServiceCommon(service, true, user); }
好了,那么上述的第4种方法可以很好解决该问题。
ps:注意上面的方法是启动前台服务,你的服务需要是前台的,这个怎么做呢,下面提供2种方法:
1) 在service中调用startForeground (最常见方法)
2) 设置service为前台,可以使用AMS的setProcessImportant设置优先级别 (优点是:不会在通知栏中出现通知图标。缺点是:需要相应的权限)
- Android O_GO后台启动服务改动
- Android开机自启动后台服务-RECEIVE_BOOT_COMPLETED
- centos后台启动服务
- nodejs 后台服务启动
- nodejs 后台服务启动
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- android service后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- -------------------Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android SERVICE后台服务进程的自启动和保持
- Android Service后台服务进程的自启动和保持
- 本月腾讯,阿里,美团等技术团队的精品文章推送
- MySQL 5.6 for Windows 解压缩版配置安装
- 实时分析百度统计的数据?互联网人定要看!
- BZOJ 1673 浅谈深度优先式搜索及斐波拉契启发式AstaR剪枝
- muduo库源码学习(base)Date
- Android O_GO后台启动服务改动
- P2S、P2P、P2SP之对比
- 学习metasploit漏洞总结
- HTML5权威指南笔记:16-理解CSS(内容简介)
- RView
- 微服务架构下,如何实现分布式跟踪?
- 【BigHereo 38】---L11---C++错题总结
- 网络获取视屏播放
- 【小程序】eval被禁,代替方法