Android进阶;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传递的数据较大(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,就可以自己想办法实现了,比如通过接口实现回调,这里就不详述了。
整个过程大概是这样的:

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进行通信,相关过程和关系大概是这样的:

与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也指向这块儿共享内存,从而实现跨进程数据访问。

附录;

附录一;Android高级技术大纲

附录二;Android进阶实战技术视频

获取方式;

加Android进阶群;701740775。即可前往免费领取。麻烦备注一下csdn领取资料

猜你喜欢

转载自blog.csdn.net/feiyu1947/article/details/86540609
今日推荐