Android应用进程与系统进程的通信
引言
网上关于Activity的启动流程以及各种Binder IPC进程通信的文章数不胜数,大多都是从应用进程到系统进程,又从系统进程回到应用进程,笔者在看这些文章的时候,其实有时真的会很烦,因为整篇文章都各种方法填充,所以笔者看了一些这样的文章之后,决心写一篇自己看起来舒服点的文章,意在总结和分析一些系统的流程和设计用意
进程通信接口的实现
应用进程需要频繁与系统进程通信,譬如Activity生命周期的各个方法都是需要经过系统进程调度的,只是在应用进程进行回调,这就需要从系统到应用的跨进程调用; 应用进程有需要将当前Activity的状态告诉系统进程,以便系统将Activity驱动到下一个状态,这就需要从应用到系统的跨进程调用。
而在Android中实现进程通信的方式笔者了解最多就是Binder了,而四大组件的启动以及状态的维护,其实都是通过Binder去实现进程通信,而Binder的底层细节以及原理则是不是本篇文章的重点,本篇文章的重点是基于Binder之上的通信流程
- IApplicationThread: 作为系统进程请求应用进程的接口
- IActivityManager: 作为应用进程请求系统进程的接口
应用进程与系统进程的通信模型:
IActivityManager以及IApplicationThread这个Binder类的类族图如下:
白色表示运行在系统进程,灰色表示运行在应用进程
在系统进程一侧:
- 最上层的有IActivityManager的接口定义
- 往下一层,有两个接口的实现,其中一个ActivityManagerNative作为Stub/服务端,其主要职责就是对远程传递过来的数据进行反序列化,另一个ActivityManagerProxy作为服务的代理运行在客户端,其主要职责就是将数据进行序列化,再传递给远程的服务端
- 再下一层,就是接口的具体业务实现AMS了,对Stub做了继承扩展
- ProcessRecord类和ActivityRecord类的对象是运行在系统进程之中,它们都由AMS管理
- AMS通过ActivityRecord来维护Activity运行时的状态信息
在应用进程一侧:
- 最上层的有IApplicationThread的接口定义,
- 往下一层,同样有代理和Stub
-
再下一层,具体业务的实现类是ApplicationThread是ActivityThread的一个内部类,ApplicationThread负责响应系统进程发起的请求,这些请求大部分都是需要调度在应用进程的主线程执行,而ActivityThread是应用进程的主线程,通过Handle往主线程抛消息,ApplicationThread就轻松将具体执行任务的工作转交给了主线程。
- Activity与ActivityThread运行在应用进程,将Activity绑定到AMS中的ActivityRecord就能开始Activity的生命周期
从通信绑定角度看Activity启动过程
Activity的启动过程什么复杂,设计的方法调用以及逻辑很多,但是从不同的视角去看Activity启动过程就可以关注到不一样的细节
当启动一个全新的Activity时,Activity的宿主进程可能还未启动,这时候就需要先启动宿主进程
-
AMS通过ProcessRecord来维护进程运行时的状态信息,需要将应用进程绑定到ProcessRecord才能开始一个Application的构建
-
AMS通过ProcessRecord来维护Activity运行时的状态信息,需要将Activity绑定到AMS中的ActivityRecord能开始Activity的生命周期
Application与ProcessRecord的绑定
从应用进程到系统进程
在ActivityThread创建的时候,会将自己的ApplicationThread绑定到AMS中,调用关系如下所示:
ActivityThread.main()
└── ActivityThread.attach()
└── IActivityManager.attachApplication(mAppThread)
└── Binder.transact()
应用进程作为客户端,通过IAcitivtyManager接口发起了跨进程调用, 跨进程传递的参数mAppThread就是IApplicationThread**的实例, 执行流程从应用进程进入到系统进程:
ActivityManagerService.onTransact()
└── ActivityManagerService.attachApplication(IApplicationThread thread)
AMS作为IActivityManager接口的服务端实现,会响应客户端的请求,最终AMS.attachApplication()函数会被执行, 该函数接收跨进程传递过来的IApplicationThread实例,将其绑定到系统进程。 具体的绑定操作细节此处不表,我们只需要知道AMS中维护了所有进程运行时的信息(ProcessRecord),一旦发生了应用进程的绑定请求, ProcessRecord.thread就被赋值成应用进程的IApplicationThread实例,这样一来,在AMS中就能通过该实例发起向应用进程的调用
AMS对于ApplicationThread的管理
final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
AMS里面用的是Android中的数据结构SparseArray对ProcessRecord进行存储,key是pid,value则是ProcessRecord
从应用进程到系统进程
在AMS.attachApplication()的过程中,会有一些信息要传递给应用进程,以便应用进程的初始化,系统进程会发起如下函数调用:
ActivityManagerService.attachApplication()
└── ActivityManagerService.attachApplicationLocked()
└── IApplicationThread.bindApplication(processName, appInfo ...)
└── Binder.transact()
此时,AMS会反转角色,即系统进程作为客户端,通过IApplicationThread接口向应用进程发起调用。 AMS中维护了ProcessRecord这个数据结构,包含了进程运行时的信息,譬如应用进程的名称processName、解析AndroidManifest.xml得到的数据结构ApplicationInfo等,其中,要传递给应用进程的数据都是Parcelable类型的实例。应用进程响应请求的调用关系如下所示:
ApplicationThread.onTransact()
└── ApplicationThread.bindApplication()
└── ActivityThread.H.handleMessage(BIND_APPLICATION)
└── ActivityThread.handleBindApplication()
└── Application.onCreate()
ApplicationThread作为IApplicationThread接口的服务端实现,运行在应用进程中, 然后ApplicationThread.bindApplication()会被执行,完成一些简单的数据封装(AppBindData)后,通过Handler抛出BIND_APPLICATION消息。这一抛,就抛到了主线程上,ActivityThread.handleBindApplication()会被执行,接着就到了各位观众较为熟悉的Application.onCreate()函数。历经应用进程和系统进程之间的一个来回,总算是创建了一个应用程序
Acitivity与ActivityRecord的绑定
在Activity类中有一个IBinder类型的属性:
private IBinder mToken;
IBinder类型表示这个属性是一个远程对象的引用,取了一个恰如其分的变量名:mToken。 为什么叫Token呢?这个名字源自于IApplicationToken.aidl这个接口, 最终ActivityRecord中的一个内部类Token实现了这个接口:
static class Token extends IApplicationToken.Stub {
final WeakReference<ActivityRecord> weakActivity;
Token(ActivityRecord activity) {
weakActivity = new WeakReference<ActivityRecord>(activity);
}
...
}
Token持有了一个ActivityRecord实例的弱引用。在创建一个ActivityRecord的时候,就会创建了一个Token类型的对象:
ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
ActivityInfo aInfo, Configuration _configuration,
ActivityRecord _resultTo, String _resultWho, int _reqCode,
boolean _componentSpecified, ActivityStackSupervisor supervisor,
ActivityContainer container, Bundle options) {
service = _service
appToken = new Token(this);
...
}
构造一个ActivityRecord时,会将自己(this)传给Token,变量ActivityRecord.appToken存的就是最终创建出来的Token
将Token传递给Activity
在启动一个新的Activity时,AMS会将ActivityRecord的Token传递给应用进程,调用关系如下所示:
ActivityStackSupervisor.realStartActivityLocked(ActivityRecord, ...)
└── IApplicationThread.scheduleLaunchActivity(...token, ...)
// 将ActivityRecord的Token跨进程传递给应用进程
└── Binder.transact()
ActivityStackSupervisor.realStartActivityLocked()表示要启动一个Activity实例,ActivityRecord作为参数。从ActivityRecord中提取出Token对象,作为跨进程调用的参数,通过IApplicationThread.scheduleLaunchActivity()传递到应用进程
应用进程这一侧,会收到启动Activity的跨进程调用,触发以下一系列的函数调用:
ApplicationThread.onTransact()
└── ApplicationThread.scheduleLaunchActivity(...token, ...)
// token将被封装进ActivityClientRecord这个数据结构中
└── ActivityThread.H.handleMessage()
└── ActivityThread.handleLaunchActivity(LAUNCH_ACTIVITY)
└── ActivityThread.performLaunchActivity(ActivityClientRecord, ...)
// 从ActivityRecord取出token
└── Activity.attch(...token, ...)
标准的Binder服务端处理流程,收到AMS传递过来的Token对象,进行一下数据封装(ActivityClientRecord),然后通过Handler抛出一个LAUNCH_ACTIVITY消息。这个消息显然也是抛到了应用进程的主线程去执行,所以ActivityThread.performLaunchActivity()函数会在主线程上执行,该函数从封装的数据结构ActivityClientRecord中取出Token对象,调用Activity.attach()函数,将其绑定到Activity上,如此一来,就建立应用进程的Activity与系统进程中ActivityRecord的关联。
系统进程维护的是ActivityRecord,应用进程维护的是Activity,两者之间的映射关系就是利用Token来维系的。 应用进程的Activity在创建的时候,就被赋予了一个Token,拿着这个Token就能完成后续与系统进程的通信。 在发生Activity切换时,应用进程会将上一个Activity的Token(AMS.startActivity()的输入参数resultTo)传递给系统进程,系统进程会根据这个Token找到ActivityRecord,对其完成调度后,再通知应用进程:Activity状态发生了变化。
参考资料: