1. 前言:你以为的HelloWorld,其实是个“巨婴”
很多刚入行的Android开发同学,打开Android Studio,写下一行 Log.d("Hello", "World"),点击Run,屏幕亮了,日志出来了,心里想:“这就完了?”
别急,这只是冰山露出水面的一角。
当你在手机上点击那个蓝色的图标时,背后发生了一场惊心动魄的接力赛。从用户点击那一刻,到APP进程真正跑起来,中间经历了AMS(Activity Manager Service)的千辛万苦调度。今天,我们不讲枯燥的理论,而是像侦探一样,顺着源码的线索,把这条链路拆得明明白白,并附上我在实际开发中踩过的坑,保证你看完能写出更底层的代码。
2. 第一棒:Launcher是怎么知道要启动谁的?
当我们点击桌面图标,其实并没有直接调用我们的APP。中间还隔着一个Launcher进程。
2.1 源码追踪:启动请求的发出
在Launcher中,核心逻辑在 Launcher.java 的 onClick 或 startActivitySafely 方法里。
// Launcher.java 简化伪代码
@Override
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
ShortcutInfo shortcut = (ShortcutInfo) tag;
// 构建 Intent
Intent intent = shortcutIntent(shortcut);
// 关键:这里并不是直接startActivity,而是通过Activity.startActivity
// 但底层最终会走到 Instrumentation
startActivity(intent, null);
}
}
这里有个大坑:Launcher和我们的APP在不同的进程吗?
- 如果我们的APP是第一次启动,不在。Launcher会先创建APP进程,再发送启动命令。
- 如果我们的APP已经在后台,在。直接通过Binder通信唤醒。
3. 第二棒:Zygote——Android的“母体”
Android中,所有APP进程都是Zygote进程 fork出来的。这就像生物界的受精卵,所有子细胞都来自这里。
3.1 为什么要有Zygote?
为了内存优化。JIT(即时编译器)需要共享,预加载常用类需要共享,甚至Dalvik/ART的虚拟机环境都要共享。如果每个APP都从零创建进程,系统内存会被瞬间吃光。
3.2 源码追踪:ZygoteInit
ZygoteServer.java 中的 runOnce() 方法是关键。
// ZygoteServer.java
private Runnable runOnce() {
// 监听zygote socket
Socket socket = mSocket.accept();
// 解析参数
String[] args = readArgumentList();
String className = args[0];
// 核心:fork新进程
pid = Zygote.forkAndSpecialize(uid, gid, gids, runtimeFlags, ...);
if (pid == 0) {
// 子进程(即我们的APP进程)
handleChildProc(args, ...);
return null;
} else {
// 父进程继续监听
return handleParentProc(pid, ...);
}
}
实际开发坑点:
如果你的APP启动很慢,检查
Zygote预加载列表。preloaded-classes文件里记录了启动时需要预加载的类。有些库如果在里面,启动会快;不在里面,首次加载会卡顿。可以通过 ADB 命令查看当前 Zygote 的内存快照来优化。
4. 第三棒:AMS——真正的“指挥官”
当Zygote fork出新进程后,新进程需要执行什么?答案是:ActivityThread.main()。
但在那之前,AMS(Activity Manager Service) 介入来了。AMS是系统服务中最重要的服务之一,运行在 system_server 进程中。
4.1 AMS如何知道要启动哪个Activity?
回到Launcher,当它调用 startActivity 时,最终会经过 Instrumentation。
// Instrumentation.java
public ActivityResult execStartActivity(...) {
IApplicationThread whoThread = mAppThread;
// 跨进程调用AMS的startActivity方法
int result = ActivityManagerNative.getDefault().startActivity(whoThread, ..., intent);
return null;
}
这里的 ActivityManagerNative.getDefault() 拿到的就是 AMS的代理对象。这是一个Binder调用,跨进程传输。
4.2 AMS内部发生了什么?
ActivityManagerService.java 中的 startActivity 方法极其复杂,我们简化核心流程:
- 解析Intent:确定要启动哪个Activity,以及它的启动模式(SingleTask/SingleTop等)。
- 检查权限:当前进程是否有权限启动这个Activity。
- 查找进程:
- 如果目标进程存在,直接发送消息。
- 如果目标进程不存在,调用
startProcessLocked()通知Zygote孵化新进程。
- 启动进程:AMS向Zygote发送socket请求,Zygote fork出新进程。
- 回调:新进程创建成功后,通过Binder回调AMS,AMS继续驱动Activity的创建流程。
// ActivityManagerService.java
private int startProcessLocked(ProcessRecord app, String hostingType, ...) {
// 构建启动参数
String niceName = app.processName;
// 调用Zygote
ZygoteProcess.Arguments args = new ZygoteProcess.Arguments();
// ... 设置uid, gid, class等
// 核心:fork进程
return mZygoteProcess.fork(app, true, null, null, args);
}
实际开发坑点:
进程名被篡改:有些恶意软件或配置不当的APP,会在
AndroidManifest.xml中指定特殊的android:process。如果你用 ADB 命令ps | grep your_app找不到进程,可能是因为进程名被改成了com.example.yourapp:remote,而不是包名。
5. 第四棒:ActivityThread——新进程的“大管家”
当Zygote fork出新进程后,新进程的入口是 ActivityThread.main()。这才是我们熟悉的世界。
5.1 main方法初始化
// ActivityThread.java
public static void main() {
// 1. 创建Looper
Looper.prepareMainLooper();
// 2. 创建ActivityThread实例
ActivityThread thread = new ActivityThread();
thread.attach(false);
// 3. 启动消息循环
Looper.loop();
}
thread.attach(false) 是连接AMS的关键。它通过Binder将当前的 ApplicationThread(ActivityThread的内部类,负责与AMS通信)注册到AMS中。
// ActivityThread.java
private void attach(boolean system) {
// 获取AMS代理
final IActivityManager mgr = ActivityManager.getService();
// 注册到AMS
mgr.attachApplication(mAppThread);
}
5.2 AMS回调ApplicationThread
AMS注册完进程后,会立即回调 ApplicationThread,开始启动Activity。
// ApplicationThread.java (ActivityThread内部类)
public final void scheduleLaunchActivity(Intent intent, ...) {
// 处理消息,最终调用handleLaunchActivity
sendMessage(H.LAUNCH_ACTIVITY, r);
}
private void handleLaunchActivity(ActivityClientRecord r, ...) {
// 关键:启动Activity
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
// 启动Window
handleResumeActivity(r.token, false, r.isForward, ...);
}
}
6. 第五棒:Activity的诞生
performLaunchActivity 是Activity真正诞生的地方。
// ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 1. 加载类
ClassLoader cl = r.packageInfo.getClassLoader();
Activity activity = null;
activity = (Activity) cl.loadClass(r.activityInfo.name).newInstance();
// 2. 创建ContextImpl
ContextImpl appContext = createBaseContextForActivity(activity, r);
// 3. 调用attach方法,建立联系
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.prev,
r.icicle, r.contentProviders, r.app, r.name, r.referrer, ...);
// 4. 调用onCreate
activity.mCalled = false;
activity.mCalled = true;
activity.onCreate(r.icicle);
return activity;
}
注意:r.activityInfo.name 是从AMS传过来的字符串,通过反射实例化。这就是为什么Activity必须有无参构造器。
7. 实际开发中的经典坑与解决方案
7.1 坑一:ANR(Application Not Responding)
现象:点击图标后,系统弹出“应用无响应”,然后杀死进程。
原因:主线程(Looper)被阻塞。可能是启动过程中执行了耗时操作,或者AMS等待进程启动超时(默认10秒)。
排查:
# 查看ANR trace文件
adb shell cat /data/anr/traces.txt
查看 main 线程的堆栈,看它在等什么。
7.2 坑二:冷启动白屏/黑屏
现象:点击APP,先出现白屏或黑屏,很久才显示内容。
原因:
- Launcher -> AMS -> Zygote -> APP进程创建 -> ActivityThread.main -> Activity.onCreate -> setContentView -> 第一次绘制。
- 这个过程涉及多次IPC(进程间通信),非常耗时。
优化方案:
- 主题优化:设置一个与APP启动页颜色一致的主题,掩盖白屏。
<style name="AppTheme.Launcher" parent="Theme.AppCompat.NoActionBar"> <item name="android:windowBackground">@drawable/splash_bg</item> </style> - 提前加载资源:在
Application的onCreate中预加载常用资源(注意不要做主线程耗时操作)。
7.3 坑三:多进程通信死锁
现象:APP内部使用多进程,某个进程启动后卡死。
原因:AMS在启动进程时,会等待进程通过Binder回调AMS完成初始化。如果进程在 Application.onCreate 或 ActivityThread.main 中执行了耗时操作(如网络请求、大量IO),会导致AMS认为进程挂起,最终触发ANR或直接杀死进程。
解决方案:
- 不要在
Application的onCreate中做耗时操作,除非你确定该进程是主进程。 - 对于子进程,尽量异步化初始化逻辑。
8. 总结:一张图看懂全流程
为了让你更清晰地理解,我把整个流程画成一张逻辑图:
用户点击图标
↓
Launcher (Process A)
↓
Instrumentation.execStartActivity
↓
Binder IPC 调用 AMS (system_server进程)
↓
AMS.startProcessLocked
↓
Zygote (fork新进程)
↓
新进程 (Process B) 启动
↓
ActivityThread.main
↓
Looper.prepareMainLooper
↓
ActivityThread.attach -> AMS.attachApplication
↓
AMS回调 ApplicationThread.scheduleLaunchActivity
↓
ActivityThread.handleLaunchActivity
↓
performLaunchActivity (实例化Activity, 调用attach, onCreate)
↓
handleResumeActivity (启动Window, 显示界面)
↓
用户看到APP
9. 结语:从源码中学会思考
理解AMS启动流程,不仅仅为了面试,更是为了在日常开发中快速定位问题。当你下次遇到启动慢、ANR、进程杀死的奇怪问题时,你可以顺着这条链路,一层层剥开,找到真正的元凶。
记住,Android系统是一个复杂的分布式系统,每个组件都在自己的岗位上默默工作。作为开发者,我们不仅要会用API,更要理解API背后的世界。这样,你才能写出真正健壮、高效的Android应用。
希望这篇文章能帮你打通任督二脉,如果有疑问,欢迎在评论区交流,我们一起探讨!
