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

2019-06-20  本文已影响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原理流程图

AIDLdemo代码

上一篇下一篇

猜你喜欢

热点阅读