Android 11进程启动分析(二):Launcher启动流程分析

上篇说到,Launcher是在ActivityManagerService#systemReady方法中启动的

public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) {
    ...

    if (bootingSystemUser) {
        mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
    }

    ...
}

这里的mAtmInternal是一个ActivityTaskManagerInternal对象。跟进后发现,startHomeOnAllDisplays是一个抽象方法,其实现类是ActivityTaskManagerService的内部类LocalService。

这里有一个规律,我们在源码里面发现类似xxxInternal的抽象类,实现类往往是xxxService里面的LocalService。

跟进之后我们发现这里面什么也没做,直接调用了mRootWindowContainer#startHomeOnAllDisplays

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

mRootWindowContainer是RootWindowContainer类对象,继续跟进

boolean startHomeOnAllDisplays(int userId, String reason) {
    boolean homeStarted = false;
    for (int i = getChildCount() - 1; i >= 0; i--) {
        final int displayId = getChildAt(i).mDisplayId;
        homeStarted |= startHomeOnDisplay(userId, reason, displayId);
    }
    return homeStarted;
}

这里做了一个遍历,在每个显示器(display)上显示对应的Home界面。继续跟进

boolean startHomeOnDisplay(int userId, String reason, int displayId) {
    return startHomeOnDisplay(userId, reason, displayId, false /* allowInstrumenting */,
            false /* fromHomeKey */);
}

boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
        boolean fromHomeKey) {
    // Fallback to top focused display or default display if the displayId is invalid.
    if (displayId == INVALID_DISPLAY) {
        final ActivityStack stack = getTopDisplayFocusedStack();
        displayId = stack != null ? stack.getDisplayId() : DEFAULT_DISPLAY;
    }

    final DisplayContent display = getDisplayContent(displayId);
    boolean result = false;
    for (int tcNdx = display.getTaskDisplayAreaCount() - 1; tcNdx >= 0; --tcNdx) {
        final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tcNdx);
        result |= startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
                allowInstrumenting, fromHomeKey);
    }
    return result;
}

这里又做了一个遍历,调用startHomeOnTaskDisplayArea启动每个显示区域(DisplayArea)的Home界面。继续跟进

boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
            boolean allowInstrumenting, boolean fromHomeKey) {
    
    ...

    Intent homeIntent = null;
    ActivityInfo aInfo = null;
    if (taskDisplayArea == getDefaultTaskDisplayArea()) {
        homeIntent = mService.getHomeIntent();
        aInfo = resolveHomeActivity(userId, homeIntent);
    } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
        Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
        aInfo = info.first;
        homeIntent = info.second;
    }

    ...

    mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
            taskDisplayArea);
    return true;
}

这里做了一个判断,判断传入的TaskDisplayArea是否是默认的TaskDisplayArea,如果是,mService。getHomeIntent()获取对应的Intent,这就是传统的Launcher的启动意图。

Intent getHomeIntent() {
    Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
    intent.setComponent(mTopComponent);
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
        intent.addCategory(Intent.CATEGORY_HOME);
    }
    return intent;
}

其中mService是ActivityTaskManagerService。
这里主要是获取Launcher的Activity对应的Intent对象以及对应的ActivityInfo,然后调用ActivityStartController#startHomeActivity方法。我们继续跟进

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason,
            TaskDisplayArea taskDisplayArea) {
    
    ...

    mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
            .setOutActivity(tmpOutRecord)
            .setCallingUid(0)
            .setActivityInfo(aInfo)
            .setActivityOptions(options.toBundle())
            .execute();
    
    ...

}

这里主要是进行了一通设置之后,调用了ActivityStarter对象的execute方法

int execute() {
    ...
    res = executeRequest(mRequest);
    ...
}

这里进而调用了ActivityStarter#executeRequest方法

private int executeRequest(Request request) {

    ...

    final ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
            callingPackage, callingFeatureId, intent, resolvedType, aInfo,
            mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode,
            request.componentSpecified, voiceSession != null, mSupervisor, checkedOptions,
            sourceRecord);
    

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

这里进行了一通检查之后,创建了一个ActivityRecord对象,然后调用了startActivityUnchecked方法

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                int startFlags, boolean doResume, ActivityOptions options, Task inTask,
                boolean restrictedBgActivity, NeededUriGrants intentGrants) {
    int result = START_CANCELED;
    final ActivityStack startedActivityStack;
    try {
        result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                startFlags, doResume, options, inTask, restrictedBgActivity, intentGrants);
    } finally {
        ...
    }

    ...

    return result;
}

跟进startActivityInner方法

int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, Task inTask,
            boolean restrictedBgActivity, NeededUriGrants intentGrants) {
    // 这里设置了一些属性,包括mDoResume
    setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor, restrictedBgActivity);
    ...

    mTargetStack.startActivityLocked(mStartActivity, topStack.getTopNonFinishingActivity(),
                newTask, mKeepCurTransition, mOptions);

    if (mDoResume) {
        final ActivityRecord topTaskActivity =
                mStartActivity.getTask().topRunningActivityLocked();
        if (!mTargetStack.isTopActivityFocusable()
                || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
                && mStartActivity != topTaskActivity)) {
            mTargetStack.ensureActivitiesVisible(null /* starting */,
                    0 /* configChanges */, !PRESERVE_WINDOWS);
            mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
        } else {
            if (mTargetStack.isTopActivityFocusable()
                    && !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
                mTargetStack.moveToFront("startActivityInner");
            }
            mRootWindowContainer.resumeFocusedStacksTopActivities(
                    mTargetStack, mStartActivity, mOptions);
        }
    }

    ...
    
}

这里mDoResume是true(见executeRequest)。这里有两个分支:

mTargetStack.ensureActivitiesVisible(null /* starting */,
                        0 /* configChanges */, !PRESERVE_WINDOWS);


mRootWindowContainer.resumeFocusedStacksTopActivities(
                        mTargetStack, mStartActivity, mOptions);

无论哪个分支,最后都是调用了ActivityStackSupervisor#startSpecificActivity

void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) {
    // Is this activity's application already running?
    final WindowProcessController wpc =
            mService.getProcessController(r.processName, r.info.applicationInfo.uid);

    boolean knownToBeDead = false;
    if (wpc != null && wpc.hasThread()) {
        realStartActivityLocked(r, wpc, andResume, checkConfig);
        return;
    }

    ....

    mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
}

这里Launcher进程还没有启动,所以realStartActivityLocked不会被执行,最后调用了mService.startProcessAsync,mService是ActivityTaskManagerService对象,跟进ActivityTaskManagerService#startProcessAsync

void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
            String hostingType) {
    ...
    final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess,
                mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead,
                isTop, hostingType, activity.intent.getComponent());
    mH.sendMessage(m);
    ...
}

这里使用了一个Lambda表达式,可以看出最后调用了ActivityManagerInternal的startProcess方法,ActivityManagerInternal的实现类是ActivityManagerService#LocalService,跟进一下

public void startProcess(String processName, ApplicationInfo info, boolean knownToBeDead,
                boolean isTop, String hostingType, ComponentName hostingName) {
    ...

    startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
                    new HostingRecord(hostingType, hostingName, isTop),
                    ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
                    false /* isolated */, true /* keepIfLarge */);
    
    ...
}

继续跟进

final ProcessRecord startProcessLocked(String processName,
            ApplicationInfo info, boolean knownToBeDead, int intentFlags,
            HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting,
            boolean isolated, boolean keepIfLarge) {
    return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
            hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
            keepIfLarge, null /* ABI override */, null /* entryPoint */,
            null /* entryPointArgs */, null /* crashHandler */);
}

mProcessList是ProcessList对象,跟进ProcessList#startProcessLocked:

final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
            boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
            int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
            boolean keepIfLarge, String abiOverride, String entryPoint, String[] entryPointArgs,
            Runnable crashHandler) {
        long startTime = SystemClock.uptimeMillis();
        ProcessRecord app;
        ...

        final boolean success =
                startProcessLocked(app, hostingRecord, zygotePolicyFlags, abiOverride);
        checkSlow(startTime, "startProcess: done starting proc!");
        return success ? app : null;
    }

跟进

final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            int zygotePolicyFlags, String abiOverride) {
    return startProcessLocked(app, hostingRecord, zygotePolicyFlags,
            false /* disableHiddenApiChecks */, false /* disableTestApiChecks */,
            false /* mountExtStorageFull */, abiOverride);
}

继续跟进。。。

boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
            boolean mountExtStorageFull, String abiOverride) {

    ...

    final String entryPoint = "android.app.ActivityThread";

    return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,
            runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,
            instructionSet, invokeWith, startTime);
}

这里可以看到,Android App进程的入口是“android.app.ActivityThread”。

继续跟进

boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
        int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
        String seInfo, String requiredAbi, String instructionSet, String invokeWith,
        long startTime) {
    ...

    if (mService.mConstants.FLAG_PROCESS_START_ASYNC) {
        if (DEBUG_PROCESSES) Slog.i(TAG_PROCESSES,
                "Posting procStart msg for " + app.toShortString());
        mService.mProcStartHandler.post(() -> handleProcessStart(
                app, entryPoint, gids, runtimeFlags, zygotePolicyFlags, mountExternal,
                requiredAbi, instructionSet, invokeWith, startSeq));
        return true;
    } else {
        ...
        final Process.ProcessStartResult startResult = startProcess(hostingRecord,
                    entryPoint, app,
                    uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,
                    requiredAbi, instructionSet, invokeWith, startTime);
            handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
                    startSeq, false);
        ...
        return app.pid > 0;
    }
}

这里应该是根据一个常量来判断进程是同步启动还是异步启动,我的版本FLAG_PROCESS_START_ASYNC为true。
跟进handleProcessStart方法:

private void handleProcessStart(final ProcessRecord app, final String entryPoint,
            final int[] gids, final int runtimeFlags, int zygotePolicyFlags,
            final int mountExternal, final String requiredAbi, final String instructionSet,
            final String invokeWith, final long startSeq) {
        
    ...
    final Process.ProcessStartResult startResult = startProcess(app.hostingRecord,
            entryPoint, app, app.startUid, gids, runtimeFlags, zygotePolicyFlags,
            mountExternal, app.seInfo, requiredAbi, instructionSet, invokeWith,
            app.startTime);

    synchronized (mService) {
        handleProcessStartedLocked(app, startResult, startSeq);
    }
    ...
}

跟进startProcess:

private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags,
            int mountExternal, String seInfo, String requiredAbi, String instructionSet,
            String invokeWith, long startTime) {
    ...
    if (hostingRecord.usesWebviewZygote()) {
        startResult = startWebView(entryPoint,
                app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                app.info.dataDir, null, app.info.packageName, app.mDisabledCompatChanges,
                new String[]{PROC_START_SEQ_IDENT + app.startSeq});
    } else if (hostingRecord.usesAppZygote()) {
        final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);

        // We can't isolate app data and storage data as parent zygote already did that.
        startResult = appZygote.getProcess().start(entryPoint,
                app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                app.info.dataDir, null, app.info.packageName,
                /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
                app.mDisabledCompatChanges, pkgDataInfoMap, whitelistedAppDataInfoMap,
                false, false,
                new String[]{PROC_START_SEQ_IDENT + app.startSeq});
    } else {
        startResult = Process.start(entryPoint,
                app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
                whitelistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
                new String[]{PROC_START_SEQ_IDENT + app.startSeq});
    }
    ...
}

这里做了一个判断,根据不同的参数调用不同的方法启动进程,这里跟进 Process.start

public static ProcessStartResult start(@NonNull final String processClass,
                                        @Nullable final String niceName,
                                        int uid, int gid, @Nullable int[] gids,
                                        int runtimeFlags,
                                        int mountExternal,
                                        int targetSdkVersion,
                                        @Nullable String seInfo,
                                        @NonNull String abi,
                                        @Nullable String instructionSet,
                                        @Nullable String appDataDir,
                                        @Nullable String invokeWith,
                                        @Nullable String packageName,
                                        int zygotePolicyFlags,
                                        boolean isTopApp,
                                        @Nullable long[] disabledCompatChanges,
                                        @Nullable Map<String, Pair<String, Long>>
                                                pkgDataInfoMap,
                                        @Nullable Map<String, Pair<String, Long>>
                                                whitelistedDataInfoMap,
                                        boolean bindMountAppsData,
                                        boolean bindMountAppStorageDirs,
                                        @Nullable String[] zygoteArgs) {
    return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, invokeWith, packageName,
                zygotePolicyFlags, isTopApp, disabledCompatChanges,
                pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
                bindMountAppStorageDirs, zygoteArgs);
}

ZYGOTE_PROCESS 是一个常量

/**
    * State associated with the zygote process.
    * @hide
    */
public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();

跟进ZygoteProcess#start

public final Process.ProcessStartResult start(@NonNull final String processClass,
                                                  final String niceName,
                                                  int uid, int gid, @Nullable int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  @Nullable String seInfo,
                                                  @NonNull String abi,
                                                  @Nullable String instructionSet,
                                                  @Nullable String appDataDir,
                                                  @Nullable String invokeWith,
                                                  @Nullable String packageName,
                                                  int zygotePolicyFlags,
                                                  boolean isTopApp,
                                                  @Nullable long[] disabledCompatChanges,
                                                  @Nullable Map<String, Pair<String, Long>>
                                                          pkgDataInfoMap,
                                                  @Nullable Map<String, Pair<String, Long>>
                                                          whitelistedDataInfoMap,
                                                  boolean bindMountAppsData,
                                                  boolean bindMountAppStorageDirs,
                                                  @Nullable String[] zygoteArgs) {
    
    if (fetchUsapPoolEnabledPropWithMinInterval()) {
        informZygotesOfUsapPoolStatus();
    }

    try {
        return startViaZygote(processClass, niceName, uid, gid, gids,
                runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
                pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
                bindMountAppStorageDirs, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
        Log.e(LOG_TAG,
                "Starting VM process through Zygote failed");
        throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
    }
}

跟进

private Process.ProcessStartResult startViaZygote(@NonNull final String processClass,
                                                      @Nullable final String niceName,
                                                      final int uid, final int gid,
                                                      @Nullable final int[] gids,
                                                      int runtimeFlags, int mountExternal,
                                                      int targetSdkVersion,
                                                      @Nullable String seInfo,
                                                      @NonNull String abi,
                                                      @Nullable String instructionSet,
                                                      @Nullable String appDataDir,
                                                      @Nullable String invokeWith,
                                                      boolean startChildZygote,
                                                      @Nullable String packageName,
                                                      int zygotePolicyFlags,
                                                      boolean isTopApp,
                                                      @Nullable long[] disabledCompatChanges,
                                                      @Nullable Map<String, Pair<String, Long>>
                                                              pkgDataInfoMap,
                                                      @Nullable Map<String, Pair<String, Long>>
                                                              whitelistedDataInfoMap,
                                                      boolean bindMountAppsData,
                                                      boolean bindMountAppStorageDirs,
                                                      @Nullable String[] extraArgs)
                                                      throws ZygoteStartFailedEx {
    ...

    synchronized(mLock) {
        // The USAP pool can not be used if the application will not use the systems graphics
        // driver.  If that driver is requested use the Zygote application start path.
        return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                            zygotePolicyFlags,
                                            argsForZygote);
    }
}

这里的openZygoteSocketIfNeeded最终调用了ZygoteState#connect方法建立了socket连接

static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress,
                @Nullable LocalSocketAddress usapSocketAddress)
                throws IOException {

    DataInputStream zygoteInputStream;
    BufferedWriter zygoteOutputWriter;
    final LocalSocket zygoteSessionSocket = new LocalSocket();

    if (zygoteSocketAddress == null) {
        throw new IllegalArgumentException("zygoteSocketAddress can't be null");
    }

    try {
        zygoteSessionSocket.connect(zygoteSocketAddress);
        zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream());
        zygoteOutputWriter =
                new BufferedWriter(
                        new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
                        Zygote.SOCKET_BUFFER_SIZE);
    } catch (IOException ex) {
        try {
            zygoteSessionSocket.close();
        } catch (IOException ignore) { }

        throw ex;
    }

    return new ZygoteState(zygoteSocketAddress, usapSocketAddress,
                            zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                            getAbiList(zygoteOutputWriter, zygoteInputStream));
}

继续跟进zygoteSendArgsAndGetResult

private Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, int zygotePolicyFlags, @NonNull ArrayList<String> args)
            throws ZygoteStartFailedEx {
    ...

    if (shouldAttemptUsapLaunch(zygotePolicyFlags, args)) {
        try {
            return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
        } catch (IOException ex) {
            // If there was an IOException using the USAP pool we will log the error and
            // attempt to start the process through the Zygote.
            Log.e(LOG_TAG, "IO Exception while communicating with USAP pool - "
                    + ex.getMessage());
        }
    }

    return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr);
}

这里做了一个shouldAttemptUsapLaunch判断,之前的文章《Android 11 进程启动分析(一)》中推断USAP是进程启动的优化机制,那么这里就是判断是使用USAP启动App进程还是直接fork。

接下来就是sokect的数据流收发了。这里不继续深抠细节,真的有朋友能忍受乏味的代码看到这里的话,自己去稍微看一下源码即可。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android系统启动过程中,会依次启动各个系统服务,其中也包括Launcher服务。LauncherAndroid系统的桌面显示服务,它负责管理并显示设备的主屏幕和应用程序列表。Launcher服务在Android系统的最后阶段启动,即在主界面启动之前。 当设备完成各个系统服务的启动,并且系统进入正常运行状态时,Launcher服务会被启动系统启动时,先启动底层服务如Zygote进程、SystemServer进程等,然后再启动Android应用进程Launcher服务作为最基本的应用程序之一,需要在其他应用程序之前加载,以确保用户可以正常使用设备的主屏幕。 Launcher服务的启动主要通过系统服务管理器来实现。系统服务管理器负责管理Android系统的各项服务,并按照事先定义好的优先级顺序启动服务。在启动Launcher服务时,系统服务管理器会调用相应的启动函数,加载Launcher相关的资源和配置文件,并开始监控用户对桌面的操作。 一旦Launcher服务启动成功,就会显示设备的主屏幕,并加载应用程序列表。通过Launcher服务,用户可以查看和管理设备上已安装的应用程序,并快捷地启动它们。同时,Launcher服务还提供了桌面小部件、壁纸等个性化设置,使用户可以自定义设备的外观和功能。 总而言之,Android系统Launcher服务在启动过程的最后阶段启动,它管理和显示设备的主屏幕和应用程序列表,为用户提供方便的桌面操作和个性化设置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值