阅读 1015

AIDL浅析

官方说明直达

何时使用AIDL

在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的primitives,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。

简单地说,在android上跨进程无法直接交互数据信息,需要经过一系列转换,让android底层来实现传输。

注:只有在跨进程且服务中处理多线程才需要用到AIDL;如果不需要跨进程只是IPC,用Binder就可以;如果不需要多线程用message就好了。

方式 条件
AIDL 需要IPC 跨进程,多线程
Binder 只有IPC 跨进程,多线程
Messager 只有IPC 单进程没有多线程

基本语法&简单实例

按照google官方的介绍,要使用AIDL需要三个步骤:

  1. 创建 .aidl 文件
  2. 实现接口
  3. 向客户端公开该接口

1.创建 .aidl 文件

新建一个项目AidlTestRemote
AndroidStudio中的目录结构
-main
|_aidl
|_java

示例代码: 建立的 IRemoteService.aidl 只含有一个add()方法

// IRemoteService.aidl
package com.example.android;

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

/** 此处新建的是一个service接口 */
interface IRemoteService {
    /** 请求这个服务的进程ID,用它来搞事情. 
    * int getPid();
    */
    
    /** 这里可以使用一些基本类型作为参数的抽象方法
     *  并通过AIDL返回
     *
     * void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
     */       
    int add(int num1 , int num2);
}
复制代码

2.服务端实现接口

在写好.aidl文件后,在 androidstudio 上 make project 项目,如下图均可以


点击后androidstudio会在如下目录自动生成与aidl同名的java文件。
在生成的 IRemoteService.java 中会有一个.Stub的子类用于实现父类的所有方法,包含了在系统底层的交互。 这里的 IRemoteService.java 自动生成,不同的aidl的抽象实现一致,也禁止修改。
This file is auto-generated. DO NOT MODIFY.
折叠代码可以看到, IRemoteService.java 中实际是由一个Stub子类和之前定义的add方法组成。若要具体实现IRemoteService(此示例中为add(int num1, int num2)方法),则继承已生成的Binder接口并实现从.aidl文件继承的方法。
那么现在建立一个 service 来继承binder并实现add(int num1, int num2)方法。

在新建的 RemoteCalculator service 中使用匿名实例 IRemoteService.Stub() 的接口并具体实现了IRemoteService的add(int num1, int num2)方法

现在 RemoteCalculator service 中的 mBinder 是Stub 类的一个实例用于定义服务的 RPC 接口,已经实现的add(int num1, int num2)方法。 当其他客户端绑定到 RemoteCalculator service 时,会通过 onBind 方法返回得到mBinder,此时通过 IRemoteService.aidl 来发送add()的参数就可以获得RemoteCalculator service计算出的结果并返回给客户端。

3.向客户端公开该接口

上述完成的服务端的建立,简单的说,就是先用aidl建一个通道,声明好客户端可以发送什么,服务端可以接收并返回什么。服务端通过service来接收客户端通过aidl发送来的数据,经过处理后,再通过aidl返回给客户端。实现了双向的通讯。

既然服务端用service来处理客户端的数据,自然客户端要绑定上服务端的service。
为方便起见此时在项目中,新建一个 Module 名为aidltestclient。 将服务端的 IRemoteService.aidl 复制过来,要注意目录结构完全一致

复制后,再点击 aidltestclient 的 make project,AS会在生成 IRemoteService.java,这与之前服务端完全一致。

在客户端 aidltestclient 中,需要做的是:
1.绑定到服务端的 RemoteCalculator service,调用它的 add(int num1, int num2)方法
2.调用 add(int num1, int num2) 方法需要客服端传入两个int参数,传参的操作需要通过IRemoteService.aidl实现
3.客户端完成操作后要解绑service(易忽视)

4.绑定到 RemoteCalculator service

在onCreate方法中执行bindServie()方法:

private void bindServie() {
        Intent intent = new Intent();
        //android新版本上需要显示发起intent,ComponentName需要传入pkg name,cls name。注意cls name需要加上包名
        intent.setComponent(new ComponentName("com.wind.fitz.aidltestremote","com.wind.fitz.aidltestremote.RemoteCalculator"));
        //(Intent service, ServiceConnection conn,int flags)
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
    }
复制代码

bindService的参数说明:intent不解释;ServiceConnection 用来接收the service object when it is created and be told if it dies and restarts。简单的说,ServiceConnection 用来接收绑定到的service的状态并作出响应,不可为空,看下面conn的实现就很清楚;flags则是绑定服务时的可选操作,设置BIND_AUTO_CREATE可以在绑定时自动创建,也可设0.

因为 ServiceConnection 不可为空,所以:

IRemoteService iRemoteService;
……
private ServiceConnection conn = new ServiceConnection() {
        //绑定上服务
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iRemoteService = IRemoteService.Stub.asInterface(service);
        }
        //服务断开
        @Override
        public void onServiceDisconnected(ComponentName name) {
            iRemoteService = null;
        }
    };
复制代码

在之前有说到,RemoteCalculator service 被绑定上时,会返回一个 mBinder

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
复制代码

那么在 onServiceConnected(ComponentName name, IBinder service) 中的 IBinder service 实质上就是 RemoteCalculator service 中的 mBinder ,那么在 RemoteCalculator service 中 private Binder mBinder = new IRemoteService.Stub(){}, 所以这么一操作,conn 中的 IBinder service 就是远程服务的 service 。
此时接收一下就好了 iRemoteService = IRemoteService.Stub.asInterface(service);(为何如此接收后面分析)
至此客户端就拿到了远程服务端的 service 即 iRemoteService。
客户端想要的结果就可以这么来获得 int result = iRemoteService.add(num1,num2);

通过 IPC 传递对象

官方说明:通过 IPC 接口把某个类从一个进程发送到另一个进程是可以实现的。 不过,您必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统可通过它将对象分解成可编组到各进程的原语。

传递自定类型是可以的,但必须支持 Parcelable 接口。

  1. 自定义类实现 Parcelable 接口。
  2. 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
  3. 自定义类添加一个名为 CREATOR 的静态字段,这个字段是一个实现 Parcelable.Creator 接口的对象。
  4. 最后,创建一个声明可打包类的 .aidl 文件。

1.创建自定义类

新建一个 IMyBook 类继承 Parcelable
包含name,price,year,author四个属性。 继承 Parcelable 接口需要实现以下三个方法

    //注意拆包顺序要与打包顺序一致
    protected IMyBook(Parcel in) {
        this.name = in.readString();
        this.price = in.readInt();
        this.year = in.readInt();
        this.author = in.readString();
    }
    
    public static final Creator<IMyBook> CREATOR = new Creator<IMyBook>() {
        //拆包
        @Override
        public IMyBook createFromParcel(Parcel in) {
            //获取的in写入新book对象
            return new IMyBook(in);//返回的新IMyBook对象到上面拆包
        }

        //默认
        @Override
        public IMyBook[] newArray(int size) {
            return new IMyBook[size];
        }
    };
    //默认
    @Override
    public int describeContents() {
        return 0;
    }

    //打包
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //将book属性打包
        dest.writeString(name);
        dest.writeInt(price);
        dest.writeInt(year);
        dest.writeString(author);

    }
复制代码

IMyBook 类也需要建议对应的aidl文件

2.建立服务端

新建 IRemote.aidl 注意导包,接口里写一个向list添加对象的抽象方法。

make project,生成 IMyBook.java 和 IRemote.java 后。建立 service 实现 IRemote 接口

3.客户端绑定服务

客户端将aidl文件复制到同级目录,同时 IMyBook 类也要复制到对应包下

和之前一样,建立 ServiceConnection

绑定到服务

调用服务端service方法,并获取返回值。完成了通讯。

注意事项

客户端在调用远程方法时,要确保在调用前绑定到远程服务!! 如下错误写法:
在点击事件内绑定服务,会导致服务未绑定完成就开始调用 add 方法。

则会出现空指针。当然出现空指针也可能是服务启动失败,可以先检查是否绑定错误,再深入分析。

基本数据类

默认情况下,AIDL 支持下列数据类型:

  1. Java八种基本数据类型(int、char、boolean、double、float、byte、long、string) 但不支持short
  2. String、CharSequence
  3. List和Map
  4. Parcelable
  5. List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
  6. Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。

您必须为以上未列出的每个附加类型加入一个 import 语句,即使这些类型是在与您的接口相同的软件包中定义。

AIDL原理

先放图

看下图,IRemote.java 是make project后AS自动生成的。看他的结构实际就是一个 Stub 和 add 方法。

add方法就是开发者定义在IRemote.aidl中的方法。可以看到它返回一个list,抛出RemoteException。
public java.util.List<com.wind.fitz.ipcaidl.IMyBook> add(com.wind.fitz.ipcaidl.IMyBook book) throws android.os.RemoteException;

这个 Stub 子类它继承自 Binder 并实现IRemote(自身)的接口。

public static abstract class Stub extends android.os.Binder implements com.wind.fitz.ipcaidl.IRemote

里面通过一个Stub()构造方法来连接。

/** Construct the stub at attach it to the interface. */在连接到接口时构建存根
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
复制代码

在客户端 ServiceConnection 中通过如下方式来获得远程服务: iRemote = IRemote.Stub.asInterface(service); 那么 **.Stub.asInterface() 实际上是返回一个 Proxy

/**
 * Cast an IBinder object into an com.wind.fitz.ipcaidl.IRemote interface,
 * generating a proxy if needed.
 */
public static com.wind.fitz.ipcaidl.IRemote asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.wind.fitz.ipcaidl.IRemote))) {
return ((com.wind.fitz.ipcaidl.IRemote)iin);
}
return new com.wind.fitz.ipcaidl.IRemote.Stub.Proxy(obj);  //a
}
复制代码

那么去看 Proxy ,上面的 a 实际走到了 b ,返回一个 mRemote (Binder对象)即 IRemote 的代理给了客户端的 iRemote。

private static class Proxy implements com.wind.fitz.ipcaidl.IRemote
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;  //b
}
复制代码

那么再看客户端调用服务的方法:
iRemote.add(new IMyBook("aaa",100,2018,"bb")); 已经知道客户端的 iRemote 实际上是 IRemote.java 中的 mRemote。 iRemote.add 调用的是 Proxy 重写的 add 方法

@Override public java.util.List<com.wind.fitz.ipcaidl.IMyBook> add(com.wind.fitz.ipcaidl.IMyBook book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.wind.fitz.ipcaidl.IMyBook> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);   //c
_reply.readException();
_result = _reply.createTypedArrayList(com.wind.fitz.ipcaidl.IMyBook.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
复制代码

在重写的 add 方法中,是一系列的序列化操作,通过 mRemote.transact 发送给底层(上述流程在Proxy内)。 之后 Stub onTransact 接收到 Proxy 发送来的数据。再联系到服务端。 不严谨的解释:

client -- [ proxy - stub ] -- server

文章分类
Android