AIDL使用详解及原理

作者: junjunxx | 来源:发表于2017-09-21 00:05 被阅读301次

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


    进程间通信.png

    1.AIDL

    ​ AIDL(Android Interface Define Language)是一种IPC通信方式,我们可以利用它来定义两个进程相互通信的接口。他是基于Service实现的一种线程间通信机制。它的本质是B/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

    相关文章

      网友评论

      • NN又回来了:在最后一步,在点击事件中引用方法的时候报错了,这个有遇到吗?“ java.lang.NullPointerException: Attempt to invoke interface method 'int coco.xx.aidlserver.IMyAidlInterface.addd(int, int, int)' on a null object reference”
        在第一次的时候已经成功了,然后我就是在接口中加了一个方法,然后就报错了!
        junjunxx:@pull2car 你把具体代码贴出来看看呢?
        NN又回来了:@junjunxx 我就是这样操作的咯,我看了一下,自动生成的aidl的文件了,这已经有了呢;就是在加这个方法的工程中,我编译了两次的咯
        junjunxx:你是加了一个addd(int,int,int)的方法嘛?加完这个方法服务端需要重新编译,生成aidl对应的新的java文件,重写服务端的service,然后将aidl复制到客户端,同样进行重新编译,再进行调用试试,应该不会有问题的:blush:

      本文标题:AIDL使用详解及原理

      本文链接:https://www.haomeiwen.com/subject/jlbasxtx.html