android Binder与AIDL

来源:互联网 发布:阿根廷04男篮名单 数据 编辑:程序博客网 时间:2024/05/20 11:50
  • Android Binder 全解析(1) – 概述
  • Android Binder 全解析(2) – 设计详解
  • Android Binder 全解析(3) – AIDL原理剖析
  • Android中AIDL的使用详解

binder是一种进程间通信方式,如果要了解他是如何运作的,我可能会看吐,就看看如何来使用它吧
这个例子中给App中的一个Service使用android:process=":remote"属性,让他运行在独立的进程中,这样就省去了我要创建两个App来完成进程间通信的麻烦
首先贴一张用到的文件的图,在一个个来讲

AIDL相关文件

创建接口 IMyAidlInterface

首先需要创建一个接口,服务端实现这个接口,客户端就可以通过这个接口来调用服务端的方法了
这个接口当然也是使用Java来写,不过为了减轻程序员的工作,可以使用AIDL来编写,然后自动生成同名的Java文件
编写完AIDL文件后。对应的Java文件不会生成,需要【clean project】或者是【rebuild project】

AIDL

直接新建AIDL文件即可,AS会自动为它创建文件夹
我例子中的AIDL文件如下

package e.xiejiaming.server;import e.xiejiaming.server.Data;interface IMyAidlInterface {    //这里解释一下例子中的三个方法是要干什么    //把服务端的data设置为客户端传入的data对象    void setData(in Data data);    //获取data对象    Data getData();    //修改data对象包含的int x的值    void changeData(out Data data);}

basicTypes方法的作用仅仅说明AIDL文件中有哪些基本类型可以被直接使用,这个方法可以直接删掉

如果需要使用Parcelable类型的对象,除了要创建Parcelable的Java实现类(例子中是Data.java)外,还需要创建同名的aidl文件(Data.aidl),并且这两个文件要有相同的包名,然后在aidl文件中声明这个Java类
Data.aidl文件如下

package e.xiejiaming.server;parcelable Data;

然后要在创建的aidl文件(IMyAidlInterface )中导入它,虽然他们在相同的包内
AS创建的aidl文件是一个接口,当创建Parcelable实现类的同名aidl文件时,系统会提示【接口名字重复】,因为已经存在相同名字的Java类了(Data.java和Data.aidl名字相同)
对此,我的做法是随便取个名字,再删除aidl文件中关于接口的的定义,再重命名

例子中定义的Parcelable类——Data.java里只有一个int型字段,名为x

aidl的接口中不能有字段,只能声明方法

aidl中生命的方法的参数必须有(in,或out,或inout),所有基本类型只能是in,这三个关键字文章后面再讲

不能修改aidl文件生成的对应的Java文件,因为编译时会自动生成,所以所有修改都是无效的

生成的AIDL对应的Java文件

编写程序是用到就是这个类,生成这个文件后aidl就没什么用了,当然也不能删了它,因为每次编译时都需要用aidl来重新生成这个Java文件

例子中的IMyAidlInterface .java的简单结构
先简单说一下:
IMyAidlInterface有三个部分
1. 静态抽象内部类Stub,扩展自IMyAidlInterface,但不实现IMyAidlInterface中定义的方法,远程服务需要实现这个类
2. Stub的静态内部类Proxy,继承自IMyAidlInterface,并实现它定义的方法,客户端获得的IMyAidlInterface接口的具体实现对象,proxy持有stub对象
3. 声明的需要实现的方法

从这里可以知道客户端获得的IMyAidlInterface对象不是远程实现的IMyAidlInterface的类型,这两个是不同的类型,只不过都继承自同一个接口
客户端的IMyAidlInterface的作用是与远程服务传输数据,以及间接调用远程IMyAidlInterface对象的方法

public interface IMyAidlInterface extends android.os.IInterface{    //远程服务需要实现这个类,它继承自binder    public static abstract class Stub extends android.os.Binder implements e.xiejiaming.server.IMyAidlInterface {        public static e.xiejiaming.server.IMyAidlInterface asInterface(android.os.IBinder obj) {            //这里返回IMyAidlInterface.Proxy对象,提供给客户端调用        }        //返回Stub对象        public android.os.IBinder asBinder() {            return this;        }        //处理客户端的远程请求        public boolean onTransact(){            switch (code) {                case INTERFACE_TRANSACTION:                 case TRANSACTION_basicTypes:                case TRANSACTION_setData:                //举个例子,客户端调用IMyAidlInterface对象的getData方法                //在这个case里完成数据传输,和调用自身的getData方法的实现                case TRANSACTION_getData:                case TRANSACTION_changeData:            }        }    }    private static class Proxy implements e.xiejiaming.server.IMyAidlInterface{        //返回proxy持有的stub对象        Ibinder asBinder(){return mRemote;}        void setData(in Data data){};        Data getData(){};        void changeData(out Data data){};    }    void setData(in Data data);    Data getData();    void changeData(out Data data);}

IMyAidlInterface接口的使用

上面已经定义了这个接口,并生成了对应的Java文件,下面给出服务端实现这个接口,客户端使用这个接口的简单代码

//服务端public class AidlService extends Service {    @Override    public IBinder onBind(Intent intent) {        return new AidlInterface();    }    //这个内部类是IMyAidlInterface的实现    class AidlInterface extends IMyAidlInterface.Stub{...}}
//客户端,这里是一个Activity中IMyAidlInterface iMyAidlInterface;ServiceConnection connection = new ServiceConnection() {    @Override    public void onServiceConnected(ComponentName name, IBinder service) {        //获得iMyAidlInterface对象        //从源码中看,这里的service就是IMyAidlInterface.Stub类型的对象        //所以在构建IMyAidlInterface.Proxy对象时,让IMyAidlInterface.Proxy对象持有IMyAidlInterface.Stub对象,这样客户端就能调用远程的方法了        iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);        try {        //因为是异步的,所以在bindService后直接使用iMyAidlInterface的话,iMyAidlInterface可能是null,所以在这里使用            Data data = new Data(10);            Log.d(TAG, "onCreate: not null");            Log.d(TAG, "onCreate: getData " + iMyAidlInterface.getData().getX());            iMyAidlInterface.setData(data);            Log.d(TAG, "onCreate: setData " + iMyAidlInterface.getData().getX());            iMyAidlInterface.changeData(data);            Log.d(TAG, "onCreate: changeData " + data().getX());        }catch (RemoteException e){            e.printStackTrace();        }    }    @Override    public void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    //service必须显示启动    Intent intent = new Intent(this,AidlService.class);    //最后一个参数必须是Service.BIND_AUTO_CREATE    bindService(intent,connection, Service.BIND_AUTO_CREATE);}

走一个binder的调用流程

以changeData方法为例,该方法是希望服务端设置传入的Data对象里面的x字段的值为100
序列化是对象的保存与读取,所以服务端与客户端持有的其实是同一个类型的两个不同的对象,而不是同一个对象的两个句柄,所以最重要的是保证客户端与服务端获得的数据的一致性
而这里我要做的是在服务端改变data对象的内部的x字段,并把它写回到客户端的data对象中

//客户端代码//传入客户端持有的Data对象,让服务端设置它的x的值为100iMyAidlInterface.changeData(data);//值已经被改变,且写回客户端,此时客户端和服务端的Data.x都是100,所以直接使用客户端的data对象即可//不需要重新获取服务端的对象 iMyAidlInterface.getData()Log.d(TAG, "onCreate: changeData " + data.getX());

在aidl中声明方法时,方法的参数必须是有in或out或inout关键字,这三个关键字的含义如下
1. in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动
2. out 的话表现为服务端将会接收到那个对象的参数为空的对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动
3. inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

我的changeData方法的参数用的是out,来看看out带来了什么
首先客户端的iMyAidlInterface对象的具体类型是IMyAidlInterface.Proxy,IMyAidlInterface.Proxy的changeData方法如下

@Overridepublic void changeData(e.xiejiaming.server.Data data) throws android.os.RemoteException {    //保存传递给远程服务的数据    android.os.Parcel _data = android.os.Parcel.obtain();    //保存远程服务返回的数据    android.os.Parcel _reply = android.os.Parcel.obtain();    try {        _data.writeInterfaceToken(DESCRIPTOR);        //调用远程IMyAidlInterface对象(IMyAidlInterface.Stub类型的)的transcat方法        mRemote.transact(Stub.TRANSACTION_changeData, _data, _reply, 0);        _reply.readException();        if ((0 != _reply.readInt())) {            //反序列化_reply,并把读取的值写到data中            //那就可以猜到先前的transcat方法一定把数据序列化到了_reply中            data.readFromParcel(_reply);        }    } finally {        _reply.recycle();        _data.recycle();    }}

从上面的代码中可以看到:客户端把_Data传递给远程,但是_Data中并没有保存Data的值
而使用in关键字的参数的方法setData,有这行代码data.writeToParcel(_data, 0);,它是把数据写入到data后再传给远程的

来看远程的transcat方法中的changeData case

case TRANSACTION_changeData: {    data.enforceInterface(DESCRIPTOR);    //它根本没理会客户端传入的`_data`,直接创建了一个`_arg0`    //所以说out关键字,服务端不接受客户端的输入,直接new一个新的,所以这个Parcelable类型Data必须有空构造器,不然这里就出错了    e.xiejiaming.server.Data _arg0;    _arg0 = new e.xiejiaming.server.Data();    //调用自身的changData方法    this.changeData(_arg0);    reply.writeNoException();    if ((_arg0 != null)) {        reply.writeInt(1);        //写回数据给客户端        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);    } else {        reply.writeInt(0);    }    return true;}

这里有一个坑
要把数据写回到reply,所以调用了_arg0(Data型)的writeToParcel方法,但是这个方法是Parcelable中没有的,所以需要在Data中手动定义这个方法,看它要怎么调用,那我就怎么写,如下

public void readFromParcel(Parcel in){    x=in.readInt();}

服务端对于changeData的实现

@Overridepublic void changeData(Data data) throws RemoteException {    //data变量中的数据会写回给客户端,所以这里改变它的值    data.setX(100);    //同时改变服务端的data,保证服务端的data与客户端的data的数据是一致的    //如果希望这样的话    this.data = data;}
原创粉丝点击