从Activity的启动流程理解Binder
简述
关于Activity启动流程和Binder的文章很多,大多数是分开来讲的,本文将二者结合起来,着重分析启动流程中跨进程方面的细节,其实,启动流程看似调用繁多,主要是复杂在Activity栈管理等方面,如果将其看作一个整体,整个启动流程就简单很多。在启动流程中,App和AMS的跨进程调用是其中的重点,理解了这个,会加深对Binder和启动流程的理解认知,也能窥到Framework层的冰山一角。另外我也发现,很多文章在讲启动流程的时候,关于ActivityMangagerService进程如何调用App进程的细节都没有讲清楚,这个问题也是我写这篇文章的初衷。阅读本文前建议了解一下AIDL,对Binder,Stub,Proxy等有一些印象。建议读一下这篇文章写给 Android 应用工程师的 Binder 原理剖析
Binder简介
对于Binder,初学的人会对里面的概念比较模糊,因为看起来确实有些绕,我在这儿写几点帮助理解。
- 所谓的“跨进程”能力指的是两个方面:一个是能够作为客户端调用远程服务的能力,一个是能够作为服务端接收客户端进程消息的能力,二者都是跨进程的一部分,分别对应transact和onTransact方法,而这两个方法的实现,分别位于BinderProxy和Binder两个类中,这两个类都在Binder.java这个文件中,读者可以自行阅读。
- BinderProxy具有发送消息的能力,通过transact方法,调用底层binder驱动,服务端的Binder具有接收底层binder驱动传过来的消息的能力,当接收到消息会调用onTransact方法。
- 刚开始看AIDL的时候需要反复记忆理解一下,否则看别的代码容易混淆。这里说几个比较容易记忆的点:一个类继承了Stub类,表示这个类是远程服务端,Stub类有个asInterface的静态方法,这个方法用在拿到binder驱动传过来的BinderProxy对象时,将该对象转化成client端使用的本地代理xxxProxy,客户端用它调用远程service的方法。该代理跟service实现了同样的接口,只不过一个是真实现,一个是假实现,这里假实现指的是它通过Binder驱动调用S端方法,真正做工作的在Service端。简言之,Stub代表service端,Proxy代表service在客户端的代理。
- 以AMS为例
public class ActivityManagerService extends IActivityManager.Stub
AMS继承了Stub类,而Stub类一共实现了三个接口:IActivityManger,IBinder,IInterface,分别对应了三种能力,管理activity、跨进程,以及IInterface的asBinder,前两者好理解,那么这里的asBinder能力是干嘛的呢?这里先卖个关子,等下讲启动流程的时候会说明。
启动流程
有了Binder的基础,我们去看启动流程,AMS和APP跨进程的过程简单说就是C端和S端分别通过二者在对方的代理去互相调用对方方法的过程。我们先从宏观角度思考,为什么要跨进程呢?我自己在客户端new一个Activity不行吗?不可以的,因为Android的安全机制以及为了统一管理Activity(比如activity栈),需要有个大管家去进行所有Activity的管理和控制,而这个管家是运行在一个单独进程的,因此App端如果想发起一个Activity的请求,需要先把“申请”提交给大管家,也就是AMS。AMS处理完这个请求之后,需要再次通过跨进程通知App端,去执行剩下的相应的工作。因此这里的核心就在于两者如何互相调用对方了。
App端如何调用AMS方法
下面看代码:用户启动一个页面时,会依次调用activity的startActivity-->Instrumentation的executestartActivity-->execStartActivitiesAsUser,这几个调用很容易找到,就简单带过,在最后这个方法里,执行了远程调用,即:
int result = ActivityManager.getService()
.startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
token, options, userId);
ActivityManager.getService获取的是什么?看ActivityMangaer.getService()这个代码里面:
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
如果对Binder有所了解,应该很容易知道,这里取得的是AMS在客户端的代理,也就是代码中的最后一行返回的am。因为App要频繁的调用AMS的方法,因此用单例模式缓存在本地了一个AMS的本地代理,从单例的第一次获取可以看到,AMS的Binder是通过ServiceManager.getService()获取到的,那么ServiceMangaer是个什么东西,其实这个就是Android系统统一管理所有远程服务的“大管家”,比如AMS,WMS等系统服务都在这里注册了,客户端想调用任意一个服务,只需要知道名字就可以通过SM获取到相应的Server的Binder。拿到Binder之后便可以通过asInterface静态方法转化成本地代理,从而调用server的方法了。因此第一次获取AMS的Binder的过程实际上是客户端跟ServiceManager的一次跨进程通信。
AMS如何通知App进程
(1)AMS如何获取到App进程的Binder的
从上面的分析知道,App获取AMS的Binder实际上是通过ServiceManager这个大管家间接获取的,那反过来AMS处理完activity的管理任务(栈操作等)之后又如何通知App的呢?
一个App总不可能像AMS那样在ServiceManger中注册吧,而且也没这个必要。那么到底是怎么通知的呢?
答案就是:App跨进程调用AMS的方法时,还顺便把App进程(这个时候App可以看作是服务端了)的Binder作为参数传给了AMS,AMS拿到这个APP的Binder之后,通过asInterface方法转化成在server端可以使用的代理,然后在需要回调App进程的时候通过这个代理来通知客户端。其实跟App端逻辑是一致的,只不过C/S调了一下顺序,C变成了S,S变成了C。下面我们从代码里验证:
我们以6.0之前版本的源码为例,新版本改成事务了,有些源码不容易看到,不如直接看老版本的,便于理解。
首先看APP调用startActivity时是如何把App进程的Binder参数传过去的,刚才说了,startActivity实际上调用的是AMS本地代理的startActivity,而AMS本地代理是ActivityMangerProxy,这里AMP是AIDL自动生成的
class ActivityManagerProxy implements IActivityManager
{
public ActivityManagerProxy(IBinder remote)
{
mRemote = remote;
}
public IBinder asBinder()
{
return mRemote;
}
public int startActivity(IApplicationThread caller, Intent intent,
String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
IBinder resultTo, String resultWho,
int requestCode, boolean onlyIfNeeded,
boolean debug, String profileFile, ParcelFileDescriptor profileFd,
boolean autoStopProfiler) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeTypedArray(grantedUriPermissions, 0);
data.writeInt(grantedMode);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
data.writeInt(onlyIfNeeded ? 1 : 0);
data.writeInt(debug ? 1 : 0);
data.writeString(profileFile);
if (profileFd != null) {
data.writeInt(1);
profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
data.writeInt(0);
}
data.writeInt(autoStopProfiler ? 1 : 0);
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result;
}
startActivity方法的第一个参数caller,这个东西是IApplicationThread,这个IApplicationThread就是AMS去通知App做相应处理的接口,它跟IActivityManger配合组成了App和AMS交互的“协议”。那么这个传过来的IApplicationThread的是谁呢,通过看代码里的调用链:
Instrumentation:
int result = ActivityManager.getService()
.startActivityAsUser(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, resultWho,
requestCode, 0, null, options, user.getIdentifier());
ContextImpl:
mMainThread.getInstrumentation().execStartActivities(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intents, options);
ActivityThread:
public ApplicationThread getApplicationThread()
{
return mAppThread;
}
可以查到,首先是instrumentation类里传入的whoThread,whoThread是ContextImpl传进来的mMainThread.getApplicationThread(),而最后这个是mAppThread,这个东西就是ActivityThread这个类的内部类ApplicationThread,我们看代码:
private class ApplicationThread extends IApplicationThread.Stub {
继承自Stub,因此从AIDL语法看出,是一个服务端,对应的客户端是谁呢?当然是AMS了,所以ApplicationThread这个类就是AMS向App进程发消息时的服务端。
思路回到主线上,上面已经说明了,在客户端调用Binder的时候把ApplicationThread参数传给了AMP的startActivity方法,接下来会执行到:
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
注意这里的caller.asBinder,这个方法就解释了前面遗留的问题,IInterface接口的方法asBinder就是在这个时候用的,即把S端(相对的)转成一个binder,之后binder写入到Parcel里,然后通过transact方法调用底层Binder驱动传给其他进程,这里也要注意,transact方法调用的是mRemote的transact,而mRemote本质上是一个BinderProxy,千万不要理解成Binder了,因为这两个类都实现了IBinder接口,我们看代码的时候很可能会误认为调用的Binder的transact。binderProxy的transact会调用transactNative函数,传给jni层,将之前保存在Parcel里的数据data传给Binder驱动,之后在传给AMS。可以这样理解,对于Binder驱动来说,它可以看成跨进程的一个“传送带”,从A进程传递给B进程,只要你实现了IInterface,就可以放到这个传送带上传送(writeStrongBinder方法)。总结一下就是IInterface接口表明了这个类可以转成一个binder从而在binder驱动中跨进程运输,IBinder接口表明了类具有跨进程的能力,即可以通过调用transact方法“使用”Binder驱动。
(2)获取到了Binder之后
上面的讨论已经知道,AMS其实在App跨进程调用AMS的时候就把ApplicationThread转成Binder传过来了,传过来以后,AMS如果要用,必须得拿到ApplicationThread的代理,怎么拿到的呢?
刚才说了AMS的onTransact方法会监听驱动传过来的对象,我们看onTransact的代码:AMS继承自IActivityManager.Stub,在源码中叫ActivityManagerNative:
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case START_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
可以看到:
IBinder b = data.readStrongBinder();客户端将binder write到Parcel中,服务端从Parcel中读了出来,然后通过asInterface转换成ApplicationThread的代理ApplicationThreadProxy这个类。注意:Binder驱动过来的IBinder不是Binder,而是BinderProxy,但是为什么我们之前传的参数是ApplicationThread,这个类是一个binder,为什么读出来以后变成了BinderProxy了呢?答案就在这个readStrongBinder里,看jni层的源码可以知道,系统在客户端收到(readStrongBinder)IBinder以后,会保存下来,通过Binder驱动传给Service时,会通过之前保存的Binder在底层创建BinderProxy,然后传给上层,其实看framework的源码,BinderProxy没有看到在java层的new方法,原来都在底层创建好了。
有了代理对象后接下来既可以直接用了:
int result = startActivity(app, intent, resolvedType,
grantedUriPermissions, grantedMode, resultTo, resultWho,
requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler);
即进入到AMS对Activity启动管理流程中了,经过复杂的跳转,最后跑到ActivityStackSupervisor这个类的realStartActivityLocked方法中,里面最终会执行到这行代码:
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
r.compat, r.task.voiceInteractor, app.repProcState, r.icicle, r.persistentState,
results, newIntents, !andResume, mService.isNextTransitionForward(),
profilerInfo);
这里的app.thread就是前面ApplicationThread在AMS中的代理,到了这里大家应该理清楚了,接下来通过代理调起App进程的ApplicationThread里的相应方法,即:scheduleLaunchActivity方法,这个方法会发送一个Message给主线程的handler :H,然后在handleMessage里通过类加载器创建出一个Activity对象,并执行onCreate方法.balabala....
最后用图片总结一下:
最后推荐一篇文章,目前发现的讲的binder最详细的,听说你 Binder 机制学的不错,来解决下这几个问题
从Activity的启动流程理解Binder的更多相关文章
- 插件占坑,四大组件动态注册前奏(一) 系统Activity的启动流程
转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52190050 前言:为什么要了解系统Activity,Service,,BroadCa ...
- Activity的启动流程分析
Activity是Android应用程序的四大组件之中的一个,负责管理Android应用程序的用户界面,一般一个应用程序中包括非常多个Activity,他们可能执行在一个进程中.也可能执行在不同的进程 ...
- Android进阶系列之源码分析Activity的启动流程
美女镇楼,辟邪! 源码,是一个程序猿前进路上一个大的而又不得不去翻越障碍,我讨厌源码,看着一大堆.5000多行,要看完得啥时候去了啊.不过做安卓的总有这一天,自从踏上这条不归路,我就认命了.好吧,我慢 ...
- Activity的启动流程
前言:新启动一个activity分为两种情况,第一种是在Launcher的桌面点击icon图标启动一个新的应用,第二种是在应用启动的情况下从OneActivity->TwoActivity 其实 ...
- 《转》深入理解Activity启动流程(二)–Activity启动相关类的类图
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先为大家介绍Act ...
- 《转》深入理解Activity启动流程(一)–Activity启动的概要流程
本文原创作者:Cloud Chou. 原文地址:http://www.cloudchou.com/android/post-788.html Android中启动某个Activity,将先启动Acti ...
- 深入理解Activity启动流程(二)–Activity启动相关类的类图
本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先 ...
- 深入理解Activity启动流程(一)–Activity启动的概要流程
概述 Android中启动某个Activity,将先启动Activity所在的应用.应用启动时会启动一个以应用包名为进程名的进程,该进程有一个主线程,叫ActivityThread,也叫做UI线程. ...
- 《转》深入理解Activity启动流程(四)–Activity Task的调度算法
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...
随机推荐
- 电脑上文件的后缀名被隐藏,把一个文本文件改成.bat时,默认打开的还是文本。
1.打开文件夹,选择组织,点击“文件夹和搜索选项”,如图: 2.选择“查看”,找到“隐藏已知文件类型的扩展名”,不要勾选这一项,如图: 3.点击“确定”或者“应用”
- syslog(),closelog()与openlog()--日志操作函数 (2)
文章出处:http://blog.chinaunix.net/uid-26583794-id-3166083.html 守护进程日志的实现 syslogd守护进程用于解决守护进程的日志记录问题,而日志 ...
- mongodb测试类
public class MongoManager { private static final String MONGO_DBNAME="local"; private stat ...
- 解读tensorflow之rnn【转】
转自:https://blog.csdn.net/mydear_11000/article/details/52414342 from: http://lan2720.github.io/2016/0 ...
- hihoCoder-1097-Prim
这题就是prim的板子题,不过如果用end每次初始化为-1的话,我们就不需要对于每次选中的下一个点进行判断是否选中了,因为每次外层循环第一次进入都是可以的. 然后还很 (i=1:i<=n;i++ ...
- 【线段树】bzoj3585: mex
非常精妙的线段树题 Description 有一个长度为n的数组{a1,a2,...,an}.m次询问,每次询问一个区间内最小没有出现过的自然数. Input 第一行n,m. 第二行为n个数. 从第三 ...
- 【Linux】用户与权限
追加用户组 groupadd 用户组名 追加新用户 useradd -d 指定用户目录 -s 指定用户使用shell -g 指定用户组 -p 指定用户密码 用户名 更改用户 添加用户到其他组 use ...
- centos6 安装windows字体
注意:字体文件必须是TTF或者ttf格式的文件, 1.yum install -y fontconfig mkfontscale2.mkdir -p /usr/share/fonts/windows_ ...
- 蓝牙学习(2)USB Adapter
主要分析一下蓝牙USB Adapter使用USB接口传输HCI包的实现及过程. 参照上面的Bluetooth core system architecture图, 蓝牙USB Adapter作为Blu ...
- python中 “==”和"is"的区别
python中 "=="是相等性比较,比较的是两个对象中的值是否相等,而“is”是一致性比较,比较的是两个对象中的内存地址 a = [1, 2, 3] b = a 此时, a == ...