如何修改 startService() 的源代码以识别它是否是从后台调用的?

How was the source code of startService() modified to recognize if it was called from background?

从大约 Android 9 开始,如果从 background 调用 startService(),它会抛出 IllegalStateException。我在开发人员控制台中多次看到此异常:

java.lang.IllegalStateException:
  at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1666)
  at android.app.ContextImpl.startService (ContextImpl.java:1611)

在这些情况下,Google 建议改为在 5 秒内 startForegroundService() 调用 startForeground()See "Background execution limits".

无论如何,从前景调用startService()是完全可以的。现在,我想知道应用程序在前台 Android recognizes/decides 究竟是如何不错误地抛出 IllegalStateException?

我开始挖掘 Android9/10 的源代码并将其与 8/7 进行比较,以发现如何修改 startService() 以识别它是否是从 foreground/background 调用的。但我相信在我之前的许多开发人员已经这样做了,如果他们能给出答案,我会很高兴。

在 AOSP10 (10.0.0_r25) 中:

服务器端:

在 startServiceLocked 来自 frameworks\base\services\core\java\com\android\server\am\ActiveServices。java:

        // 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.shortInstanceName
                    + " 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.mProcessList.getUidRecordLocked(r.appInfo.uid);
            return new ComponentName("?", "app is in background uid " + uidRec);
        }

然后在客户端:

在 ContextImpl.java 作为您的日志:

            else if (cn.getPackageName().equals("?")) {
                throw new IllegalStateException(
                        "Not allowed to start service " + service + ": " + cn.getClassName());
            }

当应用出现以下情况时,允许后台启动。

  1. 该应用程序是持久的。只有预建的系统应用程序可以执行此操作。
  2. 应用程序在一段时间内没有idle. When the app process is in background,它将被设置为空闲。
  3. 该应用处于空闲状态但在白名单中。 backgroundWhitelistUid。只有具有系统 uid 的应用才能将应用添加到此列表。

this link into Android's soure code 之后我们发现 getAppStartModeLocked():

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
        int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
    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 || 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));
            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;
}

而方法 appRestrictedInBackgroundLocked()(也从 appServicesRestrictedInBackgroundLocked() 调用作为回退)决定 startMode:

// Unified app-op and target sdk check
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:
            return ActivityManager.APP_START_MODE_NORMAL;
        case AppOpsManager.MODE_IGNORED:
            return ActivityManager.APP_START_MODE_DELAYED;
        default:
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
    }
}

但前景或背景的最终决定在 ActivityManager.isProcStateBackground(uidRec.setProcState):

/** @hide Should this process state be considered a background state? */
public static final boolean isProcStateBackground(int procState) {
    return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}

所以,这里第一种方法的这一部分获取当前前景或背景的状态:

                ProcessRecord proc;
                synchronized (mPidsSelfLocked) {
                    proc = mPidsSelfLocked.get(callingPid);
                }