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

问题描述

从 Android 9 开始,如果从 后台 调用 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 究竟是如何识别/决定应用程序在前台而不是错误地抛出 IllegalStateException 的?

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

解决方法

在 AOSP10 (10.0.0_r25) 中:

服务器端:

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

        // 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,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());
            }
,

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,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,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);
                }
,

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

  1. 该应用具有持久性。只有预构建的系统应用可以执行此操作。
  2. 该应用不是 idle。当应用进程处于 background 一定时间后,它会被设置为空闲。
  3. 该应用处于空闲状态,但在白名单中。 backgroundWhitelistUid。只有具有系统 uid 的应用才能将应用添加到此列表中。