前言

Android 10 (API 级别 29) 及更高版本对后台应用可启动 Activity 的时间施加限制。这些限制有助于最大限度地减少对用户造成的中断(可以更省电),并且可以让用户更好地控制其屏幕上显示的内容。

准备事项
  • 控制后台启动的关键函数为 ActivityStarter::shouldAbortBackgroundActivityStart
原理分析

根据以往咱们对 AMS 的认识,Activity的权限校验、安全校验等,都是放在 AMS 这一块来统一处理的。所以后台启动限制也不例外。所以我们直接来看 AMS 的 startActivity。

ActivityManagerService.java
 @Override
    public int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        return mActivityTaskManager.startActivity(caller, callingPackage, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions);
    }

这里 AMS将 startActivity 交给了 ActivityTaskManagerService 来处理。

ActivityTaskManagerService.class 
 @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }

 int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,
            boolean validateIncomingUser) {
        enforceNotIsolatedCaller("startActivityAsUser");

        userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
                Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");

        // TODO: Switch to user app stacks here.
        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setMayWait(userId)
                .execute();

    }

ActivityTaskManagerService 将参数准备好,然后交给 ActivityStarter 的 execute 来做真正的启动动作。

ActivityStarter setMayWait(int userId) {
        mRequest.mayWait = true;
        mRequest.userId = userId;
        return this;
}

 int execute() {
        try {
            if (mRequest.mayWait) {
                return startActivityMayWait(mRequest.caller, mRequest.callingUid,
                        mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid,
                        mRequest.intent, mRequest.resolvedType,
                        mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
                        mRequest.resultWho, mRequest.requestCode, mRequest.startFlags,
                        mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig,
                        mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId,
                        mRequest.inTask, mRequest.reason,
                        mRequest.allowPendingRemoteAnimationRegistryLookup,
                        mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);
            } else {
                return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent,
                        mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo,
                        mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
                        mRequest.resultWho, mRequest.requestCode, mRequest.callingPid,
                        mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid,
                        mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions,
                        mRequest.ignoreTargetSecurity, mRequest.componentSpecified,
                        mRequest.outActivity, mRequest.inTask, mRequest.reason,
                        mRequest.allowPendingRemoteAnimationRegistryLookup,
                        mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);
            }
        } finally {
            onExecutionComplete();
        }
    }

上面调用 ActivityStarter 前,会执行 ActivityStarter::setMayWait 方法,这里会将mRequest.mayWait的值设置成 true ,所以 execute 会走 startActivityMayWait 方法。这里我们需要注意变量 mRequest.allowBackgroundActivityStart,这个也是控制是否可以后台启动的一个标志,但我们正常的启动流程,这个变量一直为 false。
startActivityMayWait经过层层调用,会来到 startActivity 方法


  private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
···
  boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity,
                inTask != null, callerApp, resultRecord, resultStack);
        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                callingPid, resolvedType, aInfo.applicationInfo);
        abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
                callingPackage);

        boolean restrictedBgActivity = false;
        if (!abort) {
            try {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "shouldAbortBackgroundActivityStart");
                //1
                restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                        callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                        originatingPendingIntent, allowBackgroundActivityStart, intent);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
        }
···
	//2
	if (abort) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
                        RESULT_CANCELED, null);
            }
            // We pretend to the caller that it was really started, but
            // they will just get a cancel result.
            ActivityOptions.abort(checkedOptions);
            return START_ABORTED;
        }
        ...
        //3
         final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity);
        mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outActivity[0]);
        return res;
}

abort 这个变量来控制当前的启动是否要中止。检查后台状态,需要在不中止的情况下进行检查。在注释2中,如果abort 为true,直接跳出函数,则不会对Activity进行真正的启动。接下来我们分析 注释1 关键函数 shouldAbortBackgroundActivityStart

  boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
            final String callingPackage, int realCallingUid, int realCallingPid,
            WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart, Intent intent) {
            ···
              // don't abort if the callingUid has a visible window or is a persistent system process
        final int callingUidProcState = mService.getUidState(callingUid);
        final boolean callingUidHasAnyVisibleWindow =
                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
        final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
                || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
        final boolean isCallingUidPersistentSystemProcess =
                callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
            return false;
        }
            ···
              // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
            Slog.w(TAG, "Background activity start for " + callingPackage
                    + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return false;
        }
        ···
        return true;
    }

这个方法中,判断了很多种情况,是否应该中止后台启动。如果我们当前属window是显示的,或者系统进程,则直接返回 false。这么多判断中实则我们只需要检验通过其中一个就可以在后台进行启动Activity了。我挑选了作为应用开发可以操控的地方的代码片段。看注释,我们知道,这里对 SYSTEM_ALERT_WINDOW 权限进行了校验。如果当前 app拥有这个权限,我们就返回 false。说明允许用户在后台启动 Activity。
接着上面分析,我们在注释1的方法中拿到返回值,接着调用注释3的函数,继续进行activity的启动。
startActivity 方法,内部会调用 startActivityUnchecked 方法。

 private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity, boolean restrictedBgActivity) {
            //1
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor, restrictedBgActivity);
                ...
                //2
            if (mDoResume) {
            final ActivityRecord topTaskActivity =
                    mStartActivity.getTaskRecord().topRunningActivityLocked();
            if (!mTargetStack.isFocusable()
                    || (topTaskActivity != null && topTaskActivity.mTaskOverlay
                    && mStartActivity != topTaskActivity)) {
                // If the activity is not focusable, we can't resume it, but still would like to
                // make sure it becomes visible as it starts (this will also trigger entry
                // animation). An example of this are PIP activities.
                // Also, we don't want to resume activities in a task that currently has an overlay
                // as the starting activity just needs to be in the visible paused state until the
                // over is removed.
                mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
                // Go ahead and tell window manager to execute app transition for this activity
                // since the app transition will not be triggered through the resume channel.
                mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
            } else {
                // If the target stack was not previously focusable (previous top running activity
                // on that stack was not visible) then any prior calls to move the stack to the
                // will not update the focused stack.  If starting the new activity now allows the
                // task stack to be focusable, then ensure that we now update the focused stack
                // accordingly.
                if (mTargetStack.isFocusable()
                        && !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
                    mTargetStack.moveToFront("startActivityUnchecked");
                }
                mRootActivityContainer.resumeFocusedStacksTopActivities(
                        mTargetStack, mStartActivity, mOptions);
            }
        }
        ...
 }

restrictedBgActivity 变量就是上面 shouldAbortBackgroundActivityStart函数的结果。这里会将 restrictedBgActivity 交给注释1的 setInitialState 方法调用。

  private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
            boolean doResume, int startFlags, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            boolean restrictedBgActivity) {
            ...
           mRestrictedBgActivity = restrictedBgActivity;
           ...
          if (mRestrictedBgActivity && !mService.isBackgroundActivityStartsEnabled()) {
            mAvoidMoveToFront = true;
            mDoResume = false;
        }
 }

其中 mService.isBackgroundActivityStartsEnabled() 中的值是系统配置的值,正常情况下是不允许后台启动的。所以 mService.isBackgroundActivityStartsEnabled() 值默认为true。这个时候会对 mDoResume 进行赋值。这个变量关系到,之后的启动流程是否可以正常进行。
回到 startActivityUnchecked 的注释2中,我们知道 activity的启动完整流程,是需要执行 mRootActivityContainer.resumeFocusedStacksTopActivities()函数的。但是执行这个函数的前提是 mDoResume 为 true。setInitialState中如果后台启动受限,并且当前window为不显示状态,则mDoResume 就为false,所以这里不能执行下去。

总结

如果是系统开发,则有很多地方绕过这里的后台启动校验,但是应用开发中,如果想要在后台进行 startActivity 生效,就需要拥有 SYSTEM_ALERT_WINDOW 权限。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐