Activity 启动流程(一)—— Launcher 阶段

980 阅读6分钟

0. 前言

Activity 启动过程文章系列会按阶段拆开分析。

启动流程梳理:

  1. Activity 启动流程(一)—— Launcher 阶段
  2. Activity 启动流程(二)—— AMS 处理阶段
  3. Activity 启动流程(三)—— 应用程序进程启动阶段
  4. Activity 启动流程(四)—— ActivityThread 初始化阶段
  5. Activity 启动流程(五)—— Activity 启动阶段
  6. Activity 启动流程(六)—— Activity 窗口显示

代码基于 android-14.0.0_r9

Activity 启动大体分两类:

  1. 普通 Activity 启动。
  2. 根 Activity 启动,也就是应用第一个 Activity 的启动。

本系列讲的是第二类,即用户点击 Launcher 图标后,系统如何把一次 UI 点击逐步转换成一次正式的 Activity 启动请求。

本文只聚焦 Launcher 阶段

1. 一句话总览

Launcher 阶段做的事情其实很集中:接收点击事件,识别被点击对象,准备启动 Intent 和启动参数,然后把请求从 Launcher 进程交给系统侧的 ActivityTaskManager。

主链路如下:

图标点击 → ItemClickHandler.onClick()startAppShortcutOrInfoActivity()Launcher.startActivitySafely()Activity.startActivity()Activity.startActivityForResult()Instrumentation.execStartActivity()ActivityTaskManager.getService().startActivity(...)

这条链路里,其实就是应用进程 Launcher 通过 startActivity 调用至 ATMS。

2. Launcher 阶段源码分析

2.1 点击入口:Launcher 先把一次点击收口到统一分发点

Launcher 本身就是一个 Activity。桌面图标被点击后,并不是每个图标自己处理启动逻辑,而是统一交给 ItemClickHandler 分发。

// packages/apps/Launcher3/src/com/android/launcher3/touch/ItemClickHandler.java
public static final OnClickListener INSTANCE = ItemClickHandler::onClick;

private static void onClick(View v) {
    Launcher launcher = Launcher.getLauncher(v.getContext());
    Object tag = v.getTag();
    if (tag instanceof WorkspaceItemInfo) {
        onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
    } else if (tag instanceof FolderInfo) {
        ...
    } else if (tag instanceof AppInfo) {
        startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
    } else if (tag instanceof LauncherAppWidgetInfo) {
        ...
    }
}

ItemClickHandler 的作用不是直接启动 Activity,而是先做一次 类型识别。它通过 View.getTag() 取出绑定在图标上的 ItemInfo,再根据具体类型决定后续动作。

对于应用图标,关键分支是 AppInfo / WorkspaceItemInfo,最终都会收口到 startAppShortcutOrInfoActivity()。这说明 Launcher 在点击层做的第一件事,不是“立刻启动”,而是先判断“用户点的到底是什么对象”。

这种组织方式本质上是:

UI 事件 → 统一入口 → 数据识别 → 策略分发

价值在于三点:入口统一、分支清晰、扩展方便。

点击阅读:Android 架构中的统一分发与策略路由

点击阅读:Android View Tag

2.2 启动意图准备:把“点图标”转换成“启动请求”

当分发确认这是一次应用启动后,Launcher 进入 startAppShortcutOrInfoActivity()

private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
    Intent intent;
    if (item instanceof ItemInfoWithIcon
            && (((ItemInfoWithIcon) item).runtimeStatusFlags
            & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
        ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
        intent = new PackageManagerHelper(launcher)
                .getMarketIntent(appInfo.getTargetComponent().getPackageName());
    } else {
        intent = item.getIntent();
    }
    ...
    launcher.startActivitySafely(v, intent, item);
}

这里有一个很重要的语义切换:点击图标并不一定等价于“启动目标应用主页”。

  • 如果应用还处于安装中,Launcher 不会直接启动它,而是跳到应用市场。
  • 如果应用已可用,才使用 item.getIntent() 继续发起正常启动。

也就是说,这一层负责的不是执行启动,而是先把“用户动作”翻译成“正确的启动意图”。

对于普通应用图标,这个 Intent 通常是 Launcher 模式的主入口 Intent,例如:

public static Intent makeLaunchIntent(ComponentName cn) {
    return new Intent(Intent.ACTION_MAIN)
            .addCategory(Intent.CATEGORY_LAUNCHER)
            .setComponent(cn)
            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}

这里表达的是“从 Launcher 启动应用主入口”的启动语义,而不是任意页面跳转语义。

2.3 本地启动网关:ActivityContext 在真正调用框架前补齐启动条件

拿到 Intent 之后,请求不会直接一路冲到系统服务,而是先经过 ActivityContext.startActivitySafely() 做本地收口:

// packages/apps/Launcher3/src/com/android/launcher3/views/ActivityContext.java
default RunnableList startActivitySafely(
        View v, Intent intent, @Nullable ItemInfo item) {
    ...
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (v != null) {
        intent.setSourceBounds(Utilities.getViewBounds(v));
    }
    ...
    context.startActivity(intent, optsBundle);
    ...
}

这一层可以把它理解成 Launcher 侧的一个 本地策略网关,主要做三件事:

  1. 补齐启动参数,比如 FLAG_ACTIVITY_NEW_TASK
  2. 补齐与动画相关的信息,比如 sourceBounds 和启动 options。
  3. 在真正启动前做必要的本地检查,比如安全模式、快捷方式类型、多用户分支。

其中 FLAG_ACTIVITY_NEW_TASK 很关键。因为从 Launcher 发起应用启动,本质上是从一个外部任务入口拉起目标应用,目标 Activity 通常需要进入新的任务上下文中,这也是桌面启动应用的标准做法。

所以这一阶段的职责不是“决定能不能启动成功”,而是把 Launcher 侧该补的启动上下文补完整,再把请求交给框架层。

2.4 Framework 入口:从 Launcher Activity 进入通用启动链路

context.startActivity(intent, optsBundle) 之后,流程进入 Activity 的通用启动路径:

// frameworks/base/core/java/android/app/Activity.java
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        startActivityForResult(intent, -1);
    }
}

接着继续进入:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        ...
    } else {
        ...
    }
}

这里可以看到一个边界变化:到了 Instrumentation.execStartActivity(),启动请求开始进入跨进程交接前的统一桥接环节。

2.5 进程边界交接:Launcher 请求正式进入 ATMS

真正的跨进程交接发生在 Instrumentation.execStartActivity()

// frameworks/base/core/java/android/app/Instrumentation.java
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ...
    intent.migrateExtraStreamToClipData(who);
    intent.prepareToLeaveProcess(who);
    int result = ActivityTaskManager.getService().startActivity(
            whoThread,
            who.getOpPackageName(), who.getAttributionTag(), intent,
            intent.resolveTypeIfNeeded(who.getContentResolver()), token,
            target != null ? target.mEmbeddedID : null,
            requestCode, 0, null, options);
    notifyStartActivityResult(result, options);
    checkStartActivityResult(result, intent);
    ...
}

这段代码说明 Launcher 阶段的最后一步很明确:

  • 先对 Intent 做跨进程前的准备;
  • 再通过 ActivityTaskManager.getService() 拿到系统服务代理;
  • 最后调用 startActivity(...) 把请求送入系统侧。

ActivityTaskManager.getService() 本身只是拿 Binder 代理:

// frameworks/base/core/java/android/app/ActivityTaskManager.java
public static IActivityTaskManager getService() {
    return IActivityTaskManagerSingleton.get();
}

private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
        new Singleton<IActivityTaskManager>() {
            @Override
            protected IActivityTaskManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                return IActivityTaskManager.Stub.asInterface(b);
            }
        };

这里的重点不是展开 ATMS 内部实现,而是明确 Launcher 阶段到此为止已经完成了职责交接

Launcher 负责把一次点击整理成一份完整的启动请求; ATMS 负责接过这份请求,开始系统侧的启动裁决。

3. Launcher 阶段的关键设计点

3.1 统一入口,不让点击逻辑散落在各处

ItemClickHandler 先统一收口,再按 tag 类型分发。这是一种很典型的事件驱动 + 数据驱动分发方式,避免每种图标各自维护一套点击流程。

3.2 Launcher 只做本地准备,不抢系统裁决职责

startActivitySafely() 做的是本地参数补齐和前置检查,比如 NEW_TASK、动画选项、多用户分支、安全模式检查;真正的启动决策并不在 Launcher,而在系统服务侧。这种分层让 Launcher 负责“准备请求”,让 ATMS 负责“裁决请求”,职责边界很清楚。

4. 总结

Launcher 阶段的核心并不是“把 Activity 启动起来”,而是把一次图标点击转换成一份完整、可跨进程传递的启动请求

Instrumentation.execStartActivity() 调用 ActivityTaskManager.getService().startActivity(...) 为止,Launcher 阶段就结束了。下一阶段开始,系统侧会接手这份请求,进入 ATMS 的启动处理流程。