美文网首页
AIDL使用详解及原理(附加例子)

AIDL使用详解及原理(附加例子)

作者: 安仔夏天勤奋 | 来源:发表于2019-06-20 00:33 被阅读0次

    AIDL是什么?

    AIDL(Android Interface Define Language ——Android接口定义语言)是一种IPC通信方式,可以利用它来定义两个进程相互通信的接口,与 Service 进行跨应用、跨进程通信的一种机制,高效、灵活,使用方便。它的本质是C/S架构的,需要一个服务器端,一个客户端。既然一个服务器端一个客户端,那么开始撸码实现。

    开始AIDL的实现

    服务端步骤如下:

    1. 创建一个安卓工程
    2. 生成aidl文件,在aidl文件中写入接口方法
    3. 实现接口,并向客户端放开接口

    客户端步骤如下:

    1. 创建一个安卓工程
    2. 新建aidl目录
    3. 绑定服务,调试服务端接口

    创建一个安卓工程(服务端)

       首先在Android Studio中创建一个Andorid工程,作为aidl的服务端,包名为com.lu.aidlserver。

    生成aidl文件

       利用AS自带的插件,在src目录中右键生成一个aidl文件(new——>AIDL——>AIDL File——>输入文件名) 。


    从图中可以看出生成了一个IMyAidlInterface.aidl文件。打开文件看到如下内容。

    package com.lu.aidlserver;
    // Declare any non-default types here with import statements
    
    interface IMyAidlInterface {
        /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             * 从注解可以看得出 basicTypes()方法中的参数都是 aidl定义可以用的参数类型
             */
        //    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        //            double aDouble, String aString);
    
               String getName();//获取一个名称
               int countSum(int a ,int b);//计算a+b
    }
    

    看到aidl的语法跟java是一样的,也很好理解,声明了一个接口,里面定义了aidl服务器端暴露给客户端调用的方法,方法的参数是一些可支持的数据类型,还能支持别的数据类型?这里先放一放,下面会讲到。从注解可以看得出 basicTypes()方法中的参数都是 aidl定义可以用的参数类型。当然可以根据自己的需求定义接口。在里面的就定义了两个接口方法。完成这部分操作之后还没有结束,我们需要手动编译程序,生成aidl对应的Java代码。


    通过实现接口,并向客户端放开接口

    /**
     * Author: 安仔夏天勤奋
     * Date: 2019/6/18
     * Desc: 服务
     */
    public class MyAidlService extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new MyBinder();//返回给客户端的Binder
        }
    
        //获取aidl的Binder
        private class MyBinder extends IMyAidlInterface.Stub{
            @Override
            public String getName() throws RemoteException {
                return "我是AIDL";
            }
    
            @Override
            public int countSum(int a, int b) throws RemoteException {
                return a+b;
            }
        }
    }
    

    创建一个安卓工程(客户端)

      客户端和服务端一样的,就不多创建一个工程了,与服务端一个工程下,创建一个客户端module,包名为com.lu.aidlclient


    客户端新建aidl目录

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

    绑定服务,调试服务端接口

      在MainActivity中调试服务端的接口打打印。

    public class MainActivity extends AppCompatActivity {
    
        private com.lu.aidlserver.IMyAidlInterface binder = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //android5.0之前都可以通过配置在manifest里service 的action来启动。android5.0之后都必须使用显示intent。
    //        //启动aidl 的服务
    //        Intent intent = new Intent();
    //        intent.setComponent(new ComponentName("com.lu.aidlserver",
    //                "com.lu.aidlserver.MyAidlService"));
    //        //第一个参数是包名,第二个是类名
    
            Intent service = new Intent("com.lu.aidlserver.MyAidlService");
            // 绑定AIDL 隐式
            bindService(service, connection, BIND_AUTO_CREATE );
        }
    
        // 创建远程调用对象
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 从远程service中获得AIDL实例化对象
                //这里不能强制转换,因为虽然类的内容是一样的,但是却不是同一个。(每个app能访问到的毕竟是自己的类)
                binder = com.lu.aidlserver.IMyAidlInterface.Stub.asInterface(service);
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
                binder = null;
            }
        };
    
        public void onClickTest(View v){
            try {
    
                Log.e("lu","Name : "+binder == null ? "null" :
                        binder.getName()+"      计算a+b="+binder.countSum(9,1));
                }
    
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    
    运行结果
    06-19 00:44:32.389 4314-4314/com.lu.aidlclient E/lu: 我是AIDL      计算a+b=10
    

    打印出来结果,说明远程调用成功了,aidl的整个流程就走完了。做到这一步起码你对AIDL有一定的认知了。下面讲一下上面提到的的问题,除了AS生成aidl文件给出的数据类型还能用其他数据类型作解答。

    AIDL可使用的参数类型

      官方的数据类型就是只有这几种了。

    void basicTypes(int anInt, long aLong,boolean aBoolean, float aFloat, double aDouble, String aString);
    

      我们知道Java有8中基本数据类型,分别为byte、char、short、int、long、float、double、boolean,这些类型是否都能作为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的。更具体还没有查阅资料,有兴趣的可以自行查阅。官方文档

    AIDL引用数据类型

    引用数据类型根据官方文档介绍,可以使用String、CharSequence、List、Map。这样的话我们也可以使用自定义数据类型。

    自定义数据类型

      话不多说,代码先行。在服务端创建一个学生类,并添加一些属性。


    QQ图片20190619215714.png
    public class Student implements Parcelable {
        private int age;
        private String name;
        private String sex;
        public Student(int age, String name, String sex) {
            this.age = age;
            this.name = name;
            this.sex = sex;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        @Override
        public String toString() {
            return "\n名字="+this.name+"\t\t性别="+this.sex+"\t\t年龄="+this.age;
        }
    
        /**
         * Parcelable对数据进行分解/编组的时候必须使用相同的顺序,
         * 字段以什么顺序分解的,编组时就以什么顺序读取数据,不然会有问题
         * @param in Parcel
         */
        protected Student(Parcel in) {
            this.age = in.readInt();
            this.name = in.readString();
            this.sex = in.readString();
        }
    
        public static final Creator<Student> CREATOR = new Creator<Student>() {
            @Override
            public Student createFromParcel(Parcel in) {
                return new Student(in);
            }
    
            @Override
            public Student[] newArray(int size) {
                return new Student[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(age);
            dest.writeString(name);
            dest.writeString(sex);
        }
    }
    

    自定义数据类型,用于进程间通信的话,用Parcelable接口实现,Parcelable是类似于Java中的Serializable,Android中定义了Parcelable,用于进程间数据传递,对传输数据进行分解,编组的工作,相对于Serializable,Parcelable对于进程间通信更加高效。创建完Student实体后,还需要创建一个aidl文件,来定义一下我们的Student,否则Student在aidl中无法识别,在服务端的aidl文件中创建一个Student的aidl文件。



    Student.aidl文件就两个话。

    // Student.aidl
    package com.lu.aidlserver;
    parcelable Student;
    

    在之前的服务器端IMyAidlInterface.aidl文件中添加addStudent方法。

    // IMyAidlInterface.aidl
    package com.lu.aidlserver;
    // Declare any non-default types here with import statements
    import com.lu.aidlserver.Student;
    
    interface IMyAidlInterface {
        /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             * 从注解可以看得出 basicTypes()方法中的参数都是 aidl定义可以用的参数类型
             */
        //    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        //            double aDouble, String aString);
    
               String getName();//获取一个名称
               int countSum(int a ,int b);//计算a+b
               List<Student> addStudent(in Student student);
    }
    

    在MyAidlService中实现添加的addStudent()。

    public class MyAidlService extends Service {
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new MyBinder();//返回给客户端的Binder
        }
    
        //获取aidl的Binder
        private class MyBinder extends IMyAidlInterface.Stub{
            @Override
            public String getName() throws RemoteException {
                return "我是AIDL";
            }
    
            @Override
            public int countSum(int a, int b) throws RemoteException {
                return a+b;
            }
    
            @Override
            public List<Student> addStudent(Student student) throws RemoteException {
                List<Student> students=new ArrayList<>();
                students.add(student);
                return students;
            }
        }
    }
    

    服务端一切都准备好了,记得重新手动编译。下面进行客户端操作。

    将服务端的两个aidl文件复制到客户端,包结构必须一致,aidl文件发生变化不要忘记重新编译代码。


    接着将Student实体也复制到客户端,并且包结构一致。

    最后在客户端的MainActivity中的点击事件方法中调用服务端的addStudent()。

    public void onClickTest(View v){
            try {
    
                Log.e("lu","Name : "+binder == null ? "null" :
                        binder.getName()+"      计算a+b="+binder.countSum(9,1));
    
                if(binder!=null){
                    List<Student> students = new ArrayList<>();
                    students.addAll(binder.addStudent(new Student(18,"小卢","男")));
                    students.addAll(binder.addStudent(new Student(19,"张三","男")));
                    students.addAll(binder.addStudent(new Student(20,"李四","男")));
                    Log.e("lu","List<Student> : "+students.toString());
                }
    
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    

    运行服务端与客户端App,点击测试,输出以下日志,说明调用成功。

    06-19 22:29:04.609 5323-5323/com.lu.aidlclient E/lu: 我是AIDL      计算a+b=10
    06-19 22:29:04.610 5323-5323/com.lu.aidlclient E/lu: List<Student> : [
        名字=小卢       性别=男        年龄=18, 
        名字=张三       性别=男        年龄=19, 
        名字=李四       性别=男        年龄=20]
    

    AIDL的demo搭建完成并能正常运行。下面就聊聊AIDL原理。

    AIDL原理

      在上面的服务端的例子手动编译时,在路径为aidlserver\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\com\lu\aidlserver\目录下生成了一个IMyAidlInterface.java文件


    要了解aidl原理,我们需要看一下根据aidl生成的对应的java代码(IMyAidlInterface.java),一段一段进行分析。

    package com.lu.aidlserver;
    public interface IMyAidlInterface extends android.os.IInterface{
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.lu.aidlserver.IMyAidlInterface
    {...}
    public java.lang.String getName() throws android.os.RemoteException;
    public int countSum(int a, int b) throws android.os.RemoteException;
    //计算a+b
    public java.util.List<com.lu.aidlserver.Student> addStudent(com.lu.aidlserver.Student student) throws android.os.RemoteException;
    }
    

    从生成的代码结构来看很简单,一个静态抽象类Stub,以及aidl中定义的方法。我们也不难看出Stub就是AIDL的核心代码,Stub类的结构如下图。


    Stub的目录结构也不复杂,Stub继承系统中的Binder并实现了我们定义的aidl接口。一个构造函数,一个asInterface方法,一个asBinder方法,一个onTransact方法,一个Proxy代理类,这边Proxy与Stub同时实现了我们定义的aidl,且Proxy中实现了我们在aidl中定义的getName、countSum、addStudent方法,下面三个int为告诉系统的方法Id。

    在客户端,我们绑定服务的时候通过Stub.asInterface()回去aidl对象,代码如下:

    // 创建远程调用对象
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 从远程service中获得AIDL实例化对象
                //这里不能强制转换,因为虽然类的内容是一样的,但是却不是同一个。(每个app能访问到的毕竟是自己的类)
                binder = com.lu.aidlserver.IMyAidlInterface.Stub.asInterface(service);
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
                binder = null;
            }
        };
    

    也不难发现,客户端获取到的是一个远程服务的代理Stub.Proxy。查看asInterface源码。

    public static com.lu.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.lu.aidlserver.IMyAidlInterface))) {
    return ((com.lu.aidlserver.IMyAidlInterface)iin);
    }
    return new com.lu.aidlserver.IMyAidlInterface.Stub.Proxy(obj);
    }
    

    客户端调用getName、countSum、addStudent方法其实调用的时Stub.Proxy中实现的getName、countSum、addStudent,在Stub.Proxy中可以看到这几句核心代码。

    //getName()
    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
    //countSum()
    mRemote.transact(Stub.TRANSACTION_countSum, _data, _reply, 0);
    //addStudent()
    mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
    

    mRemote其实就是IMyAidlInterface.Stub,随意mRemote.transact传递到了IMyAidlInterface.Stub.OnTransact, onTransact中执行了getName、countSum、addStudent,回调到了我们在服务端定义MyAidlService中声明MyBidner时重写的getName、countSum、addStudent,这就是AIDL的整个流程。

    private class MyBinder extends IMyAidlInterface.Stub{
            @Override
            public String getName() throws RemoteException {
                return "我是AIDL";
            }
    
            @Override
            public int countSum(int a, int b) throws RemoteException {
                return a+b;
            }
    
            @Override
            public List<Student> addStudent(Student student) throws RemoteException {
                List<Student> students=new ArrayList<>();
                students.add(student);
                return students;
            }
        }
    

    总结

    • 服务端编写完时或AIDL文件发生变化不要忘记重新编译代码
    • 客户端复制AIDL文件时,一定要注意包名一致
    • 自定义数据类型时,Parcelable对数据进行分解/编组的时候必须使用相同的顺序
    • 客户端android5.0之前都可以通过配置在manifest里service 的action来启动。android5.0之后都必须使用显示intent。
    AIDL原理流程图

    AIDLdemo代码

    相关文章

      网友评论

          本文标题:AIDL使用详解及原理(附加例子)

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