比较浅显易懂的AIDL

Binder是Android中的一种跨进程通信方式,Android的四大组件、各种Manager 和其对应ManagerService等无不与Binder挂钩。从Android Framework角度来说,Binder是ServiceManager连接ActivityManager、WindowManager等Manager和他们相应ManagerService的桥梁; 从Android 应用层来说,Binder是客户端和服务端进行通信的媒介,正如我们在使用Service时bindService时, Service的onBind方法会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取Service中提供的服务或数据,这里的服务包括普通服务和基于AIDL的服务。

Binder单词翻译过来叫“粘合剂”,它的机制是将Client、Server、ServiceManager和Binder驱动粘合起来。其中Client、Server和Service Manager运行在用户空间,Binder驱动运行在内核空间。Service Manager和Binder驱动已经在Android平台中实现好,我们在开发过程中只要按照规范实现自己的Client和Server组件就可以了。

了解内核空间 和 用户空间
Linux内核独立于普通的应用程序,它可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。内核和上层的应用程序抽像隔离开,分别称之为内核空间和用户空间。

ServiceManager
ServiceManager是整个Binder IPC通信过程中的守护进程,本身也是一个Binder服务,它的主要就两个工作就是查询和注册Service。

Binder驱动 内核模块
linux进程间通信有很多种比如信号量、通道、socket等,但是android是用的binder。用户空间可以通过系统提供的方法调用访问内核空间,而一个用户空间想与另外一个用户空间进行通信的话,就得使用Linux的动态可加载内核模块机制(Loadable Kernel Module,LKM),因为Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间通过这个模块作为桥梁,就可以完成通信了,而这个负责各个用户进程通过Binder通信的内核模块程序叫做Binder驱动。

通信过程
我们通过例子来讲述Binder的通信过程,当你想给你的朋友打电话时,那么你就是Client,而你的朋友就是Server,而电话公司就是ServiceManager,它系统里注册了很多人的电话号码。当你在拔打你朋友电话时,若你朋友电话号码是没有在电话公司注册过,就会返回空号的错误,也就是Server没有在ServiceManager中注册,否则就能拔打电话,在这个拔过电话过程中,是要通过基站来完成接线的,而基站就是Binder驱动。

划重点 AIDL原理
AIDL是Binder的延伸,我们知道,当新建一个aidl文件后执行编译后,便会在app\build\generated\source\aidl\debug\目录下生成对应的同名的类文件。我们在日常开发中,大可不必理会此文件,也正因为此文件是自动生成和格式错乱使很多人对其望而止步。其实只要将该Java文件格式化后,就会清晰多了,这个类的结构其实很简单的。只要读懂该类,基本上就会明白AIDL的工作原理了。

首先创建一个IMyAidl.aidl文件,内容如下:

interface IMyAidl {
    int sum(int a, int b);
}

在执行编译后,就会在app\build\generated\source\aidl\debug目录下生成IMyAidl.java文件。在介绍IMyAidl.java文件前,先来创建 Client端和Server端的调用代码。

Client端的MainActivity.Java代码:

public class MainActivity extends Activity {
 
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
 
            IMyAidl myaidl = IMyAidl.Stub.asInterface(iBinder);
            try {
                myaidl.sum(1, 2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public void onServiceDisconnected(ComponentName className) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        super.onDestroy();
    }
}

Server端的MyService.Java代码:

public class MyService extends Service {
    private Binder mBinder = new IMyAidl.Stub() {
        @Override
        publicint sum(int a, int b) throws RemoteException {
            return a + b;
        }
    };
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

IMyAidl.java内容部分是这样:

 
public interface IMyAidl extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.***.IMyAidl {
        ……
    }
    public void sum(int a, int b) throws android.os.RemoteException;
}

IMyAidl.java文件中,存在一个继承自己的内部类Stub和我们在aidl中定义的sum方法。Stub就是一个Binder类。可以看出,它就是MyService中onBind方法return的mBinder对象的类。再来细看下Stub类的代码:

public static abstract class Stub extends android.os.Binder implements com***.IMyAidl {
    private static final java.lang.String DESCRIPTOR = "com.***.IMyAidl";
 
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }
 
    public static ***.IMyAidl asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof ***.IMyAidl))) {
            return ((com.***.IMyAidl) iin);
        }
        return new com.***.IMyAidl.Stub.Proxy(obj);
    }
 
    @Override
    public android.os.IBinder asBinder() {
        return this;
    }
 
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(descriptor);
                return true;
            }
            case TRANSACTION_sum: {
                data.enforceInterface(descriptor);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.sum(_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            default: {
                return super.onTransact(code, data, reply, flags);
            }
        }
    }
 
    private static class Proxy implements com***.IMyAidl {
        private android.os.IBinder mRemote;
 
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
 
        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }
 
        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }
 
        @Override
        public int sum(int a, int b) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeInt(a);
                _data.writeInt(b);
                mRemote.transact(Stub.TRANSACTION_sum, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }
 
    static final int TRANSACTION_sum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

下面我们来看看Stub类的变量和方法的含义:

DESCRIPTOR

DESCRIPTOR是Binder的唯一标识,一般用当前Binder的类名表示

asInterface

asInterface方法用于将Server端的Binder对象转换成Client端所需的AIDL接口类型的对象,正如我们可以看到asInterface方法就是我们在Client端中代码:

IMyAidl myaidl = IMyAidl.Stub.asInterface(iBinder);
try {
    myaidl.sum(1, 2);
} catch (Exception e) {
    e.printStackTrace();
}

asInterface方法它的参数是接收由服务绑定成功后返回的一个IBinder对象,返回值一个AIDL接口类型的对象,从代码中可以发现,该对象有三种情况返回值,第一种因为参数输入错误所以返回了null; 接着判断了Client端和Server端是否位于同一进程,如果是同一进程,则是第二情况,返回就是Server端中的Stub对象本身,也就是本示例中就是MyService中的mBinder对象; 如果Client端和Server端不是同一进程,那么就是第三种情况,返回的是Stub类的内部代理类Proxy对象。而Client端中代码:myaidl.sum(1, 2);调用的就是Proxy类的sum方法。

Proxy

Proxy类是内部类Stub的内部代理类,同样也是继承于IMyAidl.java。它的sum方法会使用Parcelable来准备数据,把参数都写入_data,让_reply接收方法返回值。最后使用IBinder的transact方法把数据传给Binder的Server端去。代码中mRemote就是asInterface方法接收的参数obj。这时当前线程挂起,Server端的onTransact方法会被调用。

onTransact

onTransact方法是运行在Server中的Binder线程池中的,当Client端发起跨进程请求时,远程请求会通过系统底层封装后交由onTransact方法来处理。通过TRANSACTION_sum找到对应的方法sum,接着从data中取出从Client端进程传递过来的参数,然后执行目标方法sum,在执行完毕后就向reply中写入返回值。此时,Proxy中的sum方法线程恢复执行。

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

推荐阅读更多精彩内容