[笔记]Binder通信机制概述

IPC

我们知道,在操作系统中,进程是最基本的单位,各自拥有独立的内存空间,所以进程间无法直接访问。
所以,各个操作系统都有跨进程通信机制(pipe管道/signal消息/消息队列/共享内存/semaphore信号量/Socket套接字等),IPC就是所有跨进程通信机制的统称。
在Android中,也使用了一些传统的IPC机制,例如Zygote进程的IPC采用的是Socket套接字机制(AMS通过socket通知Zygote为应用fork进程),Android中的Kill Process采用的signal信号机制,但是,在system_server进程和上层App层中,主要使用的是Binder通信机制。

Binder

Binder是Android中最常用的IPC之一,比如每个应用的主线程ActivityThread和AMS系统进程之间,就是使用binder通信(App进程通过ActivityManagerNative引用的ActivityManagerProxy向system server发送bindler消息)
图片来自startService源码从AMS进程到service的新进程启动过程分析
  • 优势
    Android中使用Binder机制,一方面是为了提高效率(数据拷贝少+C和S相对独立不干扰),一方面是为了加强安全性(通信双方身份可以确定,Server可以通过UID/PID判断是否满足访问权限)。
  • 原理

    Binder是一种C/S结构,Binder通信其实主要做两件事:维护通信地址和建立信息通道,在Binder通信机制里,Service要注册到系统内核的Service Manager里(Service Manager也是一个系统进程),Client从Service Manager里(实际上每个进程都有Service Manager的代理对象)找到要通信的Service,相当于找到通信地址;当进行通信时,通过系统内核提供的Binder驱动,在Client和Service直接建立连接。
    图片来自官网
    图片来自官网

    Client通过Server代理来访问Server,Server代理会把Client传递的参数打包成Parcel对象,通过mmap映射,实现发送给内核中的Binder驱动,Server端会自己去Binder驱动力读取数据,从中找到发送给自己的Parcel数据,解包处理。

    mmap映射过程其实是这样的,跨进程通信需要解决不同用户之间不能共享内存的问题,为此,mmap从接收方的用户空间里映射一块内存出来,放到系统内核,发送方的数据通过copy_from_user()复制到内核空间时,内核会把它放到接收方映射出来的这块儿内存里,所以不需要用copy_to_user()再把数据从内核向接收方复制一遍。


    Binder跨进程

    如果要通过Binder传递的数据较大(1M),Binder就会借助匿名共享内存Ashmem(Anonymous Shared Memroy)来传递,基本原理就是对同一块儿物理内存进行映射,在各自进程中映射为各自的虚拟内存,这样就可以在一个进程中写,在另一个进程中读,实现跨进程通信的目的。
  • Bind Service
    Binder机制在Android中应用非常广泛,它在系统分层上位于Android Framwork层的下一层,所以跨进程通信主要都是通过Binder实现,比如Service和Activity的就可以通过Binder来建立通信,即使它们分处两个进程。基本步骤是这样的:
    1.Service提供Binder对象
    在Service里自定义一个Binder类
public class YourBinder extends Binder {  
        YourService getService() { //通过Binder提供你的Service
            return YourService.this;  
        }  
    } 

然后在Service里的onBind函数返回IBinder对象(系统从Server Manager中检索到IBinder对象)

public IBinder onBind(Intent intent) {
    return yourBinder;
}

2.Activity发起BindService
Context提供的函数bindService(intent,connection,flag),就是用来向系统申请建立Binder关系的,其中在intent里告诉系统,由谁来提供目标Binder(就是上一步中的Service,Service在onBind中告诉系统Binder)。
而参数connection如下:

private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {
        ... 

在onServiceConnected回调函数中,IBinder service就是Service返回给系统的那个Binder对象。
Binder是一个C/S结构,这里的Activity就是C的角色,我们可以把多个
C给Bind到同一个S上,如果所有的C都和S解除了绑定(unBindService),并且Server本身不是startService起来的,系统会销毁这个无人需要的Service。
3.双向操作
IBinder service可以直接映射为Service中定义的Binder,然后获取Service实例,这样就能从Activity操作Service。

yourService = ((YourService.YourBinder) service).getService(); 

获取到Service实例之后,想要从Service反向操作Activity,就可以自己想办法实现了,比如通过接口实现回调,这里就不详述了。
整个过程大概是这样的:


Bind Service

AIDL

开发者经常遇到跨进程通信的需求,如果自己实现Binder机制,工作量就比较大,系统提供了AIDL接口,可以让我们更方便地实现Binder机制,用起来和在Activity里BindService很相似,主要过程如下:
1.新建aidl文件
new一个aidl文件,AS会在你的src/main/java目录同级创建一个src/main/aidl目录,然后在里面创建你的aidl文件及其所属package目录。
2.sync project
sync一下你的工程,AS会建立一个app/build/generated/source/aidl/目录,里面自动生成aidl文件对应的java接口类,这个接口类与你的aidl文件同名。
3.生成Stub
系统生成的aidl接口中,最重要就是Stub抽象类,Stub抽象类扩展android.os.Binder,同时实现你的aidl接口

public static abstract class Stub extends android.os.Binder implements IYourAidlInterface{
...

Stub里做了两件重要的事:作为Binder接收和处理消息

public boolean onTransact(...){...}

以及向调用者提供Binder代理Proxy,以便让对方给自己发消息

 private static class Proxy implements IYourAidlInterface {
        private android.os.IBinder mRemote;
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
       ...

其实,Binder并不总是返回Proxy跨进程代理,它会判断你们是否在同一进程内,如果在同一进程内,Stub会直接给调用者一个接口对象(因为Stub继承了Binder,又实现了业务接口)。
4.做一个Service
在onBind里返回我们定义的aidlBindler接口实例,实际上是通过Service Manager在本地进程中的代理,去Service Manager中寻找匹配的Service。
5.调用Binder
调用者需要自己实现一个ServiceConnection,通过context.bindService向系统要求绑定Binder,绑定成功后,就可以在ServiceConnection中通过回调函数获取IBinder:

private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            yourInterface = IYourAidlInterface.Stub.asInterface(service);

这一步里,其实是Stub视双方进程情况,返回接口或Binder代理Proxy,Stub会通过这段代码检查进程:

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

如果是同一进程内,不需要跨进程,就直接提供一个nterface接口;如果是跨进程,就提供一个Binder对象代理(IYourAidlInterface.Stub.Proxy)来实现跨进程。
6.跨进程传输
在跨进程情况下,系统会使用Binder代理Proxy来传输数据,Proxy里有一个IBinder接口对象。
Proxy传递数据其实就是调用IBinder对象,用IBinder对象的transact函数发出数据,这就会触发服务端的onTransact回调函数(在Stub里实现onTransact),服务端再把计算结果写入_reply返回值,Proxy就能通过_reply拿到返回数据:

//向服务端发送数据
mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
_reply.readException();
//接收服务端返回值
_result = _reply.readString();

所以,客户端主要是通过Stub进行通信,相关过程和关系大概是这样的:


跨进程时Stub的类和函数调用

与Intent的对比

Intent是另一种很常用的通信机制,一般会被打包为Parcel进行传输,Intent与Binder相比,效率很低,因为它是发送给系统进程,系统对intent过滤后,找到目标组件,再由系统把数据转交给目标组件,不像Bindler直接在C-S之间通信来得高效。
Intent有7个属性:ComponentName、Action、Category、Data、Type、Extra以及Flag,这7种属性可以分三类。
第一类:启动过滤,有ComponentName(显式),Action(隐式),Category(隐式)。
第二类:启动模式,Flag。
第三类:数据传值,有Data(隐式),Type(隐式),Extra(隐式、显式)。
Intent分显式和隐式两种:

  • 显式Intent就是用setClass或setComponent明确指定了启动哪个Activity或其他组件。所以显式Intent效率高,但是耦合度高。
  • 隐式Intent就是不直接指定要启动的组件,而是通过设置Intent Filter过滤条件(Action+Category),由系统来筛选优先级最高的那个Activity或其他组件,如果有多个最高优先级的组件,会提示用户手动选择。所以隐式Intent效率低,但是耦合度低。

隐式Intent必须有Action,并且在Category中至少有一个android.intent.category.DEFAULT,否则无法匹配过滤。
在数据传值时,Data其实是传一个URI,如果想为这个URI定义一个TYPE,就需要把URI和TYPE一起传给Intent,例如:intent.setDataAndType(Uri.parse(url), "audio/mp3");。
Intent还可以放Android系统剪切数据,intent.setClipData(ClipData);

与ContentProvider对比

ContentProvider就更弱了,它只是作为数据接口,向其他进程提供数据,我们也可以通过android:multiprocess属性,让每个访问进程都自己创建一个ContentProvider实例,不用去跨内存访问,当然,这样会有内存和数据同步问题,但是数据访问效率高。
ContentProvider的基本原理是ASHMEM匿名共享内存,ContentProvider保存的数据本来是不对其他进程开放的,但是其他内存可以创建一块匿名共享内存,然后用Binder将CursorWindow和共享内存文件描述传递过来,让ContentProvider也指向这块儿共享内存,从而实现跨进程数据访问。

引用

IPC、Binder、AIDL与Intent之间区别与联系
Android Bander设计与实现 - 设计篇
轻松理解 Android Binder,只需要读这一篇
Android 中AIDL的使用与理解
一次搞定Process和Task
Android学习笔记——Activity的启动和创建

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容