简述

关于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的更多相关文章

  1. 插件占坑,四大组件动态注册前奏(一) 系统Activity的启动流程

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52190050 前言:为什么要了解系统Activity,Service,,BroadCa ...

  2. Activity的启动流程分析

    Activity是Android应用程序的四大组件之中的一个,负责管理Android应用程序的用户界面,一般一个应用程序中包括非常多个Activity,他们可能执行在一个进程中.也可能执行在不同的进程 ...

  3. Android进阶系列之源码分析Activity的启动流程

    美女镇楼,辟邪! 源码,是一个程序猿前进路上一个大的而又不得不去翻越障碍,我讨厌源码,看着一大堆.5000多行,要看完得啥时候去了啊.不过做安卓的总有这一天,自从踏上这条不归路,我就认命了.好吧,我慢 ...

  4. Activity的启动流程

    前言:新启动一个activity分为两种情况,第一种是在Launcher的桌面点击icon图标启动一个新的应用,第二种是在应用启动的情况下从OneActivity->TwoActivity 其实 ...

  5. 《转》深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先为大家介绍Act ...

  6. 《转》深入理解Activity启动流程(一)–Activity启动的概要流程

    本文原创作者:Cloud Chou. 原文地址:http://www.cloudchou.com/android/post-788.html Android中启动某个Activity,将先启动Acti ...

  7. 深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先 ...

  8. 深入理解Activity启动流程(一)–Activity启动的概要流程

    概述 Android中启动某个Activity,将先启动Activity所在的应用.应用启动时会启动一个以应用包名为进程名的进程,该进程有一个主线程,叫ActivityThread,也叫做UI线程. ...

  9. 《转》深入理解Activity启动流程(四)–Activity Task的调度算法

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

随机推荐

  1. Ibatis入门基本语法

    1.       Ibatis是开源软件组织Apache推出的一种轻量级的对象关系映射(ORM)框架,和Hibernate.Toplink等在java编程的对象持久化方面深受开发人员欢迎. 对象关系映 ...

  2. java在线聊天项目 swt可视化窗口Design 登录框注册按钮点击改变窗口大小——出现注册面板 实现打开登录框时屏幕居中

    登录框注册按钮点击改变窗口大小——出现注册面板  首先用swt可视化设计登录窗口如下图: 此时窗口高度为578 没点击注册时高度为301(可自己定) 注意:注册用户的Jpanel 的border选择T ...

  3. 随机生成一份试卷,试卷的种类分为单选、多选、判断三种题型。nodejs6.0 mysql

    背景:从数据库中,随机生成一份试卷,试卷的种类分为单选.多选.判断三种题型. 首先我需要生成随机数id(在这之前我需要知道数据库中各个题型的题数,这样我才能设置随机数),并依据生成的随机数id,去查找 ...

  4. JavaScript调试技巧之console.log()详解--2015-08-07

    对于JavaScript程序的调试,相比于alert(),使用console.log()是一种更好的方式,原因在于:alert()函数会阻断 JavaScript程序的执行,从而造成副作用:而cons ...

  5. http常用状态吗以及分别代表什么意思?

    http常用状态码: 状态码 定义 说明 1xx 信息 街道请求继续处理 2xx 成功 成功的收到.理解.接受 3xx 重定向 浏览器需要执行某些特殊处理一完成请求 4xx 客户端错误 请求的语法有问 ...

  6. STL 之 vector的应用

    关于vector vector是C++提供的一个容器,它是一个能够存放任意类型的动态数组,可以随时增加和压缩数据. 使用vector时需要注意以下几点: 1. 如果要表示的向量长度较长(需要为向量内部 ...

  7. 【贪心 思维题】[USACO13MAR]扑克牌型Poker Hands

    看似区间数据结构的一道题 题目描述 Bessie and her friends are playing a unique version of poker involving a deck with ...

  8. MySQL中数组的存储

    1. MySQL中以字符串的形式存储数组 MySQL中无数组类型,通常将数组元素按某个字符分割以字符串形式存储 1.1. 求数组中元素的个数 方法:按指定符号分割字符串,返回分割后的元素个数.方法很简 ...

  9. easyUI之datagrid绑定后端返回数据的两种方式

    先来看一下某一位大佬留下的easyUI的API对datagrid绑定数据的两种方式的介绍. 虽然精简,但是,很具有“师傅领进门,修行靠个人”的精神,先发自内心的赞一个. 但是,很多人和小编一样,第一次 ...

  10. ELK踩过的各种坑 6.4版本

    一.elasticsearch 1.服务正常启动,但不能正常访问 [root@linux-node1 elasticsearch]# systemctl start elasticsearch [ro ...