一、前言

在上一篇博客Android IPC机制(二):AIDL的基本使用方法中,笔者讲述了安卓进程间通讯的一个主要方式,利用AIDL进行通讯,并介绍了AIDL的基本使用方法。其实AIDL方式利用了Binder来进行跨进程通讯,Binder是Android中的一种跨进程通讯方式,其底层实现原理比较复杂,限于笔者水平,不能展开详谈,所以这篇文章主要谈谈以AIDL为例,谈谈Binder的使用方法。


二、原理

上一篇文章中创建了一个IMyAidl.aidl文件,即接口文件,随即编译了该文件,生成了一个.java文件,该文件在gen目录下:


打开该文件,得到如下代码:

/* * This file is auto-generated.  DO NOT MODIFY. * Original file: G:\\Android\\Project\\MyAidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\IMyAidl.aidl */package com.chenyu.service;public interface IMyAidl extends android.os.IInterface {    /**     * Local-side IPC implementation stub class.     */    public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {        ......    public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException;    public java.util.List getPersonList() throws android.os.RemoteException;}
其中省略了一部分,我们先从大体上认识,然后在深入。

(1)从大体上看,该java文件是一个接口,继承了IInterface接口,接着,声明了一个静态内部抽象类:Stub,然后是两个方法,可以看到,这两个方法分别是原IMyAidl.aidl文件内声明的两个方法。

(2)我们看回Stub类,它继承了Binder,同时实现了IMyAidl。这个类实现了自己的接口!那么可想而知,该接口所声明的addPerson,getPersonList方法,将会在Stub类得到实现,具体如何实现,我们展开Stub类:

public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {        private static final java.lang.String DESCRIPTOR = "com.chenyu.service.IMyAidl";        /**         * Construct the stub at attach it to the interface.         */        public Stub() {//①            this.attachInterface(this, DESCRIPTOR);        }        /**         * Cast an IBinder object into an com.chenyu.service.IMyAidl interface,         * generating a proxy if needed.         */        public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {    //②            if ((obj == null)) {                return null;            }            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {                return ((com.chenyu.service.IMyAidl) iin);            }            return new com.chenyu.service.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 {//④            switch (code) {                case INTERFACE_TRANSACTION: {                    reply.writeString(DESCRIPTOR);                    return true;                }                case TRANSACTION_addPerson: {                    data.enforceInterface(DESCRIPTOR);                    com.chenyu.service.Person _arg0;                    if ((0 != data.readInt())) {                        _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);                    } else {                        _arg0 = null;                    }                    this.addPerson(_arg0);                    reply.writeNoException();                    return true;                }                case TRANSACTION_getPersonList: {                    data.enforceInterface(DESCRIPTOR);                    java.util.List _result = this.getPersonList();                    reply.writeNoException();                    reply.writeTypedList(_result);                    return true;                }            }            return super.onTransact(code, data, reply, flags);        }        private static class Proxy implements com.chenyu.service.IMyAidl {//⑤            ...        }        static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); //⑥        static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);    }
(3)从上往下地,我们逐个分析一下各个方法或者变量的作用:

①Stub()构造方法:此方法调用了父类Binder的attachInterface()方法,将当前的Interface与Binder联系起来,由于传递了DESCRIPTOR这个参数,唯一标识了当前Interface。


②asInterface(IBinder obj) :静态方法,传递了一个接口对象,该对象从哪里传递进来的呢?我们来看看上一章博客的客户端代码:

public void onServiceConnected(ComponentName name, IBinder service) {            Log.d("cylog", "onServiceConnected success");            iMyAidl=IMyAidl.Stub.asInterface(service);        }
在这里,可以看到,调用了IMyAidl.Stub.asInterface(service)方法,即上面的②号方法,并且把service传递了进去,我们接着往下看:

public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {            if ((obj == null)) {                return null;            }            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {                return ((com.chenyu.service.IMyAidl) iin);            }            return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);        }
首先判断obj是否有效,如果无效直接返回Null,说明客户端与服务端的连接失败了。接着调用了obj.queryLocalInterface(DESCRIPTOR)方法,为IInterface的对象赋值,注意到这里再次传递了DESCRIPTOR参数,可以猜测,这个方法应该是查找与当前Interface相关的一个方法,我们看看IBinder接口的queryLocalInterface()方法:

/**     * Attempt to retrieve a local implementation of an interface     * for this Binder object.  If null is returned, you will need     * to instantiate a proxy class to marshall calls through     * the transact() method.     */    public IInterface queryLocalInterface(String descriptor);
大概意思是说,根据descriptor的值,试图为Binder取回一个本地的interface,其中local意思应为当前进程,如果返回值是null,那么应该实例化一个proxy类。在了解了obj.queryLocalInterface(DESCRIPTOR)方法后,我们再次回到asInterface(obj)方法,继续往下看:接着是一个if判断,主要判断客户端与服务端是否处于同一进程,如果处于同一进程,那么直接返回了Stub对象本身,如果不是同一个进程,那么就会新建一个Proxy代理类(下面会提到)。


③asBinder():此方法用于返回当前对象本身。

④onTransact(int code,Parcel data,Parcel reply,int flags):该方法一般运行在服务端中的Binder线程池中,即远程请求会在该方法得到处理。传递的code值用于判断客户端的请求目标,是addPerson或者是getPersonList。我们以请求目标为addPerson()为例分析一下,提取其主要函数体如下:

case TRANSACTION_addPerson: {                    data.enforceInterface(DESCRIPTOR);                    com.chenyu.service.Person _arg0;                    if ((0 != data.readInt())) {                        _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);                    } else {                        _arg0 = null;                    }                    this.addPerson(_arg0);                    reply.writeNoException();                    return true;                }
首先声明了_arg0是Person类的对象,接着,以data为参数调用了Person类的CREATOR.createFromParcel方法,反序列化生成了Person类,这也是为什么实现了Parcelable接口的类应该同时实现CREATOR,原来在这里调用了反序列化的方法。接着,调用this.addPerson(_arg0)方法,注意:这里的this代表当前的Binder对象,那么由Binder调用的addPerson(_arg0)方法,实际上是由绑定到Binder的service调用的,即服务端调用了自身的addPerson方法。为了方便明白,让我们来回顾一下上一篇文章服务端的代码:

private IBinder iBinder= new IMyAidl.Stub() {        @Override        public void addPerson(Person person) throws RemoteException {            persons.add(person);        }        @Override        public List getPersonList() throws RemoteException {            return persons;        }    };
是不是一下子就明白了?IMyAidl.Stub()实现的接口,其中的方法在服务端得到了实现:addPerson和getPersonList()。当在Binder线程池中,调用了this.addPerson()方法,实际上回调了服务端的addPerson方法,而底层到底是怎么实现的,限于笔者的水平,暂时不了解,等以后笔者再深入了解Binder的工作机制再回答这个问题。

好了,回到当前的类,我们继续往下看:
⑤private static class Proxy:这里又出现了一个私有的静态内部类,关于这个类将在接下来详细讲述。


⑥最后两行代码分别是两个常量,标志了两个方法,即上面提到的code值。


(4)Proxy类,也实现了IMyAidl接口,同时实现了addPerson和getPersonList的方法。而Proxy类在哪里被实例化的呢?是上面(3)②中,当客户端与服务端不在同一个进程的时候,就会实例化这个代理类,并返回给客户端。什么叫做代理类呢?所谓代理,即一个中介,客户端拿到的实例,能操作服务端的部分功能,让客户端以为自己已经拿到了服务端的实例,其实不是,只是拿到服务端的一个代理而已。接下来我们展开该类,看看内部:

private static class Proxy implements com.chenyu.service.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 void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    if ((person != null)) {                        _data.writeInt(1);                        person.writeToParcel(_data, 0);                    } else {                        _data.writeInt(0);                    }                    mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);                    _reply.readException();                } finally {                    _reply.recycle();                    _data.recycle();                }            }            @Override            public java.util.List getPersonList() throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                java.util.List _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);   //①                    _reply.readException();                    _result = _reply.createTypedArrayList(com.chenyu.service.Person.CREATOR);                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }        }
这里关注两个接口方法的实现:addPerson和getPersonList,这两个方法已经多次出现了,而在代理类实现的这两个方法,是运行在客户端的!!!其主要实现过程是这样的:当客户端拿到代理类,调用addPerson或者getPersonList方法,首先会创建输入型Parcel对象_data和输出型Parcel对象_reply,接着调用①号代码调用transact来发起RPC远程请求,同时当前线程会被挂起,此时,服务端的onTransact会被调用,即上面所说的(3)④号代码,当服务端处理完请求后,会返回数据,当前线程继续执行知道返回 _result结果。


至此,对于IPC的方式之一——AIDL的原理已经剖析完毕,接下来总结一下:

1、客户端发出绑定请求,服务端和客户端绑定在同一个Binder上。客户端执行asInterface()方法,如果客户端和服务端处于同一进程,则直接返回服务端的Stub对象本身,如果处于不同进程,则返回的是Stub.proxy代理类对象。

2、客户端发送远程请求(addPerson或者getPersonList),此时客户端线程挂起,Binder拿到数据后,对数据进行处理如在不同进程,会把数据写入Parcel,调用Transact方法。

3、触发onTransact方法,该方法运行在Binder线程池,方法中会调用到服务端实现的接口方法,当数据处理完毕后,返回reply值,经过Binder返回客户端,此时客户端线程被唤醒。


三、优化

最后说一说如何优化AIDL,上面提到,客户端发送请求后,会被挂起,这意味着,如果处理数据的时间过长,那么该线程就一直等不到唤醒,这是很严重的,如果在UI线程发送请求,会直接导致ANR,所以我们需要在子线程发送异步请求,这样才能避免ANR。还有一点,Binder可能是意外死亡的,如果Binder意外死亡,那么子线程可能会一直挂起,所以我们要启用重新连接服务。有两个方法,一个是给Binder设置DeathRecipient监听,另一个是在onServiceDisconnected中重连服务




参考书籍:《Android 开发艺术探索》 任玉刚著,2015年9月第一版

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android中dispatchDraw分析
  7. Android四大基本组件介绍与生命周期
  8. Android(安卓)MediaPlayer 常用方法介绍
  9. 在Fragment中设置控件点击方法,执行失败。

随机推荐

  1. Android工程师怒斥360拦截弹窗广告
  2. Android系统架构解析
  3. Android(安卓)页面回调跳转(startActivity
  4. Android之隐式意图(Intent)如何查找匹配
  5. Android控件之TextView全解析
  6. 从零开始--系统深入学习android(实践-让我
  7. Android内核详解之Low memory killer
  8. 在Ubuntu7.10上编译android
  9. android差分更新之服务端
  10. Ubuntu下 eclipse搭建android集成开发环