android12 开机启动桌面Activity 分析

9 阅读7分钟

不用翻墙的源码阅读网站 xrefandroid.com/

调用链 SystemServer.startOtherServices() -> ActivityManagerService.systemReady() -> ActivityTaskManagerService.startHomeOnAllDisplays() -> RootWindowContainer.startHomeOnTaskDisplayArea() -> ActivityStartController.startHomeActivity()

ActivityTaskManagerService.startHomeOnAllDisplays(currentUserId, "systemReady");

public boolean startHomeOnAllDisplays(int userId, String reason) {
    synchronized (mGlobalLock) {
        return mRootWindowContainer.startHomeOnAllDisplays(userId, reason);
    }
}

ActivityStartController.startHomeActivity

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason,
        TaskDisplayArea taskDisplayArea) {
    final ActivityOptions options = ActivityOptions.makeBasic();
    options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
    if (!ActivityRecord.isResolverActivity(aInfo.name)) {
        // The resolver activity shouldn't be put in root home task because when the
        // foreground is standard type activity, the resolver activity should be put on the
        // top of current foreground instead of bring root home task to front.
        options.setLaunchActivityType(ACTIVITY_TYPE_HOME);
    }
    final int displayId = taskDisplayArea.getDisplayId();
    options.setLaunchDisplayId(displayId);
    options.setLaunchTaskDisplayArea(taskDisplayArea.mRemoteToken
            .toWindowContainerToken());

    // The home activity will be started later, defer resuming to avoid unnecessary operations
    // (e.g. start home recursively) when creating root home task.
    mSupervisor.beginDeferResume();
    final Task rootHomeTask;
    try {
        // Make sure root home task exists on display area.
        rootHomeTask = taskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
    } finally {
        mSupervisor.endDeferResume();
    }
    
    //通过ActivityStarter执行启动
    mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
            .setOutActivity(tmpOutRecord)
            .setCallingUid(0)
            .setActivityInfo(aInfo)
            .setActivityOptions(options.toBundle())
            .execute();
    mLastHomeActivityStartRecord = tmpOutRecord[0];
    if (rootHomeTask.mInResumeTopActivity) {
        // If we are in resume section already, home activity will be initialized, but not
        // resumed (to avoid recursive resume) and will stay that way until something pokes it
        // again. We need to schedule another resume.
        mSupervisor.scheduleResumeTopActivities();
    }
}

ActivityStarter.execute() ->ActivityStarter.executeRequest()

private int executeRequest(Request request) {
    if (TextUtils.isEmpty(request.reason)) {
        throw new IllegalArgumentException("Need to specify a reason.");
    }
    mLastStartReason = request.reason;
    mLastStartActivityTimeMs = System.currentTimeMillis();
    mLastStartActivityRecord = null;

    final IApplicationThread caller = request.caller;
    Intent intent = request.intent;
    NeededUriGrants intentGrants = request.intentGrants;
    String resolvedType = request.resolvedType;
    ActivityInfo aInfo = request.activityInfo;
    ResolveInfo rInfo = request.resolveInfo;
    final IVoiceInteractionSession voiceSession = request.voiceSession;
    final IBinder resultTo = request.resultTo;
    String resultWho = request.resultWho;
    int requestCode = request.requestCode;
    int callingPid = request.callingPid;
    int callingUid = request.callingUid;
    String callingPackage = request.callingPackage;
    String callingFeatureId = request.callingFeatureId;
    final int realCallingPid = request.realCallingPid;
    final int realCallingUid = request.realCallingUid;
    final int startFlags = request.startFlags;
    final SafeActivityOptions options = request.activityOptions;
    Task inTask = request.inTask;

    int err = ActivityManager.START_SUCCESS;
    // Pull the optional Ephemeral Installer-only bundle out of the options early.
    final Bundle verificationBundle =
            options != null ? options.popAppVerificationBundle() : null;

    WindowProcessController callerApp = null;
    if (caller != null) {
        callerApp = mService.getProcessController(caller);
        if (callerApp != null) {
            callingPid = callerApp.getPid();
            callingUid = callerApp.mInfo.uid;
        } else {
            Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid
                    + ") when starting: " + intent.toString());
            err = ActivityManager.START_PERMISSION_DENIED;
        }
    }

    final int userId = aInfo != null && aInfo.applicationInfo != null
            ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
    if (err == ActivityManager.START_SUCCESS) {
        Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
                + "} from uid " + callingUid);
    }

    ActivityRecord sourceRecord = null;
    ActivityRecord resultRecord = null;
    if (resultTo != null) {
        sourceRecord = mRootWindowContainer.isInAnyTask(resultTo);
        if (DEBUG_RESULTS) {
            Slog.v(TAG_RESULTS, "Will send result to " + resultTo + " " + sourceRecord);
        }
        if (sourceRecord != null) {
            if (requestCode >= 0 && !sourceRecord.finishing) {
                resultRecord = sourceRecord;
            }
        }
    }

    final int launchFlags = intent.getFlags();
    if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
        // Transfer the result target from the source activity to the new one being started,
        // including any failures.
        if (requestCode >= 0) {
            SafeActivityOptions.abort(options);
            return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
        }
        resultRecord = sourceRecord.resultTo;
        if (resultRecord != null && !resultRecord.isInRootTaskLocked()) {
            resultRecord = null;
        }
        resultWho = sourceRecord.resultWho;
        requestCode = sourceRecord.requestCode;
        sourceRecord.resultTo = null;
        if (resultRecord != null) {
            resultRecord.removeResultsLocked(sourceRecord, resultWho, requestCode);
        }
        if (sourceRecord.launchedFromUid == callingUid) {
            // The new activity is being launched from the same uid as the previous activity
            // in the flow, and asking to forward its result back to the previous.  In this
            // case the activity is serving as a trampoline between the two, so we also want
            // to update its launchedFromPackage to be the same as the previous activity.
            // Note that this is safe, since we know these two packages come from the same
            // uid; the caller could just as well have supplied that same package name itself
            // . This specifially deals with the case of an intent picker/chooser being
            // launched in the app flow to redirect to an activity picked by the user, where
            // we want the final activity to consider it to have been launched by the
            // previous app activity.
            callingPackage = sourceRecord.launchedFromPackage;
            callingFeatureId = sourceRecord.launchedFromFeatureId;
        }
    }

    if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
        // We couldn't find a class that can handle the given Intent.
        // That's the end of that!
        err = ActivityManager.START_INTENT_NOT_RESOLVED;
    }

    if (err == ActivityManager.START_SUCCESS && aInfo == null) {
        // We couldn't find the specific class specified in the Intent.
        // Also the end of the line.
        err = ActivityManager.START_CLASS_NOT_FOUND;
    }

    if (err == ActivityManager.START_SUCCESS && sourceRecord != null
            && sourceRecord.getTask().voiceSession != null) {
        // If this activity is being launched as part of a voice session, we need to ensure
        // that it is safe to do so.  If the upcoming activity will also be part of the voice
        // session, we can only launch it if it has explicitly said it supports the VOICE
        // category, or it is a part of the calling app.
        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0
                && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
            try {
                intent.addCategory(Intent.CATEGORY_VOICE);
                if (!mService.getPackageManager().activitySupportsIntent(
                        intent.getComponent(), intent, resolvedType)) {
                    Slog.w(TAG, "Activity being started in current voice task does not support "
                            + "voice: " + intent);
                    err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
                }
            } catch (RemoteException e) {
                Slog.w(TAG, "Failure checking voice capabilities", e);
                err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
            }
        }
    }

    if (err == ActivityManager.START_SUCCESS && voiceSession != null) {
        // If the caller is starting a new voice session, just make sure the target
        // is actually allowing it to run this way.
        try {
            if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
                    intent, resolvedType)) {
                Slog.w(TAG,
                        "Activity being started in new voice task does not support: " + intent);
                err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Failure checking voice capabilities", e);
            err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
        }
    }

    final Task resultRootTask = resultRecord == null
            ? null : resultRecord.getRootTask();

    if (err != START_SUCCESS) {
        if (resultRecord != null) {
            resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED,
                    null /* data */, null /* dataGrants */);
        }
        SafeActivityOptions.abort(options);
        return err;
    }

    boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
            requestCode, callingPid, callingUid, callingPackage, callingFeatureId,
            request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord,
            resultRootTask);
    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_WINDOW_MANAGER,
                    "shouldAbortBackgroundActivityStart");
            restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                    callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                    request.originatingPendingIntent, request.allowBackgroundActivityStart,
                    intent);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
        }
    }

    // Merge the two options bundles, while realCallerOptions takes precedence.
    ActivityOptions checkedOptions = options != null
            ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
    if (request.allowPendingRemoteAnimationRegistryLookup) {
        checkedOptions = mService.getActivityStartController()
                .getPendingRemoteAnimationRegistry()
                .overrideOptionsIfNeeded(callingPackage, checkedOptions);
    }
    if (mService.mController != null) {
        try {
            // The Intent we give to the watcher has the extra data stripped off, since it
            // can contain private information.
            Intent watchIntent = intent.cloneFilter();
            abort |= !mService.mController.activityStarting(watchIntent,
                    aInfo.applicationInfo.packageName);
        } catch (RemoteException e) {
            mService.mController = null;
        }
    }

    mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
            callingFeatureId);
    if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid,
            callingUid, checkedOptions)) {
        // activity start was intercepted, e.g. because the target user is currently in quiet
        // mode (turn off work) or the target application is suspended
        intent = mInterceptor.mIntent;
        rInfo = mInterceptor.mRInfo;
        aInfo = mInterceptor.mAInfo;
        resolvedType = mInterceptor.mResolvedType;
        inTask = mInterceptor.mInTask;
        callingPid = mInterceptor.mCallingPid;
        callingUid = mInterceptor.mCallingUid;
        checkedOptions = mInterceptor.mActivityOptions;

        // The interception target shouldn't get any permission grants
        // intended for the original destination
        intentGrants = null;
    }

    if (abort) {
        if (resultRecord != null) {
            resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED,
                    null /* data */, null /* dataGrants */);
        }
        // We pretend to the caller that it was really started, but they will just get a
        // cancel result.
        ActivityOptions.abort(checkedOptions);
        return START_ABORTED;
    }

    // If permissions need a review before any of the app components can run, we
    // launch the review activity and pass a pending intent to start the activity
    // we are to launching now after the review is completed.
    if (aInfo != null) {
        if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                aInfo.packageName, userId)) {
            final IIntentSender target = mService.getIntentSenderLocked(
                    ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingFeatureId,
                    callingUid, userId, null, null, 0, new Intent[]{intent},
                    new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
                            | PendingIntent.FLAG_ONE_SHOT, null);

            Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);

            int flags = intent.getFlags();
            flags |= Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;

            /*
             * Prevent reuse of review activity: Each app needs their own review activity. By
             * default activities launched with NEW_TASK or NEW_DOCUMENT try to reuse activities
             * with the same launch parameters (extras are ignored). Hence to avoid possible
             * reuse force a new activity via the MULTIPLE_TASK flag.
             *
             * Activities that are not launched with NEW_TASK or NEW_DOCUMENT are not re-used,
             * hence no need to add the flag in this case.
             */
            if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0) {
                flags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
            }
            newIntent.setFlags(flags);

            newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
            newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
            if (resultRecord != null) {
                newIntent.putExtra(Intent.EXTRA_RESULT_NEEDED, true);
            }
            intent = newIntent;

            // The permissions review target shouldn't get any permission
            // grants intended for the original destination
            intentGrants = null;

            resolvedType = null;
            callingUid = realCallingUid;
            callingPid = realCallingPid;

            rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, 0,
                    computeResolveFilterUid(
                            callingUid, realCallingUid, request.filterCallingUid));
            aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags,
                    null /*profilerInfo*/);

            if (DEBUG_PERMISSIONS_REVIEW) {
                final Task focusedRootTask =
                        mRootWindowContainer.getTopDisplayFocusedRootTask();
                Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
                        true, false) + "} from uid " + callingUid + " on display "
                        + (focusedRootTask == null ? DEFAULT_DISPLAY
                                : focusedRootTask.getDisplayId()));
            }
        }
    }

    // If we have an ephemeral app, abort the process of launching the resolved intent.
    // Instead, launch the ephemeral installer. Once the installer is finished, it
    // starts either the intent we resolved here [on install error] or the ephemeral
    // app [on install success].
    if (rInfo != null && rInfo.auxiliaryInfo != null) {
        intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent,
                callingPackage, callingFeatureId, verificationBundle, resolvedType, userId);
        resolvedType = null;
        callingUid = realCallingUid;
        callingPid = realCallingPid;

        // The ephemeral installer shouldn't get any permission grants
        // intended for the original destination
        intentGrants = null;

        aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
    }
    // TODO (b/187680964) Correcting the caller/pid/uid when start activity from shortcut
    // Pending intent launched from systemui also depends on caller app
    if (callerApp == null && realCallingPid > 0) {
        final WindowProcessController wpc = mService.mProcessMap.getProcess(realCallingPid);
        if (wpc != null) {
            callerApp = wpc;
        }
    }
    final ActivityRecord r = new ActivityRecord.Builder(mService)
            .setCaller(callerApp)
            .setLaunchedFromPid(callingPid)
            .setLaunchedFromUid(callingUid)
            .setLaunchedFromPackage(callingPackage)
            .setLaunchedFromFeature(callingFeatureId)
            .setIntent(intent)
            .setResolvedType(resolvedType)
            .setActivityInfo(aInfo)
            .setConfiguration(mService.getGlobalConfiguration())
            .setResultTo(resultRecord)
            .setResultWho(resultWho)
            .setRequestCode(requestCode)
            .setComponentSpecified(request.componentSpecified)
            .setRootVoiceInteraction(voiceSession != null)
            .setActivityOptions(checkedOptions)
            .setSourceRecord(sourceRecord)
            .build();

    mLastStartActivityRecord = r;

    if (r.appTimeTracker == null && sourceRecord != null) {
        // If the caller didn't specify an explicit time tracker, we want to continue
        // tracking under any it has.
        r.appTimeTracker = sourceRecord.appTimeTracker;
    }

    // Only allow app switching to be resumed if activity is not a restricted background
    // activity and target app is not home process, otherwise any background activity
    // started in background task can stop home button protection mode.
    // As the targeted app is not a home process and we don't need to wait for the 2nd
    // activity to be started to resume app switching, we can just enable app switching
    // directly.
    WindowProcessController homeProcess = mService.mHomeProcess;
    boolean isHomeProcess = homeProcess != null
            && aInfo.applicationInfo.uid == homeProcess.mUid;
    if (!restrictedBgActivity && !isHomeProcess) {
        mService.resumeAppSwitches();
    }

    mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
            request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask,
            restrictedBgActivity, intentGrants);

    if (request.outActivity != null) {
        request.outActivity[0] = mLastStartActivityRecord;
    }

    return mLastStartActivityResult;
}

阶段一:ActivityStarter 的任务栈裁决

ActivityStarter 会执行 executeRequest(...) 方法,根据启动标志(Intent Flag)和 Activity 类型,决定如何安放这个新的 Activity。

对于 Home Activity,系统会执行特殊的 HOME 任务栈处理逻辑:

  1. 查找或复用 Home 任务:在 RootWindowContainer 中,系统会找到我们在 startHomeActivity() 中确保存在的 rootHomeTask(即根 Home 任务)。如果发现该任务栈已经存在一个 Launcher 实例,系统会决定是复用它还是重置它,而不是重复创建。
  2. 清除栈顶干扰:如果当前 Home 任务栈的栈顶有其他的临时 Activity(比如弹窗或第三方应用页面被意外压入),系统会调用 Task.deliverClearTop 或 Task.performClearTask 将其清除,确保桌面位于该任务栈的栈顶
  3. 生成 ActivityRecord:系统为即将启动的桌面 Activity 创建一个 ActivityRecord 对象,该对象记录了桌面 Activity 的所有状态信息,并将其挂载到 rootHomeTask 的任务栈中

阶段二:从 System 进程到 App 进程的生命周期调度

任务栈安置完毕后,系统需要真正地“运行”桌面应用的代码。这一步涉及进程间通信(IPC),由 ActivityTaskManagerService 和 ApplicationThread 协作完成:

  1. 获取或创建应用进程ActivityStarter 会调用 ActivityManagerService.startProcessAsync,检查桌面应用的进程是否存在。如果未启动(如开机首次启动),系统会通过 Zygote 进程孵化(Fork)  出桌面应用的主进程。

  2. 绑定 Application:进程创建后,系统会调用桌面应用的 Application.onCreate(),完成应用上下文和资源的初始化。

  3. 事务调度:系统通过 ClientLifecycleManager 向桌面应用进程发送事务(Transaction)。对于桌面启动,通常是按顺序发送:

    • ON_CREATE 事务(对应 onCreate
    • ON_START 事务(对应 onStart
    • ON_RESUME 事务(对应 onResume
  4. 处理系统窗口:在 onResume 阶段,系统还会调用 Activity.makeVisible(),为后续的窗口显示做最后准备。


阶段三:窗口绘制与最终显示

当桌面应用的 onResume 执行完毕,Activity 进入 RESUMED 状态,但此时用户可能还看不到界面,因为窗口(Window)尚未完成第一次绘制(即"冷启动")

  1. 添加窗口:桌面应用通过 ViewManager.addView(实际上是 WindowManagerGlobal)将根布局(DecorView)添加到 WindowManager 中。这会触发 Session.addToDisplay 的跨进程调用,通知 System UI 进程(或系统服务)创建一个 WindowState
  2. 申请 Surface:系统服务端的 WindowState 会向 SurfaceFlinger 申请一块用于显示的 Surface 缓冲区域。
  3. 测量、布局与绘制:在应用端,DecorView 会触发展开 View 树的 measure -> layout -> draw 流程,将 Launcher 的图标、壁纸等元素绘制到 Canvas 中,并将数据填充到上述的 Surface 缓冲区。
  4. 请求显示(ApplySurface) :绘制完成后,WindowManager 向系统 WindowManagerService 提交一个 setTransaction 请求,告知系统该窗口的 Surface 已经准备就绪,可以显示。
  5. SurfaceFlinger 合成:系统端的 SurfaceFlinger 接收到缓冲区数据后,会将该 Surface 图层与状态栏、导航栏等图层进行合成,并最终输出到屏幕硬件上。此时,用户才能真正看到桌面。