Android系统启动流程(八)——Launcher启动

1 概述

Launcher是Android的桌面应用,也是一个系统初始化完成之后自动拉起的应用。
Launcher会在SystemServer进程初始化完成之后,在ActivityManagerService的systemReady方法中开始执行拉起流程。
Launcher保存着所用应用的缩略图,还有应用的信息,通过桌面点击应用的缩略图就可以启动应用。

2 启动流程

frameworks/base/services/java/com/android/server/SystemServer.java

mActivityManagerService.systemReady(() -> {
    ...
}

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) {
	...
	if (bootingSystemUser) {
    t.traceBegin("startHomeOnAllDisplays");
        mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
        t.traceEnd();
    }
    ...
}

frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

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

frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

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

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 Task rootTask = getTopDisplayFocusedRootTask();
        displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY;
    }

    final DisplayContent display = getDisplayContent(displayId);
    return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
                    result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
                            allowInstrumenting, fromHomeKey),
            false /* initValue */);
}

boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
        boolean allowInstrumenting, boolean fromHomeKey) {
    // Fallback to top focused display area if the provided one is invalid.
    if (taskDisplayArea == null) {
        final Task rootTask = getTopDisplayFocusedRootTask();
        taskDisplayArea = rootTask != null ? rootTask.getDisplayArea()
                : getDefaultTaskDisplayArea();
    }

    Intent homeIntent = null;
    ActivityInfo aInfo = null;
    if (taskDisplayArea == getDefaultTaskDisplayArea()) {
        // xy:获取Intent
        homeIntent = mService.getHomeIntent();
        // xy:通过Intent查找合适的Activity并返回ActivityInfo
        aInfo = resolveHomeActivity(userId, homeIntent);
    } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
        Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
        aInfo = info.first;
        homeIntent = info.second;
    }
    if (aInfo == null || homeIntent == null) {
        return false;
    }

    if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea, allowInstrumenting)) {
        return false;
    }

    // Updates the home component of the intent.
    // xy:设置Intent
    homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
    homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
    // Updates the extra information of the intent.
    if (fromHomeKey) {
        homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
        if (mWindowManager.getRecentsAnimationController() != null) {
            mWindowManager.getRecentsAnimationController().cancelAnimationForHomeStart();
        }
    }
    homeIntent.putExtra(WindowManagerPolicy.EXTRA_START_REASON, reason);

    // Update the reason for ANR debugging to verify if the user activity is the one that
    // actually launched.
    final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
            aInfo.applicationInfo.uid) + ":" + taskDisplayArea.getDisplayId();
    // xy:启动Activity
    mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
            taskDisplayArea);
    return true;
}

这里会构造Intent去在PMS中查询合适的Activity,并返回ActivityInfo,然后通过ActivityInfo里的参数重新设置Intent,通过Intent和ActivityInfo来调用startHomeActivity启动Activity
首先看一下Intent的构建
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

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

Intent中,Action为mTopComponent,其值是Intent.ACTION_MAIN,category为Intent.CATEGORY_HOME。通过这两个设置就能够定位到Launcher的Activity

<activity
    android:name="com.android.launcher3.Launcher"
    android:launchMode="singleTask"
    android:clearTaskOnLaunch="true"
    android:stateNotNeeded="true"
    android:windowSoftInputMode="adjustPan"
    android:screenOrientation="unspecified"
    android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
    android:resizeableActivity="true"
    android:resumeWhilePausing="true"
    android:taskAffinity=""
    android:exported="true"
    android:enabled="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <action android:name="android.intent.action.SHOW_WORK_APPS" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.MONKEY"/>
        <category android:name="android.intent.category.LAUNCHER_APP" />
    </intent-filter>
    <meta-data
        android:name="com.android.launcher3.grid.control"
        android:value="${packageName}.grid_control" />
</activity>

如上面Launcher这个Activity的<intent-filter>中所示
然后看一下怎么查询的

ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) {
    final int flags = ActivityManagerService.STOCK_PM_FLAGS;
    final ComponentName comp = homeIntent.getComponent();
    ActivityInfo aInfo = null;
    try {
        if (comp != null) {
            // Factory test.
            aInfo = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
        } else {
            final String resolvedType =
                    homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
            final ResolveInfo info = AppGlobals.getPackageManager()
                    .resolveIntent(homeIntent, resolvedType, flags, userId);
            if (info != null) {
                aInfo = info.activityInfo;
            }
        }
    } catch (RemoteException e) {
        // ignore
    }

    if (aInfo == null) {
        Slog.wtf(TAG, "No home screen found for " + homeIntent, new Throwable());
        return null;
    }

    aInfo = new ActivityInfo(aInfo);
    aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId);
    return aInfo;
}

这里都是通过PackageManager然后调用到PMS中去获取的
frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java

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

    // xy:获取ActivityStarter,并调用execute方法
    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对象,并调用一些set方法设置属性,然后调用execute方法
frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java

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

private int executeRequest(Request request) {
	...
	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();
    ...
    mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
        request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,
        inTask, inTaskFragment, restrictedBgActivity, intentGrants);
    ...
}

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, Task inTask,
            TaskFragment inTaskFragment, boolean restrictedBgActivity,
            NeededUriGrants intentGrants) {
    ...
    try {
        mService.deferWindowLayout();
        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
        result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
                intentGrants);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
        startedActivityRootTask = handleStartResult(r, options, result, newTransition,
                remoteTransition);
        mService.continueWindowLayout();
    }
    postStartActivityProcessing(r, result, startedActivityRootTask);

    return result;
}

@VisibleForTesting
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        int startFlags, boolean doResume, ActivityOptions options, Task inTask,
        TaskFragment inTaskFragment, boolean restrictedBgActivity,
        NeededUriGrants intentGrants) {
    ...
    if (mDoResume) {
        final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked();
        if (!mTargetRootTask.isTopActivityFocusable()
                || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
                && 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.
            // Passing {@code null} as the start parameter ensures all activities are made
            // visible.
            mTargetRootTask.ensureActivitiesVisible(null /* starting */,
                    0 /* configChanges */, !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.
            mTargetRootTask.mDisplayContent.executeAppTransition();
        } else {
            // If the target root-task was not previously focusable (previous top running
            // activity on that root-task was not visible) then any prior calls to move the
            // root-task to the will not update the focused root-task.  If starting the new
            // activity now allows the task root-task to be focusable, then ensure that we
            // now update the focused root-task accordingly.
            if (mTargetRootTask.isTopActivityFocusable()
                    && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
                mTargetRootTask.moveToFront("startActivityInner");
            }
            mRootWindowContainer.resumeFocusedTasksTopActivities(
                    mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
        }
    }
    ...
}

这里面会构造Activity的ActivityRecord对象,并调用startActivityUnchecked,然后调用startActivityInner,然后调用resumeFocusedTasksTopActivities方法
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

boolean resumeFocusedTasksTopActivities(
            Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
            boolean deferPause) {
    ...
    for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
        final DisplayContent display = getChildAt(displayNdx);
        final boolean curResult = result;
        boolean[] resumedOnDisplay = new boolean[1];
        display.forAllRootTasks(rootTask -> {
            final ActivityRecord topRunningActivity = rootTask.topRunningActivity();
            if (!rootTask.isFocusableAndVisible() || topRunningActivity == null) {
                return;
            }
            if (rootTask == targetRootTask) {
                // Simply update the result for targetRootTask because the targetRootTask
                // had already resumed in above. We don't want to resume it again,
                // especially in some cases, it would cause a second launch failure
                // if app process was dead.
                resumedOnDisplay[0] |= curResult;
                return;
            }
            if (rootTask.getDisplayArea().isTopRootTask(rootTask)
                    && topRunningActivity.isState(RESUMED)) {
                // Kick off any lingering app transitions form the MoveTaskToFront
                // operation, but only consider the top task and root-task on that
                // display.
                rootTask.executeAppTransition(targetOptions);
            } else {
                resumedOnDisplay[0] |= topRunningActivity.makeActiveIfNeeded(target);
            }
        });
        result |= resumedOnDisplay[0];
        if (!resumedOnDisplay[0]) {
            // In cases when there are no valid activities (e.g. device just booted or launcher
            // crashed) it's possible that nothing was resumed on a display. Requesting resume
            // of top activity in focused root task explicitly will make sure that at least home
            // activity is started and resumed, and no recursion occurs.
            final Task focusedRoot = display.getFocusedRootTask();
            if (focusedRoot != null) {
                result |= focusedRoot.resumeTopActivityUncheckedLocked(target, targetOptions);
            } else if (targetRootTask == null) {
                result |= resumeHomeActivity(null /* prev */, "no-focusable-task",
                        display.getDefaultTaskDisplayArea());
            }
        }
    }

    return result;
}

调用到resumeTopActivityUncheckedLocked
frameworks/base/services/core/java/com/android/server/wm/Task.java

boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
    if (mInResumeTopActivity) {
        // Don't even start recursing.
        return false;
    }

    boolean someActivityResumed = false;
    try {
        // Protect against recursion.
        mInResumeTopActivity = true;

        if (isLeafTask()) {
            if (isFocusableAndVisible()) {
                someActivityResumed = resumeTopActivityInnerLocked(prev, options, deferPause);
            }
        } else {
            int idx = mChildren.size() - 1;
            while (idx >= 0) {
                final Task child = (Task) getChildAt(idx--);
                if (!child.isTopActivityFocusable()) {
                    continue;
                }
                if (child.getVisibility(null /* starting */)
                        != TASK_FRAGMENT_VISIBILITY_VISIBLE) {
                    break;
                }

                someActivityResumed |= child.resumeTopActivityUncheckedLocked(prev, options,
                        deferPause);
                // Doing so in order to prevent IndexOOB since hierarchy might changes while
                // resuming activities, for example dismissing split-screen while starting
                // non-resizeable activity.
                if (idx >= mChildren.size()) {
                    idx = mChildren.size() - 1;
                }
            }
        }

        // When resuming the top activity, it may be necessary to pause the top activity (for
        // example, returning to the lock screen. We suppress the normal pause logic in
        // {@link #resumeTopActivityUncheckedLocked}, since the top activity is resumed at the
        // end. We call the {@link ActivityTaskSupervisor#checkReadyForSleepLocked} again here
        // to ensure any necessary pause logic occurs. In the case where the Activity will be
        // shown regardless of the lock screen, the call to
        // {@link ActivityTaskSupervisor#checkReadyForSleepLocked} is skipped.
        final ActivityRecord next = topRunningActivity(true /* focusableOnly */);
        if (next == null || !next.canTurnScreenOn()) {
            checkReadyForSleep();
        }
    } finally {
        mInResumeTopActivity = false;
    }

    return someActivityResumed;
}

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
    if (!mAtmService.isBooting() && !mAtmService.isBooted()) {
        // Not ready yet!
        return false;
    }

    final ActivityRecord topActivity = topRunningActivity(true /* focusableOnly */);
    if (topActivity == null) {
        // There are no activities left in this task, let's look somewhere else.
        return resumeNextFocusableActivityWhenRootTaskIsEmpty(prev, options);
    }

    final boolean[] resumed = new boolean[1];
    final TaskFragment topFragment = topActivity.getTaskFragment();
    resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause);
    forAllLeafTaskFragments(f -> {
        if (topFragment == f) {
            return;
        }
        if (!f.canBeResumed(null /* starting */)) {
            return;
        }
        resumed[0] |= f.resumeTopActivity(prev, options, deferPause);
    }, true);
    return resumed[0];
}
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
    ...
    if (next.attachedToProcess()) {
    	...
    } else {
        // Whoops, need to restart this activity!
        if (!next.hasBeenLaunched) {
            next.hasBeenLaunched = true;
        } else {
            if (SHOW_APP_STARTING_PREVIEW) {
                next.showStartingWindow(false /* taskSwich */);
            }
            if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
        }
        ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);
        mTaskSupervisor.startSpecificActivity(next, true, true);
    }
    ...
}

这里由于进程还没有创建,所以会走else流程,然后调用startSpecificActivity方法
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java

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()) {
        try {
            realStartActivityLocked(r, wpc, andResume, checkConfig);
            return;
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception when starting activity "
                    + r.intent.getComponent().flattenToShortString(), e);
        }

        // If a dead object exception was thrown -- fall through to
        // restart the application.
        knownToBeDead = true;
        // Remove the process record so it won't be considered as alive.
        mService.mProcessNames.remove(wpc.mName, wpc.mUid);
        mService.mProcessMap.remove(wpc.getPid());
    }

    r.notifyUnknownVisibilityLaunchedForKeyguardTransition();

    final boolean isTop = andResume && r.isTopRunningActivity();
    mService.startProcessAsync(r, knownToBeDead, isTop,
            isTop ? HostingRecord.HOSTING_TYPE_TOP_ACTIVITY
                    : HostingRecord.HOSTING_TYPE_ACTIVITY);
}

这里面会判断进程是否启动,如果进程已经启动,则调用realStartActivityLocked方法执行Activity的启动,如果进程没有启动,则调用startProcessAsync通知zygote创建进程。

3 总结

  • Launcher在SystemServer进程初始化完成之后,通过AMS的systemReady方法启动
  • 首先会找到一个可显示的设备,然后在其显示区域中显示
  • 构造一个Intent,Action为ACTION_MAIN,category为CATEGORY_HOME,然后在PMS中查询对应的Intent,然后返回一个ActivityInfo
  • 根据Intent和ActivityInfo来启动Activity,最终调用到startSpecificActivity
  • 这里面会判断进程是否启动,如果进程没有启动,则通知zygote去fork应用进程,如果启动了,则调用realStartActivityLocked进行Activity的启动流程
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android系统启动流程可以分为以下几个步骤: 1. 启动电源以及系统启动:当电源按下时,引导芯片代码开始执行,加载引导程序Bootloader到RAM,并执行该程序。 2. 引导程序Bootloader:引导程序是一个小程序,其主要作用是将系统OS启动起来并运行。 3. Linux内核启动:内核启动时,进行系统设置,包括设置缓存、被保护存储器、计划列表,并加载驱动。内核会在系统文件中寻找"init"文件,并启动init进程或系统的第一个进程。 4. init进程启动:init进程是Android系统的第一个用户空间进程,它负责启动和管理其他系统进程。init进程会读取init.rc文件,根据其中的配置启动系统服务和应用进程。 5. Zygote进程和SystemServer进程启动:Zygote进程是一个特殊的进程,它作为应用进程的模板,用于快速创建新的应用进程。SystemServer进程是Android系统的核心服务进程,负责启动和管理各种系统服务。 6. 应用层进程启动:在Android系统中,应用层进程包括Launcher进程,即主屏幕的进程,以及其他应用程序的进程。这些进程会根据用户的操作和应用的需求来启动和管理。 总结起来,Android系统启动流程包括引导程序Bootloader的加载和执行、Linux内核的启动、init进程的启动、Zygote进程和SystemServer进程的启动,以及应用层进程的启动。这些步骤共同完成了Android系统的初始化和启动。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [Android系统启动流程(一)解析init进程启动过程](https://blog.csdn.net/itachi85/article/details/54783506)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Android系统启动流程](https://blog.csdn.net/xhmj12/article/details/128149490)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android 面试必备 - 系统、App、Activity 启动过程“一锅端”](https://blog.csdn.net/zzz777qqq/article/details/115698795)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值