Android AIDL 简单使用和部分解释

AIDL 是跨进程通信的一种实现方式,那么这里最少也要两个进程的角色才能够使用跨进程通信吧。那么接下将分成两个角色来说明使用步骤:

服务端进程:

①:创建一个 aidl 文件夹(可选),在该文件夹下创建一个 AIDL 文件:IOperationServer.aidl

②:在这个 aidl 文件中声明提供给客户端调用的接口,点击保存,即会在 gen 目录下自动生成该接口的 IOperationServer.java 文件

interface IOperationServer{
    double getSum(double first,double second);
}

③:创建 Service,创建 AIDL 接口的子类作为内部类(class Mabinder extends AIDLbigname.Stub{重写方法}), 然后再 onBind() 方法中返回该内部类的对象。

// 实现该接口,这里是实现该方法的地方
private IOperationServer.Stub mBinder = new IOperationServer.Stub() {
    @Override
    public double getSum(double first, double second)
            throws RemoteException {
        return first + second;
    }
};

// 在onbind()方法中返回该实例
@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

④:最后在清单文件中,注册服务:其中注意加上action:android:name="myservice"(这里可自定义,主要是为了让客户端通过action绑定服务)

<service android:name=".IOperationServer" >  
    <intent-filter>  
        <action android:name="com.hl.myservice" />  
        <category android:name="android.intent.category.DEFAULT" />  
    </intent-filter>  
</service>  

客户端进程:

① 将服务端的 AIDL 文件拷贝到 main 文件夹下。(包名要求与服务端的一致,会自动在 gen 目录下生成 IOperationServer.java 文件)

② 绑定服务:在需要需要调用该接口提供的方法的地方(一般就在 Activity)绑定服务端的 Service。
获取 aidl 接口实例。bindService 的第二个参数为 ServiceConnection 接口,当绑定的状态发生变化时,会回调该接口。该接口有两个函数:onServiceConnected 和 onServiceDisconnected。可以在 onServiceConnected 中获取服务端返回的 Binder 对象

private IOperationServer iOperationServer;  
private MyServiceConnection connection = new MyServiceConnection();  

Intent intent = new Intent("com.hl.myservice");  
private class MyServiceConnection implements ServiceConnection {  
     @Override  
      public void onServiceConnected(ComponentName name, IBinder service) {  
           iOperationServer= IOperationServer .Stub.asInterface(service);  
           Log.d("aidl", "onServiceConnected:" + iOperationServer);  
        }  
     @Override  
      public void onServiceDisconnected(ComponentName name) {  
        }  
};  
bindService(intent, connection , BIND_AUTO_CREATE);  

③ 可以通过这个接口 iOperationServer 去调用服务端的远程方法了

double sum = iOperationServer.getSum(first, second);

④ 记得在 onDestroy 中进行解绑。

unbindService(connection);  

自动生成的Java文件比较长,以前没有仔细关注过,现在对其中的内容进行一些分析:

整个文件是一个继承了 IIterface 的接口类,其中它的内容主要分为两个部分,
一个是在aidl 文件中声明的方法,另一个是一个抽象类 stub
关于第一部分,两个方法只需要在这里声明一下就可以了,不需要具体的实现过程,主要关注点还是在第二部分。
这个抽象类 Stub 继承 Binder 类,并实现你写的 aidl 文件类,它其中又分为以下几个部分:

  1. 同 aidl 文件中所声明几个方法所相对应的几个静态整型常量,用于标识在 transact 过程中客户端中请求的到底是哪个方法
  2. 一个静态常量:DESCRIPTOR
  3. 一个无参的构造方法
  4. asInterface() 方法
  5. asBinder() 方法
  6. onTransact() 方法
  7. 实现你所写的 aidl 文件类的 Proxy 类

贴出所有代码 :

1 定义一个 IOperation 接口,并在内部声明一个供客户端跨进程调用的方法,并使该接口继承 IInterface 接口。

public interface IOperation extends IInterface {

    /* Binder描述符 */
    static final String DESCRIPTOR = "com.example.aidlmanualtest.IOperation";

    /**
     * 方法标识符 :规定了最小和最大范围
     * The action to perform. This should be a number between
     * {@link #FIRST_CALL_TRANSACTION}
     * {@link #LAST_CALL_TRANSACTION}.
     * 
     * 这段说明是在IBiner的transact()方法中
     */
    static final int TRANSACTION_getSum = IBinder.FIRST_CALL_TRANSACTION + 0;
    // 定义了一个供跨进程调用的方法
    public double getSum(double first, double second) throws RemoteException;
}

2 跨进程方法调用的处理逻辑,主要集中在接口实现类 Stub 和 Stub 的代理 Proxy 类中

public class IOperationStub extends Binder implements IOperation {

    public IOperationStub() {
        /**
         * Convenience method for associating a specific interface with the
         * Binder. After calling, queryLocalInterface() will be implemented for
         * you to return the given owner IInterface when the corresponding
         * descriptor is requested.
         * 
         * 将一个IInterface类型的接口实例与Binder关联起来,DESCRIPTOR相当于一个标识。当该方法被调用后,
         * 可通过queryLocalInterface(DESCRIPTOR)获取与这个标识相应的接口实例
         */
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * 用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象区分进程:
     * <p/>
     * 客户端和服务端处于同一进程:直接返回服务端的Stub对象
     * <p/>
     * 客户端和服务端处于不同进程:返回封装好的Stub.proxy对象
     * 
     * @param obj
     * @return
     */
    public static IOperation asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        /**
         * 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.
         * 
         * 尝试在本地检索实现了该接口的Binder对象实例
         */
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (iin != null && (iin instanceof IOperation)) {
            return ((IOperation) iin);
        }
        /**
         * 假如本地检索返回为null,需要手动生成一个proxy代理类,并在其中通过transact() 方法去处理请求
         */
        return new IOperationStub.Proxy(obj);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    /**
     * 该方法运行在服务端,当客户端发起请求,会进入到该方法进行处理
     * <p/>
     * code:用于标识客户端请求调用的目标方法(即在IOperation中定义的方法标识符)
     * <p/>
     * data:当请求的目标方法含有参数时,该参数封装了请求的参数
     * <p/>
     * reply:当请求需要返回结果时,该参数封装了处理结果
     */
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        // 通过code区分不同的方法调用
        switch (code) {
        case INTERFACE_TRANSACTION:
            reply.writeString(DESCRIPTOR);
            return true;
        case TRANSACTION_getSum:
        /**
             * Read an IBinder interface token in the parcel at the current
             * {@link #dataPosition}. This is used to validate that the
             * marshalled transaction is intended for the target interface.
             */
            data.enforceInterface(DESCRIPTOR);
            // 通过data获取请求的参数
            double first = data.readDouble();
            double second = data.readDouble();
            double result = this.getSum(first, second);
            reply.writeNoException();
            // 将结果写入到reply中
            reply.writeDouble(result);
            return true;
        default:
            break;
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public double getSum(double fisrt, double second) throws RemoteException {
        return 0;
    }

    private static class Proxy implements IOperation {

        private IBinder mRemote;

        Proxy(IBinder mRemote) {
            this.mRemote = mRemote;
        }

        @Override
        public IBinder asBinder() {
            return mRemote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public double getSum(double fisrt, double second)
                throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            double result = 0;
            try {
                /**
                 * Store an IBinder interface token in the parcel at the current
                 * {@link #dataPosition}. This is used to validate that the
                 * marshalled transaction is intended for the target interface.
                 */
                data.writeInterfaceToken(DESCRIPTOR);
                // 将请求的参数写入到data中
                data.writeDouble(fisrt);
                data.writeDouble(second);
                // 调用transact()方法,发起跨进程请求,当前进程挂起
                mRemote.transact(TRANSACTION_getSum, data, reply, 0);
                // 从reply中获取返回的结果
                reply.readException();
                result = reply.readDouble();
            } catch (Exception e) {
            } finally {
                data.recycle();
                reply.recycle();
            }
            return result;
        }
    }

}

其中重要属性和方法说明如下:
1.DESCRIPTOR:
Binder 中唯一的标识,自动生成时用当前 Binder 类名表示。
static final int TRANSACTION_getSum = IBinder.FIRST_CALL_TRANSACTION + 0;
声明的整型的 id 用于标识在 transact 过程中客户端中请求的到底是哪个方法。

2.asInterface(android.os.IBinder object)
将服务端的 Binder 对象转换成客户端所需 AIDL 接口类型的对象,转换是区分进程的,客户端和服务器在同一进程中,返回的是服务端 Stub 本身,否则就返回系统封装后的 Stub.proxy 对象。

3.asBinder
返回当前的 Binder 对象

4.onTransact
本方法运行在服务器端中 Binder 线程池中,客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理:

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException

通过 code 确定客户端方法的方法,然后从 data 中取得参数 (如果存在参数的话),然后执行在服务端的目标方法,执行完成之后,向 reply 中写入返回值 (如果客户端中需要返回值的话)。当然 onTransact 方法是有返回值的,如果返回 false,那么客户端就会请求失败。

5.Proxy#getSum(double fisrt, double second)
在客户端中运行,当客户端远程调用此方法时,内部实现方法如下:先创建所需的输入型 Parcel 对象_data, 输出型 Parcel 对象 _replay 和返回值对象 List。先将需求参数写入_data 中,接着调用 transact 方法发起远程调用 (RPC) 请求,同时当前线程会挂起,然后服务端的 onTransact 方法会被调用,当 RPC 方法结束返回后,当前线程从挂起状态变成重新运行状态,并从_reply 中取出 RPC 过程的返回结果,最后返回 _reply 中的数据。其中的内容就是大致就是调用 Binder 对象的 transact() 方法,因为调用该方法是在线程中进行,所以注意需要加个 try。 基本图解如下:

Proxy图解

总体逻辑:
1 客户端调用 getSum() 方法
2 将该方法需要的传递的参数写入到 data 中
3 调用 transact() 方法发起跨进程请求
4 服务端的 onTransact() 的方法被调用,客户端的进程被挂起
5 服务端返回结果,从 reply 中取出结果,并返回

linkToDeath & unlinkToDeath

Binder 运行在服务端进程,如果服务进程由于某种原因异常终止,此时服务端的 Binder 链接断裂 (Binder 死亡),会导致我们的远程调用失败。更为关键的是,不知道 Binder 链接断裂,那么客户端的功能就会受影响。为了解决这个问题,Binder 中提供了两个配对的方法 lineToDeath 和 unlinkToDeath,通过 linktoDeath 可以 Binder 设置死亡代理,Binder 死亡时会受到死亡通知,此时可以重新发起连接请求从而恢复链接。

private IOperationServer iOperationServer;  

private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d(TAG,"binder died...");
            if (iOperationServer== null)  
                return;  
            iOperationServer.asBinder().unlinkToDeath(mDeathRecipient, 0);  
            iOperationServer= null;  
            // restart server or rebinding ServiceConnection
            bindService(intent, connection , BIND_AUTO_CREATE);  
        }
    } ;

在客户端绑定远程服务成功后,给 binder 设置死亡代理:

private IOperationServer iOperationServer;  
private MyServiceConnection connection = new MyServiceConnection();  

Intent intent = new Intent("com.hl.myservice");  
private class MyServiceConnection implements ServiceConnection {  
     @Override  
      public void onServiceConnected(ComponentName name, IBinder service) {  
           iOperationServer= IOperationServer .Stub.asInterface(service);  
           Log.d("aidl", "onServiceConnected:" + iOperationServer);  
             
            try {
                    Log.d("aidl", "onServiceConnected "+name.toShortString()+"  "+service.getInterfaceDescriptor());
                    service.linkToDeath(deathHandle, 0);
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }  
     @Override  
      public void onServiceDisconnected(ComponentName name) {}  
};  
bindService(intent, connection , BIND_AUTO_CREATE);  

基本上通过以上两个步骤,就会给 Binder 设置死亡代理,当 Binder 死亡的时候就会受到相应的通知了。

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

推荐阅读更多精彩内容