枕上从妨一夜睡,灯前读尽十年诗。这篇文章主要讲述Android binder学习一:主要概念相关的知识,希望能为你提供帮助。
要看得懂android代码,首先要了解binder机制。binder机制也是android里面比較难以理解的一块,这里记录一下binder的重要概念以及实现。作为备忘。
部分内容来源于网上,如有侵权。请及时告知。
1.binder通信机制概述binder通信是一种client-server的通信结构,
1.从表面上来看。是client通过获得一个server的代理接口,对server进行直接调用;
2.实际上,代理接口中定义的方法与server中定义的方法是一一相应的。
3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的參数打包成为Parcel对象。
4.代理接口将该Parcel发送给内核中的binder driver.
5.server会读取binder driver中的请求数据,假设是发送给自己的。解包Parcel对象,处理并将结果返回;
6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。
文章图片
2.为什么使用binder通信linux中有管道,system V IPC。socket等进程间通信机制,那么为什么在android中使用了一个全新的binder通信机制呢?
一、可靠性。在移动设备上,通常採用基于Client-Server的通信方式来实现互联网与设备间的内部通信。
眼下linux支持IPC包含传统的管道,System V IPC。即消息队列/共享内存/信号量,以及socket中仅仅有socket支持Client-Server的通信方式。
Android系统为开发人员提供了丰富进程间通信的功能接口,媒体播放。传感器。无线传输。
这些功能都由不同的server来管理。开发都仅仅关心将自己应用程序的client与server的通信建立起来便能够使用这个服务。
毫无疑问。如若在底层架设一套协议来实现Client-Server通信,添加了系统的复杂性。
在资源有限的手机 上来实现这样的复杂的环境,可靠性难以保证。
二、传输性能。
socket主要用于跨网络的进程间通信和本机上进程间的通信。但传输效率低。开销大。消息队列和管道採用存储-转发方式。即数据先从发送方缓存区复制到内核开辟的一块缓存区中,然后从内核缓存区复制到接收方缓存区。其过程至少有两次拷贝。尽管共享内存无需拷贝,但控制复杂。
比較各种IPC方式的数据拷贝次数。
共享内存:0次。Binder:1次。Socket/管道/消息队列:2次。
IPC | 数据拷贝次数 |
共享内存 | 0 |
Binder | 1 |
Socket/管道/消息队列 | 2 |
基于以上原因,Android须要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求。这就是Binder。Binder基于Client-Server通信模式,传输过程仅仅需一次拷贝,为发送发加入UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。
3.service manager顾名思义,service manager就是android以下管理service的一个进程,它本身也是一个service,这里的service和init.rc里面的service有一些区别,init.rc中的service都是一个进程,而这里的service可能不是一个单独的进程。
每个service在使用之前都必须向SM注冊。每个client要使用service前都应该先向SM查询是否存在这个service。假设存在,则给client返回这个service的handle。以下是sm中main函数的关键代码:
int main(int argc, char **argv) { struct binder_state *bs; bs = binder_open(128*1024); if (!bs) { ALOGE("failed to open binder driver\n"); return -1; }if (binder_become_context_manager(bs)) { ALOGE("cannot become context manager (%s)\n", strerror(errno)); return -1; }......//svcmgr_handle的值为0 svcmgr_handle = BINDER_SERVICE_MANAGER; binder_loop(bs, svcmgr_handler); return 0; }
在SM中主要做了例如以下工作:
- 打开binder设备,映射128k的内存到应用空间。
- 指定 svcmgr_handle的值为0,当client与SM通信时,须要先创建一个handle为0的代理binder。
- binder_become_context_manager通知binder driver使SM为context manager。
- binder_loop是一个死循环。里面不停的读binder是否有数据,假设有数据,则解析,对于BR_TRANSACTION,会调用svcmgr_handler来处理。
- SM维护了一个svclist来存储service的信息。一个新的service须要向SM注冊add到这个列表,而client请求时会在svclist里面查找请求的service。一个service包含两个重要的信息,handle和name。add和get都会依据name来进行匹配。
文章图片
由上可知,service在使用前会先作为client向SM注冊。应用若要使用某一个服务。须要先向SM获取该服务的handle,然后通过handle来调用该服务提供的方法。
4.ProcessStateProcessState是以单例模式设计的。每一个进程在使用binder机制通信时,均须要维护一个ProcessState实例来描写叙述当前进程在binder通信时的binder状态。
ProcessState有例如以下2个主要功能:
1.创建一个thread,该线程负责与内核中的binder模块进行通信,称该线程为Pool thread。
2.为指定的handle创建一个BpBinder对象。并管理该进程中全部的BpBinder对象。
4.1 Pool thread
在Binder IPC中。全部进程均会启动一个thread来负责与BD(binder driver)来直接通信。也就是不停的读写BD。这个线程的实现主体是一个IPCThreadState对象,以下会介绍这个类型。
以下是 Pool thread的启动方式:
ProcessState::self()-> startThreadPool();
4.2 BpBinder获取
BpBinder主要功能是负责client向BD发送调用请求的数据。它是client端binder通信的核心对象,通过调用transact函数向BD发送调用请求的数据。它的构造函数例如以下:
BpBinder(int32_t handle);
通过BpBinder的构造函数发现,BpBinder会将当前通信中server的handle记录下来,当有数据发送时,会通知BD数据的发送目标ProcessState通过例如以下方式来获取BpBinder对象:
ProcessState::self()-> getContextObject(handle);
在这个过程中,ProcessState会维护一个BpBinder的vector mHandleToObject。每当ProcessState创建一个BpBinder的实例时。回去查询mHandleToObject,假设相应的handle已经有binder指针,那么不再创建。否则创建binder并插入到mHandleToObject中。
ProcessState创建的BpBinder实例,普通情况下会作为參数构建一个client端的代理接口。这个代理接口的形式为BpINTERFACE,比如在与SM通信时,client会创建一个代理接口BpServiceManager。
5.IPCThreadStateIPCThreadState也是以单例模式设计的。因为每一个进程仅仅维护了一个ProcessState实例。同一时候ProcessState仅仅启动一个Pool thread,也就是说每一个进程仅仅会启动一个Pool thread。因此每一个进程则仅仅须要一个IPCThreadState就可以。
Pool thread的实际内容则为:
IPCThreadState::self()-> joinThreadPool();
ProcessState中有2个Parcel成员。mIn和mOut,Pool thread会不停的查询BD中是否有数据可读。假设有将其读出并保存到mIn,同一时候不停的检查mOut是否有数据须要向BD发送,假设有,则将其内容写入到BD中,总而言之。从BD中读出的数据保存到mIn,待写入到BD中的数据保存在了mOut中。
ProcessState中生成的BpBinder实例通过调用IPCThreadState的transact函数来向mOut中写入数据,这种话这个binder IPC过程的client端的调用请求的发送过程就明了了。
IPCThreadState有两个重要的函数,talkWithDriver函数负责从BD读写数据。executeCommand函数负责解析并运行mIn中的数据。
文章图片
6.主要基类6.1基类IInterface
为server端提供接口。它的子类声明了service可以实现的全部的方法。
6.2基类IBinder
BBinder与BpBinder均为IBinder的子类,因此能够看出IBinder定义了binder IPC的通信协议。BBinder与BpBinder在这个协议框架内进行的收和发操作,构建了主要的binder IPC机制。
6.3基类BpRefBase
client端在查询SM获得所需的的BpBinder后,BpRefBase负责管理当前获得的BpBinder实例。
7.两个接口类7.1 BpINTERFACE
假设client想要使用binder IPC来通信,那么首先会从SM出查询并获得server端service的BpBinder,在client端。这个对象被觉得是server端的远程代理。为了可以使client可以想本地调用一样调用一个远程server,server端须要向client提供一个接口,client在在这个接口的基础上创建一个BpINTERFACE,使用这个对象,client的应用可以想本地调用一样直接调用server端的方法。而不用去关心详细的binder IPC实现。
以下看一下BpINTERFACE的原型:
class BpINTERFACE : public BpInterface< IINTERFACE>
顺着继承关系再往上看
template< typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
BpINTERFACE分别继承自INTERFACE,和BpRefBase;
● BpINTERFACE既实现了service中各方法的本地操作,将每一个方法的參数以Parcel的形式发送给BD。比如BpServiceManager的
virtual status_t addService(const String16& name, const sp< IBinder> & service) { Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); status_t err = remote()-> transact(ADD_SERVICE_TRANSACTION, data, & reply); return err == NO_ERROR ? reply.readExceptionCode() : err; }
● 同一时候又将BpBinder作为了自己的成员来管理,将BpBinder存储在mRemote中,BpServiceManager通过调用BpRefBase的remote()来获得BpBinder指针。
7.2 BnINTERFACE
在定义android native端的service时,每一个service均继承自BnINTERFACE(INTERFACE为service name)。BnINTERFACE类型定义了一个onTransact函数。这个函数负责解包收到的Parcel并运行client端的请求的方法。
顺着BnINTERFACE的继承关系再往上看。
class BnINTERFACE: public BnInterface< IINTERFACE>
IINTERFACE为client端的代理接口BpINTERFACE和server端的BnINTERFACE的共同接口类,这个共同接口类的目的就是保证service方法在C-S两端的一致性。
再往上看
class BnInterface : public INTERFACE, public BBinder
同一时候我们发现了BBinder类型,这个类型又是干什么用的呢?既然每一个service均可视为一个binder,那么真正的server端的binder的操作及状态的维护就是通过继承自BBinder来实现的。可见BBinder是service作为binder的本质所在。
那么BBinder与BpBinder的差别又是什么呢?
事实上它们的差别非常easy,BpBinder是client端创建的用于消息发送的代理,而BBinder是server端用于接收消息的通道。查看各自的代码就会发现,尽管两个类型均有transact的方法。可是两者的作用不同,BpBinder的transact方法是向IPCThreadState实例发送消息,通知其有消息要发送给BD;而BBinder则是当IPCThreadState实例收到BD消息时,通过BBinder的transact的方法将其传递给它的子类BnSERVICE的onTransact函数运行server端的操作。
8.ParcelParcel是binder IPC中的最主要的通信单元。它存储C-S间函数调用的參数.可是Parcel仅仅能存储主要的数据类型,假设是复杂的数据类型的话,在存储时。须要将其拆分为主要的数据类型来存储。
简单的Parcel读写不再介绍。以下着重介绍一下2个函数。
8.1 writeStrongBinder
当一个service 调用add_service把自己增加到SM中时。就会遇到这样的情况,例如以下(IServiceManager.cpp):
virtual status_t addService(const String16& name, const sp< IBinder> & service, bool allowIsolated) { Parcel data, reply; data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name); data.writeStrongBinder(service); data.writeInt32(allowIsolated ? 1 : 0); status_t err = remote()-> transact(ADD_SERVICE_TRANSACTION, data, & reply); return err == NO_ERROR ?
reply.readExceptionCode() : err; }
当中writeStrongBinder(Parcel.cpp)例如以下:
status_t Parcel::writeStrongBinder(const sp< IBinder> & val) { return flatten_binder(ProcessState::self(), val, this); }
接着看flatten_binder:
status_t flatten_binder(const sp< ProcessState> & /*proc*/, const sp< IBinder> & binder, Parcel* out) { flat_binder_object obj; obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; if (binder != NULL) { IBinder *local = binder-> localBinder(); if (!local) { BpBinder *proxy = binder-> remoteBinder(); if (proxy == NULL) { ALOGE("null proxy"); } const int32_t handle = proxy ? proxy-> handle() : 0; obj.type = BINDER_TYPE_HANDLE; obj.binder = 0; /* Don‘t pass uninitialized stack data to a remote process */ obj.handle = handle; obj.cookie = 0; } else { obj.type = BINDER_TYPE_BINDER; obj.binder = reinterpret_cast< uintptr_t> (local-> getWeakRefs()); obj.cookie = reinterpret_cast< uintptr_t> (local); } } else { obj.type = BINDER_TYPE_BINDER; obj.binder = 0; obj.cookie = 0; }return finish_flatten_binder(binder, obj, out); }
addService的參数为一个BnINTERFACE类型指针。BnINTERFACE又继承自BBinder:
BBinder* BBinder::localBinder() { return this; }
所以写入到Parcel的binder类型为BINDER_TYPE_BINDER。在SM中,当service的binder类型不为BINDER_TYPE_HANDLE时,SM将不会将此service加入到svclist,可是非常显然每一个service的加入都是成功的,addService在開始传递的binder类型为BINDER_TYPE_BINDER,SM收到的binder类型为BINDER_TYPE_HANDLE。那么这个过程其中到底发生了什么?这个问题是这种,在binder driver中(Binder.c)由下面代码:
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) { ..........................................if (fp-> type == BINDER_TYPE_BINDER) fp-> type = BINDER_TYPE_HANDLE; else fp-> type = BINDER_TYPE_WEAK_HANDLE; fp-> handle = ref-> desc; .......................................... }
由之前我们已经知道,SM仅仅是保存了server binder的handle和name。那么当client须要和某个service通讯的时候,怎样获得service的binder呢?接着看readStrongBinder
8.2 readStrongBinder
当server端收到client的调用请求之后。假设须要返回一个binder时,能够向BD发送这个binder。当IPCThreadState实例收到这个返回的Parcel时。client能够通过这个函数将这个被server返回的binder读出。
sp< IBinder> Parcel::readStrongBinder() const { sp< IBinder> val; unflatten_binder(ProcessState::self(), *this, & val); return val; }
再看unflatten_binder:
status_t unflatten_binder(const sp< ProcessState> & proc, const Parcel& in, sp< IBinder> * out) { const flat_binder_object* flat = in.readObject(false); if (flat) { switch (flat-> type) { case BINDER_TYPE_BINDER: *out = reinterpret_cast< IBinder*> (flat-> cookie); return finish_unflatten_binder(NULL, *flat, in); case BINDER_TYPE_HANDLE: *out = proc-> getStrongProxyForHandle(flat-> handle); return finish_unflatten_binder( static_cast< BpBinder*> (out-> get()), *flat, in); } } return BAD_TYPE; }
发现假设server返回的binder类型为BINDER_TYPE_BINDER的话。也就是返回一个binder引用的话。直接获取这个binder;假设server返回的binder类型为BINDER_TYPE_HANDLE时,也就是server返回的不过binder的handle,那么须要又一次创建一个BpBinder返回给client。
有上面的代码能够看出,SM保存的service的binder不过一个handle,而client则是通过向SM获得这个handle。从而又一次构建代理binder与server通信。
这里顺带提一下一种特殊的情况。binder通信的两方就可以作为client,也能够作为server.也就是说此时的binder通信是一个半双工的通信。
那么在这样的情况下,操作的过程会比单工的情况复杂,可是主要的原理是一样的,有兴趣能够分析一下MediaPlayer和MediaPlayerService的样例。
以上就是涉及到binder通讯的一些比較重要的点。
关于binder的详细实现就须要查看binder driver的代码了。
查看详情
关注微信公众平台:程序猿互动联盟(coder_online),你能够第一时间获取原创技术文章。和(java/C/C++/Android/Windows/Linux)技术大牛做朋友。在线交流编程经验,获取编程基础知识,解决编程问题。程序猿互动联盟。开发者自己的家。
【Android binder学习一(主要概念)】
文章图片
推荐阅读
- android的控件
- uiautomator android 官网翻译
- Android 软键盘弹出时布局内指定内容上移实现及问题解决
- NiceFish的ERROR in AppModule is not an NgModule问题
- 如何设置kubectl端口转发(详细分步指南)
- 如何在Kubernetes集群上安装Jenkins(详细分步指南)
- 如何在Kubernetes上部署RabbitMQ(分步指南)
- 如何更改和使用Vim配色方案(详细操作指南)
- Cassandra与MongoDB有什么区别(哪个更好一点?)