AIDL学习

AIDL

AIDL的核心有两点

  • AIDL是一种跨进程通讯方式
    这种方式是基于Binder机制来进行的,Binder本质上是基于C/S架构,Service提供服务(方法),Client使用服务(方法调用)
  • AIDL本质上是一种代码生成的方式
    从这一点上来说它和apt代码生成方式没有本质区别,AIDL的特殊之处在于它生成的代码是Binder进程间通讯的最外层封装。

AIDL特点

AIDL的语法和Java基本类似,但是也有不同点,主要在以下几点

  • 支持的类型
  • Parcelable和AIDL接口都需要导包
  • 没有public等关键字
  • 所有非基本类型的都需要添加in、out、inout关键字,用于指明数据流向,默认为in

支持的类型

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

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
  • String
  • CharSequence
  • Parcelable Parcelable必须显示加入一个 import 语句,即使这些类型是在与接口相同的软件包中定义。
  • List List 中的所有元素都必须是列表中支持的数据类型、其他 AIDL 生成的接口或Parcelable类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
  • Map
    Map 中的所有元素都必须是列表中支持的数据类型、其他 AIDL 生成的接口或Parcelable类型。 不支持通用 Map(如 Map 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。
  • AIDL对应的接口

AIDL生成文件解析

AIDL最终会生成一个继承自IInterface的接口,先简单看下这个类的结构。这里我做了一些结构优化,看上去更方便一些

public interface IMusicControler extends IInterface{

    public static abstract class Stub extends Binder implements IMusicControler{
        private static final String DESCRIPTOR = "site.yihome.ipc.IMusicControler";

        public static IMusicControler asInterface(IBinder obj){
           ......
        }

        @Override
        protected boolean onTransact(int code, Parcel data,  Parcel reply, int flags) throws RemoteException {
            ......
            return super.onTransact(code, data, reply, flags);
        }

        public static class Proxy implements IMusicControler{
            private IBinder mRemote;
            Proxy(IBinder remote) {
                mRemote = remote;
            }

            @Override
            public IBinder asBinder()
            {
                return mRemote;
            }

            @Override
            public void startMusic(Music music) throws RemoteException {
                ......
            }

            @Override
            public void stopMusic() throws RemoteException {
                ......
            }

            @Override
            public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException {
                ......
            }
        }

    }

    public void startMusic(Music music) throws RemoteException;

    public void stopMusic() throws RemoteException;

    public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException;
}

从上面的代码中,我们可以明显的看到IMusicControler中以包名定义一个标示DESCRIPTOR,这个标示贯穿整个通讯过程,同时定义一个三层结构

  • 最外层就是我们AIDL中定义的最原始的接口类型IMusicControler,和它定义的方法
  • 第二层是一个抽象类Stub,他是三层中唯一的Binder对象,用于和Binder交互完成真正的进程间通讯
  • 第三层是一个IMusicControler的实现类Proxy,和名字一样是Service端的代理类,进程间通讯时client首先调用的就是这个类的方法,然后在通过Binder调用远程对象。

如下图所示,Stub和Proxy本质是就是Binder两端的数据封装和解析层


AIDL学习_第1张图片
image

Proxy主要完成Client调用时参数的转化,它把方法的参数转换成Parcel类型,用于Binder通信时传输数据,同时也把Binder传回的Parcel类型的返回值转换成对应我们需要的类型。

Stub和Proxy类似,但是Stub主要完成的是Server端的数据转化。具体的代码细节我们借助下面AIDL调用的全过程来讲解

AIDL调用全过程

Client调用过程

AIDL的调用过程,我们从ServiceConnectiononServiceConnected方法开始

override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
    musicControler = IMusicControler.Stub.asInterface(service)
    ......
}

一般来说,bindService后在onServiceConnected的回掉中,我们都会通过Stub的asInterface方法获取可以操作的IInterface对象,asInterface具体细节如下

public static IMusicControler asInterface(IBinder obj){
    if(obj==null){
        return null;
    }
    IInterface iIn = obj.queryLocalInterface(DESCRIPTOR);
    if(iIn!=null&&(iIn instanceof IMusicControler)){
        return (IMusicControler) iIn;
    }
    return new Proxy(obj);
}

asInterface中,首先通过DESCRIPTOR去查询本地接口(即非跨进程),如果是进程间通讯则会用这个IBinder对象去new一个Proxy对象。也就是说我们在Client中拿到的IMusicControler事实上就是Proxy。

Proxy(IBinder remote) {
    mRemote = remote;
}

如果这时调用startMusic(Music music)去调用远程服务,事实上是调用的Proxy的方法

public void startMusic(Music music) throws RemoteException {
    Parcel _data = Parcel.obtain();
    Parcel _reply = Parcel.obtain();

    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((music!=null)) {
            _data.writeInt(1);
            music.writeToParcel(_data, 0);
        }
        else {
            _data.writeInt(0);
        }
        mRemote.transact(TRANSACTION_startMusic, _data, _reply, 0);
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

可以看到Proxy事实上只是把参数封装到了Parcel对象中,同时会去获取reply中的数据。同时会远程服务IBinder的transact,这个IBinder事实上是一个BinderProxy对象。这里就会调用BinderProxy的transact方法

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
    ......
    try {
        return transactNative(code, data, reply, flags);
    } finally {
        ......
    }
}

可以看到这个transact方法最终会调用native方法通过Binder完成真正的跨进程。Client的流程到此结束。

Server端调用过程

在IPC的Service中,我们需要在onBind方法中需要返回一个Stub的实现类

return new IMusicControler.Stub() {......};

这个对象就是Server端的入口类,Client中transactNative方法最终会调用Stub的onTransact这个由它自己实现的方法

 protected boolean onTransact(int code, Parcel data,  Parcel reply, int flags) throws RemoteException {
    switch (code){
        case INTERFACE_TRANSACTION:
            reply.writeString(DESCRIPTOR);
            return true;
        case TRANSACTION_startMusic:
            data.enforceInterface(DESCRIPTOR);
            Music music;
            if ((0!=data.readInt())) {
                music = Music.CREATOR.createFromParcel(data);
            }else {
                music = null;
            }
            this.startMusic(music);
            reply.writeNoException();
            break;
        ......
    }
    return super.onTransact(code, data, reply, flags);
}

这个方法会对在transact传入的code进行不同的处理,同时从Parcel中获取实际参数,最后调用Service中的对应方法。

AIDL异步调用

默认情况下AIDL的调用是同步的,即必须等到Server端执行完成后才会返回。如何实现异步调用呢,可以看到在前面的transact中有四个参数,我们用了其中三个,而第四个参数flag正是用来标示调用方式的,默认0标示同步调用,如果需要异步调用,则需要把flag设置为FLAG_ONEWAY(这个过程想要用AIDL文件进行的话只需要在对应的方法前添加oneway关键字)

但是通过异步调用,我们是获取不到返回值的,因此需要接口回掉,但是普通的接口回掉无法满足跨进程通讯的需求,这种情况怎么处理呢?

还是Binder,前面一般情况下Service是充当Server端提供方法,Activity作为Client调用方法,Binder完成Client对Server的调用,当需要接口回掉时,两者身份就会反转,Activity作为Service提供IBinder对象,供Service调用,而这个IBinder对象则调用Service的方法通过参数传递(前面也说过,AIDL是支持这种IBinder对象传递的)

Binder的回掉方法是执行在子线程中的,这一点可能和Binder的机制有关,有待进一步研究

RemoteCallbackList

上面这一种异步回掉只是一种简单的情况,当一个Service需要对很多Client进行回掉时,就需要对注册的回掉进行一个系统的管理了,RemoteCallbackList正是用来处理这种情况的,它内部使用了一个ArrayMap来存储所有的回掉,同时会在需要的时候进行调用,核心的代码如下

public class RemoteCallbackList {
    ArrayMap mCallbacks
        = new ArrayMap();
    public boolean register(E callback) {
        return register(callback, null);
    }
    
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            // Flag unusual case that could be caused by a leak. b/36778087
            logExcessiveCallbacks();
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }
    
     public void broadcast(Consumer action) {
        int itemCount = beginBroadcast();
        try {
            for (int i = 0; i < itemCount; i++) {
                action.accept(getBroadcastItem(i));
            }
        } finally {
            finishBroadcast();
        }
    }
}

这个类并不复杂,它主要封装了对callback常见的操作,同时帮助我们做了很多线程同步上的工作。

下步目标

了解Binder通讯原理

你可能感兴趣的:(AIDL学习)