AIDL使用详解及原理

我们都知道,在Android中,系统会为每个进程分配对应的内存空间,这部分内存是彼此间相互独立,不可直接交互的,这样的设计是处于安全性以及系统稳定性方面考虑的,比如当我们的App奔溃时,不至于导致其他App无法运行,甚至死机等情况。那么,Android中是否就无法实现进程间通信呢?答案当然是否定的。Android中进程通信的方式有很多,比如AIDL就可以实现这样子的需求。


进程间通信.png

1.AIDL

​ AIDL(Android Interface Define Language)是一种IPC通信方式,我们可以利用它来定义两个进程相互通信的接口。他是基于Service实现的一种线程间通信机制。它的本质是C/S架构的,需要一个服务器端,一个客户端。

2.AIDL的使用

2.1创建aidl

​ 首先我们在AndroidStudio中创建一个Andorid工程,

​ 随后添加一个module,作为aidl的服务端

​ 在aidlserver中创建aild目录, 同时创建一个aidl文件

aidlserver目录.png
// IMyAidlInterface.aidl
package com.yunzhou.aidlserver;

// Declare any non-default types here with import statements

interface IMyAidlInterface {

    /**
    * 自己添加的方法
    */
    int add(int value1, int value2);
}

​ 这边可以看到aidl的语法跟JAVA是一样的,声明了一个接口,里面定义了aidl服务器端暴露给客户端调用的方法。

​ 完成这部分操作之后还没有结束,我们需要手动编译程序,生成aidl对应的Java代码

aidl生成过程.png
2.2实现接口,并向客户端放开接口
public class MyAidlService extends Service {
    public MyAidlService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return iBinder;
    }

    private IBinder iBinder = new IMyAidlInterface.Stub(){
        @Override
        public int add(int value1, int value2) throws RemoteException {
            return value1 + value2;
        }
    };
}

​ 我们创建了一个service,并在service内部声明了一个IBinder对象,它是一个匿名实现的IMyAidlInterface.Stub的实例(这部分我们后面讲),同时我们在发现IMyAidlInterface.Stub实例实现了add方法,这个方法正是我们在aidl中声明的供客户端调用的方法。

2.3客户端调用aidl

​ 首先在客户端跟服务器一样,新建aidl目录,将服务器端的aidl拷贝到客户端,这边特别要注意,拷贝后的客户端的aidl文件包目录必须与服务器端保持一致,拷贝完后同样时编译工程,让客户端也生成对应的java文件

客户端使用aidl_目录.png

​ 其次就是在Activity的onCreate中绑定服务


 private ServiceConnection connection = new ServiceConnection() {
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           //绑定服务成功回调
           aidl = IMyAidlInterface.Stub.asInterface(service);
       }

       @Override
       public void onServiceDisconnected(ComponentName name) {
           //服务断开时回调
           aidl = null;
       }
   };

@Override
protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      //do something
      bindService();
}

private void bindService(){
        Intent intent = new Intent();
        //Android 5.0开始,启动服务必须使用显示的,不能用隐式的
        intent.setComponent(new ComponentName("com.yunzhou.aidlserver", "com.yunzhou.aidlserver.MyAidlService"));
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

​ 绑定完服务,就是进行调用了,具体的页面细节这边不做展示,就是在Activity中放了一个按钮,点击按钮进行远程调用

int result = aidl.add(12, 12);
Log.e(TAG, "远程回调结果:" + result);
远程调用结果.png

​ 可以看到,logcat打印出来结果,说明远程调用成功了,至此aidl的整个流程就走完了。

3.AIDL可使用的参数类型

3.1基本数据类型

我们都知道Java有8中基本数据类型,分别为byte,char,short,int,long,float,double,boolean,那这8中数据类型是否都能作为aidl的参数进行传递呢?我们可以在aild中尝试下,并编译,看看有没有错

void basicTypes(byte aByte, char aChar, short aShort, int anInt, long aLong, float aFloat,
            double aDouble, boolean aBoolean);

发现这样子定义,无法成功编译,经过筛查发现aidl并不能支持short基本数据类型,至于为什么呢,可以看一看Android中的Parcel,Parcel是不支持short的,这应该是考虑到兼容性问题吧。

所以基本数据类型支持:byte,char,int,long,float,double,boolean

3.2引用数据类型

引用数据类型根据官方介绍,可以使用String,CharSequence,List,Map,当然,我们也可以使用自定义数据类型。

3.3自定义数据类型

自定义数据类型,用于进程间通信的话,必须实现Parcelable接口,Parcelable是类似于Java中的Serializable,Android中定义了Parcelable,用于进程间数据传递,对传输数据进行分解,编组的工作,相对于Serializable,他对于进程间通信更加高效。

我们来看下下面的例子

public class User implements Parcelable {

    private int id;
    private String name;

    public User() {
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public User(Parcel in){
      //注意顺序!!!注意顺序!!!注意顺序!!!
        this.id = in.readInt();
        this.name = in.readString();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      //注意顺序!!!注意顺序!!!注意顺序!!!
        dest.writeInt(id);
        dest.writeString(name);
    }

    public static final  Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

​ 这边我们定义了一个User类,实现了Parcelable接口,大致的类结构就是这个样子的,需要注意的一点是,Parcelable对数据进行分解/编组的时候必须使用相同的顺序,字段以什么顺序分解的,编组时就以什么顺序读取数据,不然会有问题!

​ 创建完实体后,我们需要创建一个aidl文件,来定义一下我们的User,否则User在aidl中无法识别

// IMyAidlInterface.aidl
package com.yunzhou.aidlserver;

parcelable User;

​ 并在之前的服务器端aidl中新增方法

interface IMyAidlInterface {
    int add(int value1, int value2);
    List<User> addUser(in User user);
}

​ 在service中实现新增的addUser方法

private ArrayList users;

@Override
public IBinder onBind(Intent intent) {
  users = new ArrayList<User>();
  return iBinder;
}

private IBinder iBinder = new IMyAidlInterface.Stub(){
  @Override
  public int add(int value1, int value2) throws RemoteException {
    return value1 + value2;
  }

  @Override
  public List<User> addUser(User user) throws RemoteException {
    users.add(user);
    return users;
  }
};

​ 此时,server端的目录就够如下

parcelable_服务端结构.png

​ 服务器端一切准备就绪后,我们对客户端进行操作,首先,我们将服务端的两个aidl文件复制到客户端,包结构必须一致,aidl文件发生变化不要忘记重新编译代码

​ 然后,将User实体也复制到客户端,并且包结构一致。

​ 最后,在客户端进行addUser的操作(这边只是添加了一个按钮,每点击一次就调用一次addUser)

try {
  ArrayList<User> users = (ArrayList<User>) aidl.addUser(new User(12, "demaxiya"));
  Log.e(TAG, "远程回调结果:" + users.toString());
} catch (RemoteException e) {
  e.printStackTrace();
}

​ 此时客户端的目录结构如下:


parcelable_客户端结构.png

​ 运行服务端与客户端App,点击addUser,输出日下日志,说明调用成功


parcelable_远程调用结果.png

4.AIDL原理

​ 要了解aidl原理,我们需要看一下根据aidl生成的对应的java代码了,

public interface IMyAidlInterface extends android.os.IInterface{
    public static abstract class Stub extends android.os.Binder implements com.yunzhou.aidlserver.IMyAidlInterface{...}
    public int add(int value1, int value2) throws android.os.RemoteException;
    public java.util.List<com.yunzhou.aidlserver.User> addUser(com.yunzhou.aidlserver.User user) throws android.os.RemoteException;
}

​ 我们可以看到,生成的代码结构很简单,一个静态抽象类Stub,以及aidl中定义的方法,其中Stub肯定时核心,我们深入阅读

Stub.png

​ Stub的目录结构也不复杂,一个构造函数,一个asInterface方法,一个asBinder方法,一个onTransact方法,一个Proxy代理类,这边Proxy与Stub同时实现了我们定义的aidl,且Proxy中实现了我们在aidl中定义的add/addUser方法。下面两个int为告诉系统的方法Id

​ 在客户端,我们绑定服务的时候通过Stub.asInterface()回去aidl对象,查看asInterface源码,我们不难发现,客户端获取到的其实时Stub.Proxy,一个远程服务的代理。

//客户端获取aidl
aidl = IMyAidlInterface.Stub.asInterface(service);

//Stub的asInterface
public static com.yunzhou.aidlserver.IMyAidlInterface asInterface(android.os.IBinder obj) {
  if ((obj==null)) {
    return null;
  }
  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  if (((iin!=null)&&(iin instanceof com.yunzhou.aidlserver.IMyAidlInterface))) {
    return ((com.yunzhou.aidlserver.IMyAidlInterface)iin);
  }
  return new com.yunzhou.aidlserver.IMyAidlInterface.Stub.Proxy(obj);
}

​ 所以客户端调用add/addUser方法其实调用的时Stub.Proxy中实现的add/addUser,在Stub.Proxy中我们可以看到一句核心代码

//add
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

//addUser
mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);

​ 追溯源头,mRemote其实就是IMyAidlInterface.Stub,随意mRemote.transact传递到了IMyAidlInterface.Stub.OnTransact, onTransact中执行了add/addUser,回调到了我们在服务端定义Service中声明IBidner时重写的add/addUser,这就是AIDL的整个流程。

private IBinder iBinder = new IMyAidlInterface.Stub(){
  @Override
  public int add(int value1, int value2) throws RemoteException {
    return value1 + value2;
  }

  @Override
  public List<User> addUser(User user) throws RemoteException {
    users.add(user);
    return users;
  }
};

​ 下面用一张图来总结aidl原理,这边特别感谢imooc

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

推荐阅读更多精彩内容