Android Fk:【JavaCrash】Android 26以后限制使用startService启动后台服务
一. 问题概述
1.出错调用栈
E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.unionpay.uppay.action.HCE pkg=com.sankuai.meituan }: app is in background uid null
07-23 19:06:29.734 15328 15377 E AndroidRuntime: FATAL EXCEPTION: Thread-9
07-23 19:06:29.734 15328 15377 E AndroidRuntime: Process: com.unionpay.uppay, PID: 15328
07-23 19:06:29.734 15328 15377 E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.unionpay.uppay.action.HCE pkg=com.sankuai.meituan }: app is in background uid null
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1515)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1471)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:654)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at com.unionpay.mobile.android.hce.f.a(Unknown Source:33)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at com.unionpay.mobile.android.hce.h.run(Unknown Source:6)
2.主要原因:
Android O 8.0(API 26之后) 之后不再允许后台service直接通过startService方式去启动, 具体行为变更
如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。新的 Context.startForegroundService() 函数将启动一个前台服务。
现在,即使应用在后台运行, 系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。
3.解决办法:
1. 修改启动方式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
//并且在service里再调用startForeground方法,不然就会出现ANR
context.startForeground(SERVICE_ID, builder.getNotification());
该种方式启动用户会有感知啊,有个通知额,==
2. 调用者作好保护,防止被炸
try {
Intent cameraIntent = new Intent("XXX.XXX");
cameraIntent.setPackage("com.XXX.XXX");
mContext.startServiceAsUser(cameraIntent, UserHandle.CURRENT);
} catch (Exception e) {
Slog.e(TAG, "IllegalAccessException", e);
}
3. 放弃使用起后台服务唤醒进程
采用jobJobScheduler替换需要起后台服务的唤醒操作方式。
二.原因分析
1. 先搜所log打印的地方
搜索“Not allowed to start service”
//frameworks/base/core/java/android/app/ContextImpl.java
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
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();
}
}
搜索“app is in background”
//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 {
// If this isn't a direct-to-foreground start, check our ability to kick off an
// arbitrary service
if (!r.startRequested && !fgRequired) {
...
return new ComponentName("?", "app is in background uid " + uidRec);
...
}
}
下面就是找这两者之间的联系了,概括在图中:
这里写图片描述
三.总结
- 从抛异常的地方可以看出,最终将是调用者会crash(如果不保护处理的话)
即 A应用进程以startservice的形式调用B应用进程的service,如果满足以下条件:
a.O及O以上的手机平台上
b.B应用进程的AndroidManifest里声明了targetSdk大于与等于26
c.B应用进程不是persistent应用
d.B应用进程当前进入后台且处于idle状态
e.B应用不在电源管理的白名单中
f.B应用进程不再运行后台运行的白名单中
此时A应用进程就会crash(如果不做相关保护的话) - A应用进程可以是system_server
- O及O以上的app尽量不要通过起后台service进行操作,需要用到后台service的话可以通过JobScheduler进行处理,即如果你是个简单的三方应用,不要再使用
调用后台service的形式唤醒应用了,调用者会很危险!!!
4.具体流程图如下:
(提供上面简图的draw.io的xml文件,可以使用draw.io导入修改
提供如下时序图的uml文件,可使用带plantuml插件的inteliJ AS打开编辑
https://pan.baidu.com/s/17MO9CXdcSBLI_kuywvCuZA)
这里写图片描述