4、Activity 启动流程(上):从startActivity到AMS的IPC调用链路

各位同学,今天我们来啃一块硬骨头——Activity的启动流程。说实话,这块内容我当年刚接触时也觉得很绕,代码调用链又长又深。但别怕,我们把它拆成上下两讲,今天先聚焦前半段:从你调用startActivity()开始,到消息最终抵达AMS(Activity Manager Service)的完整IPC链路。

嗯,说白了,就是搞清楚「你的App进程」和「系统进程(System Server)」之间,到底是怎么搭上话的。

4.1 起点:startActivity() 到底干了什么?

我们平时写代码,启动一个Activity通常就一行:

startActivity(new Intent(this, TargetActivity.class));

但这一行背后,藏着好几层封装。我个人习惯把调用链分成三段来看:

  1. 应用层调用:Activity.startActivity() → Instrumentation.execStartActivity()
  2. 跨进程通信层:Instrumentation → ActivityManagerNative(实际是ActivityManagerProxy)→ Binder驱动
  3. 系统服务层:AMS.onStartActivity() 接收请求

你想想看,你的App跑在自己的进程里,AMS跑在System Server进程里,两个进程怎么通信?靠的就是Binder。这里我提一句,Binder是Android IPC的基石,如果你对Binder不熟,建议先补一下,不然后面看AMS的代码会有点吃力。

4.2 Instrumentation:被忽略的幕后管家

很多人以为startActivity()直接调了AMS,其实中间还夹着一个Instrumentation。这个类平时不显山不露水,但它的作用非常大。

我们来看一下源码(基于Android 13,但核心逻辑没变):

// Activity.java
public void startActivity(Intent intent) {
    startActivityForResult(intent, -1);
}

public void startActivityForResult(Intent intent, int requestCode) {
    // ... 省略参数检查
    Instrumentation.ActivityResult ar =
        mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, this,
            intent, requestCode, options);
}

注意这里传了一个mMainThread.getApplicationThread(),这个参数很关键。它是ApplicationThread对象,是ActivityThread的内部类,本质上是一个Binder服务端。AMS后续要通过它来回调你的App进程。

我曾经在排查一个ANR问题时,发现就是ApplicationThread的Binder调用超时了,导致AMS等不到回调,直接杀了进程。嗯,这个坑后面细说。

4.3 进入Instrumentation.execStartActivity()

好,我们继续往下走。看看Instrumentation里做了什么:

// Instrumentation.java
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    
    // 1. 获取AMS的代理对象
    IActivityManager manager = ActivityManager.getService();
    
    // 2. 调用AMS的startActivity方法
    int result = manager.startActivity(
            whoThread, who.getBasePackageName(), intent,
            intent.resolveTypeIfNeeded(who.getContentResolver()),
            token, target != null ? target.mEmbeddedID : null,
            requestCode, 0, null, options);
    
    // 3. 检查结果
    checkStartActivityResult(result, intent);
    
    return null;
}

这里有个关键点:ActivityManager.getService()返回的是什么?

说白了,它返回的是一个Binder代理对象。这个代理对象封装了与AMS通信的所有细节。你调用manager.startActivity()时,实际上是在向Binder驱动发送一个TRANSACTION码,Binder驱动再把数据传递给System Server进程中的AMS。

核心要点ActivityManager.getService() 返回的是 ActivityManagerProxy 对象,它实现了 IActivityManager 接口。这个接口定义了AMS对外提供的所有服务方法。

4.4 Binder调用链路:从Proxy到Stub

我们来看一下ActivityManagerProxy.startActivity()的实现:

// ActivityManagerProxy.java (已废弃,但逻辑类似)
public int startActivity(IApplicationThread caller, String callingPackage,
        Intent intent, String resolvedType, IBinder resultTo,
        String resultWho, int requestCode, int startFlags,
        ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
    
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    
    // 写入Binder接口描述
    data.writeInterfaceToken(IActivityManager.descriptor);
    // 写入参数
    data.writeStrongBinder(caller != null ? caller.asBinder() : null);
    data.writeString(callingPackage);
    intent.writeToParcel(data, 0);
    // ... 省略其他参数写入
    
    // 通过Binder驱动发送请求
    mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
    
    // 读取返回结果
    int result = reply.readInt();
    reply.recycle();
    data.recycle();
    return result;
}

看到mRemote.transact()这行了吗?这就是真正的跨进程调用。mRemote是BinderProxy对象,它会把数据打包后发给Binder驱动。Binder驱动再找到AMS对应的Binder实体(即ActivityManagerNative的子类),调用它的onTransact()方法。

在AMS端,接收流程是这样的:

// ActivityManagerNative.java (实际是ActivityManagerService继承它)
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
    switch (code) {
        case START_ACTIVITY_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            // 读取参数
            IBinder b = data.readStrongBinder();
            IApplicationThread caller = IApplicationThread.Stub.asInterface(b);
            String callingPackage = data.readString();
            Intent intent = Intent.CREATOR.createFromParcel(data);
            // ... 省略其他参数读取
            
            // 调用真正的AMS方法
            int result = startActivity(caller, callingPackage, intent, ...);
            
            reply.writeNoException();
            reply.writeInt(result);
            return true;
        }
    }
}

个人经验:我在做系统稳定性优化时,经常需要抓Binder调用日志。用adb shell dumpsys binder可以查看当前所有Binder线程的状态。如果发现某个Binder调用卡住了,基本就能定位到是哪个服务出了问题。

4.5 整体调用链路图

为了让你更直观地理解,我画了一张流程图。这张图把从App进程到AMS的完整IPC路径展示出来了:

Activity启动IPC调用链路 App进程 System Server进程 Activity.startActivity() 应用层入口 Instrumentation.execStartActivity() 获取AMS代理,发起调用 ActivityManagerProxy Binder代理,封装transact()调用 Binder驱动 ActivityManagerNative.onTransact() Binder服务端,解析参数 ActivityManagerService.startActivity() 真正的启动逻辑入口 图:从App进程到AMS的IPC调用链路

4.6 关键参数:ApplicationThread 的作用

刚才提到,execStartActivity()里传了一个whoThread参数,它是ApplicationThread对象。这个对象为什么重要?

因为AMS在收到启动请求后,需要做很多事情:检查权限、解析Intent、创建ActivityRecord、处理栈管理……但这些都发生在系统进程。当AMS决定要真正创建Activity时,它需要回调你的App进程来执行Activity.onCreate()等生命周期方法。

这个回调,就是通过ApplicationThread完成的。AMS持有它的Binder引用,可以跨进程调用它的方法。

避坑指南:我曾经遇到一个诡异的问题——App启动时闪退,但没有任何异常日志。后来发现是ApplicationThread的Binder引用在某个时机被释放了,导致AMS回调时找不到目标。这种情况通常发生在进程被异常杀死后重建时,Binder引用没有正确恢复。

4.7 小结:这一段的本质是什么?

好了,我们来总结一下今天的内容。从startActivity()到AMS的IPC调用链路,本质上就是:

  • 你的App进程:通过Binder代理对象,把启动请求打包成Parcel数据,发给Binder驱动。
  • Binder驱动:作为内核模块,负责跨进程数据传输,找到目标服务。
  • System Server进程:AMS的Binder服务端收到数据,解包后调用真正的startActivity()方法。

说白了,这就是一次标准的Binder IPC调用。只不过Android在应用层封装了好几层,让你用起来感觉就像调用本地方法一样简单。

下一讲,我们会深入AMS内部,看看它收到请求后是怎么处理Activity栈、怎么创建ActivityRecord、又是怎么回调App进程的。嗯,那才是真正的重头戏。

个人建议:如果你现在对Binder还不太熟悉,建议先花点时间理解Binder的基本原理。不然看AMS的代码会像看天书。我当年就是Binder没吃透,结果在分析Activity启动流程时卡了整整一周。


公众号:蓝海资料掘金营,微信deep3321