android Binder机制(一)架构设计
Binder 架构设计
Binder 被设计出来是解决 Android IPC(进程间通信) 问题的。Binder 将两个进程间交互的理解为 Client 向 Server 进行通信。
如下:binder总体架构图
如上图所示,Binder 架构分为 Client、Server、Service Manager 和 Binder Driver。
- Client: 服务调用者,一般就是我们应用开发者,通过调用诸如
List<PackageInfo> packs = getActivity().getPackageManager().getInstalledPackages(0);
这样的代码,来向 ServerManager 请求 Package 服务。 - Server: 服务提供者,这里面会有许多我们常用的服务,例如 ActivityService 、 WindowMananger, 这些系统服务提供的功能,是的我们能够使用 Wifi,Display等等设备,从而完成我们的需求。
- Service Manager: 这里是类似于前文中的DNS,绝大多数的服务都是通过 Service Manager来获取,通过这个 DNS 来屏蔽掉 对其他Server的直接操作。
- Binder Driver: 底层的支持逻辑,在这里承担路由的工作,不论风雨,使命必达,即使对面的server挂掉了,也会给你相应的死亡通知单 (Death Notification)
总结起来说,应用程序(Client) 首先向 Service Manager 发送请求 WindowManager 的服务,Service Manager 查看已经注册在里面的服务的列表,找到相应的服务后,通过 Binder kernel 将其中的 Binder 对象返回给客户端,从而完成对服务的请求。
Binder Driver 是怎样充当路由角色的?
对于有网络编程经验的人来说,Socket 是很常用的概念。在Linux系统中,一切都被认为是文件,网络流也是文件,同样 Socket 也是文件,遵循着 open - write / read - close
的模式,Binder Framework在设计的时候,也同样设计了类似的概念。
而在 Binder Framework 中 Binder 充当了 Socket 的角色,在不同的进程里面穿梭,提供了通信的基础。对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点, Client通过这个『地址』向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的管道入口,要想和某个Server通信首先必须建立这个管道并获得管道入口。我们知道如果要访问一个对象的话,需要拿到这个对象的引用地址,我们可以这么认为 Binder 就是远程对象的一个地址,通过这个 Binder 就能轻松地拿到远程对象的控制权,也可以说 Binder 是句柄,可能符合现在的场景。
而让 Binder 起到上诉神奇作用的就是 Binder Driver。Binder Driver 在这里的作用就是前面提及的路由器,它工作在内核态,通过一系列 open()
, mmap()
, ioctl()
, poll()
等操作,指定了一系列的协议,实现了 Binder 在不同进程之间的传递工作,这里就不再详细阐述了,有兴趣的同学可以自行查看相关文档。
Service Manager 怎么当DNS的?
根据前文的描述,Service Manager是将相应的服务名字转换成具体的引用,也就是说使得 client 能够通过 bidner 名字来从 Server 中拿到对 binder 实体的引用。这里唯一需要特别说明的地方在于,Service Manager 的特殊性。我们知道 Service Manager 是一个进程,其他 Server 也是另一个进程,他们之间是如何进行通信的了?在没有其他中间服务进程的参与下,Service Manager 与 其他进程如何凭空通信?
这就是先有鸡,还是先有蛋的问题。答案是先有鸡,也就是说 Service Manager 首先就被创建了,并被赋予了一个特殊的句柄,这个句柄就是 0 。换而言之,其他 Server 进程都可以通过这个 0句柄
与 Service Manager 进行通信,在整个系统启动时,其他 Server 进程都向这个 0句柄
进行注册,从而使得客户端进程在需要调用服务时,能够通过这个 Service Manager 查询到相应的服务进程。
如下:binder framework 工作原理图
理解Aidl中Stub和Stub.Proxy
aidl生成的java代码中,Stub类是继承于Binder类的,也就是说Stub实例就是Binder实例。
服务端一般会实例化一个Binder对象,例如:
public class AIDLService extends Service { private static final String TAG = "AIDLService"; IPerson.Stub stub = new IPerson.Stub() { @Override public String greet(String someone) throws RemoteException { Log.i(TAG, "greet() called"); return "hello, " + someone; } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind() called"); return stub; } ... }
客户端中在Service绑定的时候可以获取到这个Stub(Binder),如:
private IPerson person; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("ServiceConnection", "onServiceConnected() called"); person = IPerson.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { //This is called when the connection with the service has been unexpectedly disconnected, //that is, its process crashed. Because it is running in our same process, we should never see this happen. Log.i("ServiceConnection", "onServiceDisconnected() called"); } };
像上面一样,在连接Service的时候,服务端的Stub(Binder)以参数的形式传过来了–IBinder service,然后我们通过asInterface()方法获取它的实例对象。
aidl文件自动生成的java类中可以看到asInterface()这个接口的实现,大概的意思就是:
如果客户端和服务端在同一个进程下,那么asInterface()将返回Stub对象本身,否则返回Stub.Proxy对象。
也就是说asInterface()返回的对象有两种可能(实际上有三种,还有一种是null),Stub和Stub.Proxy。它们有什么区别呢?
如果在同一个进程下的话,那么asInterface()将返回服务端的Stub对象本身,因为此时根本不需要跨进称通信,那么直接调用Stub对象的接口就可以了,返回的实现就是服务端的Stub实现,也就是根本没有跨进程通信;
如果不是同一个进程,那么asInterface()返回是Stub.Proxy对象,该对象持有着远程的Binder引用,因为现在需要跨进程通信,所以如果调用Stub.Proxy的接口的话,那么它们都将是IPC调用,它会通过调用transact方法去与服务端通信。
以上就是两者的区别。
Stub是服务端实现的存根,而Proxy则是Stub的代理。
public interface IPerson extends android.os.IInterface { public static abstract class Stub extends android.os.Binder implements IPerson { public Stub(){this.attachInterface(this, DESCRIPTOR);}...public static IPerson asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } //mDescriptor的初始化在attachInterface()过程中赋值android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR); //查询本地 if (((iin != null) && (iin instanceof IPerson))) { return ((IPerson) iin); } return new IPerson.Stub.Proxy(obj); } ... @Overridepublic boolean onTransact(int code, android.os.Parcel data,android.os.Parcel reply, int flags)throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_greet: {data.enforceInterface(DESCRIPTOR);java.lang.String _arg0;_arg0 = data.readString();java.lang.String _result = this.greet(_arg0);reply.writeNoException();reply.writeString(_result);return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements IPerson {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic java.lang.String greet(java.lang.String someone)throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(someone);mRemote.transact(Stub.TRANSACTION_greet, _data, _reply, 0);_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}}static final int TRANSACTION_greet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); //方法用数字表示 } }
Binder机制框架概览
如何从进程A传两个整数给进程B,进程B把两个数相加后返回结果给进程A。
下面我们从总体上看一看这个方案是怎样设计的:
进程A通过bindService
方法去绑定在进程B中注册的一个service
,系统收到进程A的bindService
请求后,会调用进程B中相应service
的onBind
方法,该方法返回一个特殊对象,系统会接收到这个特殊对象,然后为这个特殊对象生成一个代理对象,再将这个代理对象返回给进程A,进程A在ServiceConnection
回调的onServiceConnected
方法中接收该代理对象,依靠这个代理对象的帮助,就可以解决我们的问题啦。
总体流程如下图:
step 1: 进程B创建Binder 对象
为进程B实现一个特殊的对象,就是前面提到的service
的onBind
方法要返回的对象。这个对象有两个特性:
- 一个是具有完成特定任务的能力(在我们的问题中,就是将两个整数相加并返回结果的能力)
- 一个是被跨进程传输的能力。
什么样的对象具有这样的能力呢?答案是Binder
类的对象。下面我们分析一下Binder
是怎样拥有这两个能力的。
Binder中有如下关键方法:
public class AIDLService extends Service { private static final String TAG = "AIDLService"; IPerson.Stub stub = new IPerson.Stub() { @Override public String greet(String someone) throws RemoteException { Log.i(TAG, "greet() called"); return "hello, " + someone; } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind() called"); return stub; } ... }
Binder具有被跨进程传输的能力是因为它实现了IBinder
接口。系统会为每个实现了该接口的对象提供跨进程传输,这是系统给我们的一个很大的福利。
Binder具有的完成特定任务的能力是通过它的attachInterface
方法获得的,我们可以简单理解为该方法会将(descriptor,plus
)作为(key,value
)对存入Binder
对象中的一个Map<String,IInterface>
对象中,Binder
对象可通过attachInterface
方法持有一个IInterface
对象(即plus
)的引用,并依靠它获得完成特定任务的能力。queryLocalInterface
方法可以认为是根据key
值(即参数 descriptor
)查找相应的IInterface
对象。onTransact
方法暂时不用管,后面会讲到。
好的,现在我们来实现IInterface
和Binder
对象,概略代码如下:
public interface IPlus extends IInterface {public int add(int a,int b); }public class Stub extends Binder {@Overrideboolean onTransact(int code, Parcel data, Parcel reply, int flags){......//这里我们覆写了onTransact方法,暂时不用管,后面会讲解。 }...... } IInterface plus = new IPlus(){//匿名内部类public int add(int a,int b){//定制我们自己的相加方法return a+b;}public IBinder asBinder(){ //实现IInterface中唯一的方法,return null ;} }; Binder binder = new Stub(); binder.attachIInterface(plus,"PLUS TWO INT");
step 2: 进程A接收进程B的Binder对象
好了,现在我们有了这个特殊的对象binder
,可以在进程B的service
中的onBind
方法将它返回了,即return binder ;
下面就是见证奇迹的时候。系统会首先收到这个binder
对象,然后,它会生成一个BinderProxy
(就是前面提到的Binder 的内部类)类的对象,姑且称之为binderproxy
,然后将该对象返回给进程A,现在进程A终于在onServiceConnected
方法中接收到了binderproxy
对象(心情有木有小激动?)。为了下面讲解方便,再次贴出Binder
类的概要信息。
public class Binder implement IBinder{void attachInterface(IInterface plus, String descriptor)IInterface queryLocalInterface(Stringdescriptor) //从IBinder中继承而来boolean onTransact(int code, Parcel data, Parcel reply, int flags)//暂时不用管,后面会讲。final class BinderProxy implements IBinder {IInterface queryLocalInterface(Stringdescriptor) {return null ;//注意这行代码!!//下面会讲到。这行代码只是示例,不是源代码。 }......} }
此时的进程A以为收到的是binder
对象,它兴奋了,它迫不及待地要通过queryLocalInterface
方法获取这个binder
的plus
对象,利用该对象的加法功能进行加法计算。可结果呢?
首先,binderproxy.queryLocalInterface("PLUS TWO INT")
调用是合法的,因为queryLocalInterface
方法是IBinder
中的方法,而BinderProxy
和Binder
都实现了IBinder
接口。但是,binderproxy
对象显然没有plus
对象,因为它根本就没有attachInterface
方法(这是Binder
才有滴)。所以,可想而知,进程A的binderproxy.queryLocalInterface("PLUS TWO INT")
调用返回的将是一个null
(参见上面的示例代码)。
step 3: 进程A利用进程B传过来的对象发起请求
进程A出离愤怒了,我要的是binder
,我要的是它里面的plus
来帮我完成加法运算,进程B竟然给我一个冒牌货binderproxy
(显然,它冤枉了进程B,都是系统惹得祸)。
正在进程A气得头顶冒烟时,binderproxy
对象说话了:“别生气进程A,我虽然只是binder
对象的代理,但是,我也不是吃素的,你把你的数据(两个int
)和你想进行的操作(plus.add
)通过我的transact
方法(这是在IBinder
接口中定义的方法)交给我,我可以替你向binder
对象请求你需要的功能,等binder
对象把结果给我时,我再把结果交给你不就行了?”
于是,进程A通过binderproxy
对象的transact
方法,提交了请求。代码概略如下:
android.os.Parcel data = android.os.Parcel.obtain(); android.os.Parcel reply = android.os.Parcel.obtain();int _result;data.writeInterfaceToken("PLUS TWO INT"); data.writeInt(a); data.writeInt(b); binderproxy.transact(1, data, reply, 0);//为简单起见,最后一个0暂时不管它
简单解释一下上面代码。data
是用来写进程A的数据的(即整数 a和b),reply
是准备用来接收结果的。transact
方法中的第一个参数是整数1,它是进程A与进程B的一个约定,1就代表想让进程B对进程A传入的数据执行加法操作。这个约定也可以定义在 Stub类中,如下所示:
public static final int ADD = 1;
此时,我们可以将binderproxy.transact(1, data, reply, 0);
中的1替换为Stub.ADD
。Stub.ADD
其实可以是任何整数值的,我们选择1纯属为了简单。
step 4: 进程B收到并处理进程A的请求
binderproxy.transact
调用发生后,会引起系统的注意,系统意识到binderproxy
想找它的真身binder
对象执行一个操作了(看!系统其实一直存着binder
和binderproxy
的对应关系呢!)。于是系统将这个请求中的数据转发给binder
对象,binder
对象将会在onTransact
中收到binderproxy
传来的数据(Stub.ADD,data,reply,0
),于是它从data
中取出进程A传来的数据,又根据Stub.ADD
确定进程A想让它执行加法操作,于是它就执行了加法操作,并把结果写回reply
。代码概略如下:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR);return true; } //样板代码,不用管,下一行才是重点。case Stub.ADD: { data.enforceInterface("PLUS TWO INT"); int _arg0; _arg0 = data.readInt();int _arg1; _arg1 = data.readInt();int _result = this.queryLocalIInterface("PLUS TWO INT") .add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; }} return super.onTransact(code, data, reply, flags); }
简单解释一下以上代码。我们知道进程A写数据时写入了一个InterfaceToken
,就是这行代码
data.writeInterfaceToken("PLUS TWO INT");
这个意思是说,让进程B在自己的binder
对象中利用PLUS TWO INT
调用queryLocalIInterface
方法查找相应的IInterface
对象,进程A要执行的操作就在该对象中,至此,我们很容易理解Stub.ADD
就代表了plus
中的add
方法。这是一个二级查找过程,即通过PLUS TWO INT
确定要plus
来执行功能,通过Stub.ADD
确定要执行plus
中的add
方法。
step 5: 进程A获取进程B返回的处理结果
进程B把结果写入reply
后,进程A就可以从reply
读取结果了。代码概略如下:
binderproxy.transact(Stub.ADD, data, reply, 0); reply.readException(); _result = reply.readInt();
更深入的Binder原理参考
Android Bander设计与实现 - 设计篇
转载于:https://www.cnblogs.com/mingfeng002/p/6978949.html
android Binder机制(一)架构设计相关推荐
- Android Binder机制(1):Binder架构分析
从这篇博客开始,将进入Binder机制的分析系列,顺序是先讲解Binder机制的框架,理解了整体思想后,再深入分析各层的细节实现,最后会实现一个自己的本地服务. 1.Binder的历史 BeOS是Be ...
- aidl使用_借助 AIDL 理解 Android Binder 机制——Binder 来龙去脉
AIDL 是 Android Interface Definition Language(Android 接口定义语言)的缩写,它是 Android 进程间通信的接口语言.由于 Android 系统的 ...
- 理解Android Binder机制(3/3):Java层
本文是Android Binder机制解析的第三篇,也是最后一篇文章.本文会讲解Binder Framework Java部分的逻辑. Binder机制分析的前面两篇文章,请移步这里: 理解Andro ...
- 理解Android Binder机制(1/3):驱动篇
Binder的实现是比较复杂的,想要完全弄明白是怎么一回事,并不是一件容易的事情. 这里面牵涉到好几个层次,每一层都有一些模块和机制需要理解.这部分内容预计会分为三篇文章来讲解.本文是第一篇,首先会对 ...
- Android Binder机制情景源码分析之Binder回调注册和反注册
我们在日常开发中,经常用到Binder来进行跨进程通信,有个比较常见的场景是向服务端注册Binder回调,比如: IActivityManager中有两个成对的方法,Client端向AMS所在的服务端 ...
- Android开发面试:架构设计和网络知识答案精解
目录 架构设计 编程思想 六大设计原则 重构-Code Smell AOP 设计模式 创建型5个 行为型11个 结构型7个 编程范式 MVC MVP MVVM MVI 模块化 组件化 插件化.热修复 ...
- Android Binder机制学习笔记
声明,学习材料:http://my.unix-center.net/~Simon_fu/?p=875,不是简单的copy 1 Android的进程间通讯机制(IPC)用的是自己的binder机制 ...
- 数据传递型情景下事件机制与消息机制的架构设计剖析(目录)
目录 数据传递型情景下事件机制与消息机制的架构设计剖析(一) 转载于:https://www.cnblogs.com/hailan/p/3616766.html
- Android - Binder机制 - Binder框架总结
以下几篇文章是较深入分析binder机制. 目录 1. Android - Binder机制 - ServiceManager 2. Android - Binder机制 - 普通service注册 ...
最新文章
- 一键生成CSDN文章的思维导图目录
- Hibernate 实体映射类的状态值自动转换
- java -jar 停止_推荐:Linux启动Java程序jar包Shell脚本
- 微信公众平台开发(97) 图文消息
- mybatis Example 使用方法
- nginx服务器开启缓存、反向代理
- 数据结构与算法系列——从菜鸟到入门
- python getopt参数参数自动补全_如何在Python中使用getopt / OPTARG?如果给出过多的参数(9),如何转移参数?...
- Socket编程实践(2) --Socket编程导引
- ubuntu 16卸载mysql_ubuntu16.04 彻底卸载MySQL
- 为什么Linux CFS调度器没有带来惊艳的碾压效果? | CSDN博文精选
- 线性表_栈_逆波兰计算式(Reverse Polish Notation)
- IE8的样式兼容性适应方法【转】
- 苹果macOS 13 Ventura 5K原生动态壁纸
- 12 | 腾讯云代码分析快速部署
- Mapper的xml文件基础语法笔记,增删改查,遍历
- 大数据、云计算、元宇宙——吉吉拍的探索之路
- 苹果cms10自适应模板好看的苹果cmsv10美化模板免费
- hypermesh闪退启动解决(最全!!!)
- login shell和non-login shell
热门文章
- html密码框输入内容隐藏,密码框显示提示文字的功能实现
- linux外接NetApp存储,netapp linux iscsi 实现
- vue 指令 v-cloak
- mysql with
- linux 自动启动设置
- linux常用删除空文件夹,Linux基础 linux系统中的批量删除文件与空文件删除的命令介绍...
- mysql事务与锁_mysql之事务和锁
- VMware vSAN性能测试那点后续的事
- 截止2020年06月06日证书获得
- Spring学习总结(11)——Spring JMS MessageConverter介绍