源码分析Android系统后台应用启动服务crash

3,761 阅读7分钟

源码分析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比较清晰,我们选择此门,确定代码层面的解决方法。

  1. 需要拿到GMS的uid字符串

反编译GMS.apk,从AndroidManifest.xml拿到sharedUserId字符串

android:sharedUserId="com.google.uid.shared"
  1. 在系统里面为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);

  1. 添加GMS的uid到AMS里面的后台应用白名单

Android/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

int[] mBackgroundAppIdWhitelist = new int[] {
    BLUETOOTH_UID,
    GOOGLE_UID
};