0. 前言
Activity 启动过程文章系列会按阶段拆开分析。
启动流程梳理:
- Activity 启动流程(一)—— Launcher 阶段
- Activity 启动流程(二)—— AMS 处理阶段
- Activity 启动流程(三)—— 应用程序进程启动阶段
- Activity 启动流程(四)—— ActivityThread 初始化阶段
- Activity 启动流程(五)—— Activity 启动阶段
- Activity 启动流程(六)—— Activity 窗口显示
代码基于 android-14.0.0_r9。
Activity 启动大体分两类:
- 普通 Activity 启动。
- 根 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 事件 → 统一入口 → 数据识别 → 策略分发
价值在于三点:入口统一、分支清晰、扩展方便。
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 侧的一个 本地策略网关,主要做三件事:
- 补齐启动参数,比如
FLAG_ACTIVITY_NEW_TASK。 - 补齐与动画相关的信息,比如
sourceBounds和启动 options。 - 在真正启动前做必要的本地检查,比如安全模式、快捷方式类型、多用户分支。
其中 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 的启动处理流程。