源码分析Android系统后台应用启动服务crash
有的时候表面上看起来似乎一切运行正常,但是通过查看日志发现是有问题的。
下面这段日志,截取自某设备开机过程的日志。通过查看,发现有GMS的crash。
--------- beginning of crash
03-09 03:46:43.183 3804 3804 E AndroidRuntime: FATAL EXCEPTION: main
03-09 03:46:43.183 3804 3804 E AndroidRuntime: Process: com.google.android.gms.persistent, PID: 3804
03-09 03:46:43.183 3804 3804 E AndroidRuntime: java.lang.RuntimeException: Unable to start receiver com.google.android.gms.chimera.GmsIntentOperationService$PersistentTrustedReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { act=android.intent.action.LOCKED_BOOT_COMPLETED flg=0x9000010 cmp=com.google.android.gms/.chimera.GmsIntentOperationService (has extras) }: app is in background uid UidRecord{bf88dc8 u0a10 RCVR idle change:uncached procs:2 seq(0,0,0)}
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ActivityThread.handleReceiver(ActivityThread.java:3388)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ActivityThread.access$1200(ActivityThread.java:199)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1661)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.os.Looper.loop(Looper.java:193)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6669)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:866)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: Caused by: java.lang.IllegalStateException: Not allowed to start service Intent { act=android.intent.action.LOCKED_BOOT_COMPLETED flg=0x9000010 cmp=com.google.android.gms/.chimera.GmsIntentOperationService (has extras) }: app is in background uid UidRecord{bf88dc8 u0a10 RCVR idle change:uncached procs:2 seq(0,0,0)}
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1532)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:664)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at iap.startService(:com.google.android.gms@19831064@19.8.31 (080306-284611645):3)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:664)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:664)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:664)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at bkn.a(:com.google.android.gms@19831064@19.8.31 (080306-284611645):4)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at bkn.onReceive(:com.google.android.gms@19831064@19.8.31 (080306-284611645):7)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at gvr.onReceive(:com.google.android.gms@19831064@19.8.31 (080306-284611645):3)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: at android.app.ActivityThread.handleReceiver(ActivityThread.java:3379)
03-09 03:46:43.183 3804 3804 E AndroidRuntime: ... 8 more
下面,我们按照问题的解决顺序分析一下这题:
- 这是什么问题?
- 从源码的角度弄清楚产生问题的原因
- 找到解决方案
这是什么问题?
首先分析日志
--------- beginning of crash
系统发生了crash
AndroidRuntime: Process: com.google.android.gms.persistent, PID: 3900
发生crash的是应用程序进程com.google.android.gms.persistent
AndroidRuntime: java.lang.RuntimeException: Unable to start receiver com.google.android.gms.chimera.GmsIntentOperationService$PersistentTrustedReceiver: java.lang.IllegalStateException: Not allowed to start service Intent { act=android.intent.action.LOCKED_BOOT_COMPLETED flg=0x9000010 cmp=com.google.android.gms/.chimera.GmsIntentOperationService (has extras) }: app is in background uid UidRecord{bf88dc8 u0a10 RCVR idle change:uncached procs:2 seq(0,0,0)}
启动GmsIntentOperationService服务的时候,系统抛出了Android运行时异常。原因是后台uid的app不允许启动服务。其实就是不允许后台应用启动服务。
AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1577)
直接看Caused by
下面一行,这个运行时异常是ContexImpl类的startServiceCommon()方法抛出的,位于ContextImpl.java文件的1577行。
从源码角度弄清楚产生问题的原因
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) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {
throw new IllegalStateException(
line1577 "Not allowed to start service " + service + ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
AMS返回的ComponentName是经过特殊处理的,本题中返回的包名是“?”号。Android系统的Framework过于庞大,在熟悉或不熟悉的情况下,都可以通过搜索关键字app is in background uid
,直接找到问题相关的具体代码段。
Android/frameworks/base$ grep -r "app is in background uid"
services/core/java/com/android/server/am/ActiveServices.java: return new ComponentName("?", "app is in background uid " + uidRec);
ActiveServices.startServiceLocked()方法过于庞大,只贴关键部分代码 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 (forcedStandby || (!r.startRequested && !fgRequired)) {
// Before going further -- if this app is not allowed to start services in the
// background, then at this point we aren't going to let it period.
final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
Slog.w(TAG, "Background start not allowed: service "
+ service + " to " + r.name.flattenToShortString()
+ " from pid=" + callingPid + " uid=" + callingUid
+ " pkg=" + callingPackage + " startFg?=" + fgRequired);
... ...
// This app knows it is in the new model where this operation is not
// allowed, so tell it what has happened.
UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
... ...
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
return cmp;
}
AMS.getAppStartModeLocked()方法返回的不是ActivityManager.APP_START_MODE_NORMAL,这里就返回一个packagename是"?"包名是原因的ComponentName,阻断service的进一步启动。 getAppStartModeLocked()方法是取得应用程序的启动模式。应用程序有四中启动模式,分别是:正常启动、延迟启动、延迟启动并抛出异常、取消启动。
/** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: normal free-to-run operation. */
public static final int APP_START_MODE_NORMAL = 0;
/** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later. */
public static final int APP_START_MODE_DELAYED = 1;
/** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later, with
* rigid errors (throwing exception). */
public static final int APP_START_MODE_DELAYED_RIGID = 2;
/** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: disable/cancel pending
* launches; this is the mode for ephemeral apps. */
public static final int APP_START_MODE_DISABLED = 3;
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
UidRecord uidRec = mActiveUids.get(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
+ (uidRec != null ? uidRec.idle : false));
if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {
boolean ephemeral;
if (uidRec == null) {
ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
UserHandle.getUserId(uid), packageName);
} else {
ephemeral = uidRec.ephemeral;
}
if (ephemeral) {
// We are hard-core about ephemeral apps not running in the background.
return ActivityManager.APP_START_MODE_DISABLED;
} else {
if (disabledOnly) {
// The caller is only interested in whether app starts are completely
// disabled for the given package (that is, it is an instant app). So
// we don't need to go further, which is all just seeing if we should
// apply a "delayed" mode for a regular app.
return ActivityManager.APP_START_MODE_NORMAL;
}
final int startMode = (alwaysRestrict)
? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
: appServicesRestrictedInBackgroundLocked(uid, packageName,
packageTargetSdk);
if (DEBUG_BACKGROUND_CHECK) {
Slog.d(TAG, "checkAllowBackground: uid=" + uid
+ " pkg=" + packageName + " startMode=" + startMode
+ " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid, false)
+ " onwhitelist(ei)=" + isOnDeviceIdleWhitelistLocked(uid, true));
}
if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
// This is an old app that has been forced into a "compatible as possible"
// mode of background check. To increase compatibility, we will allow other
// foreground apps to cause its services to start.
if (callingPid >= 0) {
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
proc = mPidsSelfLocked.get(callingPid);
}
if (proc != null &&
!ActivityManager.isProcStateBackground(proc.curProcState)) {
// Whoever is instigating this is in the foreground, so we will allow it
// to go through.
return ActivityManager.APP_START_MODE_NORMAL;
}
}
}
return startMode;
}
}
return ActivityManager.APP_START_MODE_NORMAL;
}
系统有埋日志,先放开DEBUG_BACKGROUND_CHECK
,重新编译service.jar,push到设备里面,再来看一下打印信息,可以提升分析问题的效率。
03-09 03:46:42.926 3109 3784 D ActivityManager: checkAllowBackground: uid=10010 pkg=com.google.android.gms rec=UidRecord{9514de0 u0a10 RCVR idle change:uncached procs:2 seq(0,0,0)} always=false idle=true
03-09 03:46:42.926 3109 3784 I ActivityManager: App 10010/com.google.android.gms targets O+, restricted
03-09 03:46:42.926 3109 3784 D ActivityManager: checkAllowBackground: uid=10010 pkg=com.google.android.gms startMode=2 onwhitelist=false onwhitelist(ei)=false
03-09 03:46:42.926 3109 3784 W ActivityManager: Background start not allowed: service Intent { act=android.intent.action.LOCKED_BOOT_COMPLETED flg=0x9000010 cmp=com.google.android.gms/.chimera.GmsIntentOperationService (has extras) } to com.google.android.gms/.chimera.GmsIntentOperationService from pid=3900 uid=10010 pkg=com.google.android.gms startFg?=false
从日志看start mode是2,即APP_START_MODE_DELAYED_RIGID。alwaysRestrict前面传入的是false。下面进入方法appServicesRestrictedInBackgroundLocked()
int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Persistent app?
if (mPackageManagerInt.isPackagePersistent(packageName)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " is persistent; not restricted in background");
}
return ActivityManager.APP_START_MODE_NORMAL;
}
// Non-persistent but background whitelisted?
if (uidOnBackgroundWhitelist(uid)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " on background whitelist; not restricted in background");
}
return ActivityManager.APP_START_MODE_NORMAL;
}
// Is this app on the battery whitelist?
if (isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ false)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName
+ " on idle whitelist; not restricted in background");
}
return ActivityManager.APP_START_MODE_NORMAL;
}
// None of the service-policy criteria apply, so we apply the common criteria
return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
三个if都不成立,接着进入方法appRestrictedInBackgroundLocked()
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Apps that target O+ are always subject to background check
if (packageTargetSdk >= Build.VERSION_CODES.O) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "App " + uid + "/" + packageName + " targets O+, restricted");
}
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
// ...and legacy apps get an AppOp check
int appop = mAppOpsService.noteOperation(AppOpsManager.OP_RUN_IN_BACKGROUND,
uid, packageName);
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Legacy app " + uid + "/" + packageName + " bg appop " + appop);
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
// If force-background-check is enabled, restrict all apps that aren't whitelisted.
if (mForceBackgroundCheck &&
!UserHandle.isCore(uid) &&
!isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ true)) {
if (DEBUG_BACKGROUND_CHECK) {
Slog.i(TAG, "Force background check: " +
uid + "/" + packageName + " restricted");
}
return ActivityManager.APP_START_MODE_DELAYED;
}
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
default:
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
}
设备的系统版本是P,所以在第一个if直接返回,返回的启动模式刚好是APP_START_MODE_DELAYED_RIGID。 后台应用限制,Android O以上的版本不允许后台应用启动服务。 在这套检查逻辑里面是有三个后门的,通过这些后门,可以突破上面的限制,使后台应用可以启动服务。后门在方法appServicesRestrictedInBackgroundLocked()里面。
- 后门1 Persistent app。应用程序把自己设置为Persistent app,可以突破后台应用无法启动服务的限制。
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:persistent="true"
android:theme="@style/AppTheme"
- 后门2 应用在后台应用白名单里面
/**
* Non-persistent appId whitelist for background restrictions
*/
int[] mBackgroundAppIdWhitelist = new int[] {
BLUETOOTH_UID
};
- 后门3 应用在电池白名单里面
boolean isOnDeviceIdleWhitelistLocked(int uid, boolean allowExceptIdleToo) {
final int appId = UserHandle.getAppId(uid);
final int[] whitelist = allowExceptIdleToo
? mDeviceIdleExceptIdleWhitelist
: mDeviceIdleWhitelist;
return Arrays.binarySearch(whitelist, appId) >= 0
|| Arrays.binarySearch(mDeviceIdleTempWhitelist, appId) >= 0
|| mPendingTempWhitelist.indexOfKey(uid) >= 0;
}
总结一下:这题产生的原因是后台应用gms想要启动服务,Android P系统不允许,被拦截并抛出异常。
找到解决方案
原因清楚了,系统的整个处理逻辑也了解,解决思路就很清晰了。可以从三个后门入手找解决方案。后门一是应用端处理,GMS是第三方应用无源代码,无法处理。后门二和后门三是系统端处理,看着后门2比较清晰,我们选择此门,确定代码层面的解决方法。
- 需要拿到GMS的uid字符串
反编译GMS.apk,从AndroidManifest.xml拿到sharedUserId字符串
android:sharedUserId="com.google.uid.shared"
- 在系统里面为GMS添加uid定义
Android/frameworks/base/core/java/android/os/Process.java
public static final int GOOGLE_UID = 1069;
Android/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private static final int GOOGLE_UID = Process.GOOGLE_UID;
mSettings.addSharedUserLPw("com.google.uid.shared", GOOGLE_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
- 添加GMS的uid到AMS里面的后台应用白名单
Android/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
int[] mBackgroundAppIdWhitelist = new int[] {
BLUETOOTH_UID,
GOOGLE_UID
};