美文网首页
AIDL简单总结

AIDL简单总结

作者: 梧叶已秋声 | 来源:发表于2022-09-02 17:09 被阅读0次

    1.AIDL简介

    AIDL是Android Interface Definition Language的简写,即Android接口定义语言。我们知道Android系统为每一个应用开启一个独立的虚拟机,每个应用都运行在各自进程里(默认情况下),彼此之间相互独立,无法共享内存。当一个应用想要访问另一个应用的数据或调用其方法,就要用到Android系统提供的IPC机制。而AIDL就是Android实现IPC机制的方式之一。

    AIDL的代码生成器,已经根据.aidl文件自动帮我们生成Proxy、Stub(抽象类)两个类,并且把客户端代理mRemote的transact()过程以及服务器端的onTtransact()过程默认实现好了,我们只需要在服务端继承Stub,实现自己的业务方法即可。

    2.AIDL的使用

    2.1创建AIDL文件

    新建AIDL文件,build生成对应的Java文件。

    // IMyAidlInterface.aidl
    interface IMyAidlInterface {
    
        /**
        * 自己添加的方法
        */
        int add(int value1, int value2);
    }
    

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

    创建了一个service,并在service内部声明了一个IBinder对象,IMyAidlInterface.Stub实例,重写了add方法

    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;
            }
        };
    }
    

    2.3 客户端调用AIDL

    2.3.1首先要保证客户端存在对应的AIDL文件。

    将服务器端的AIDL文件拷贝到客户端,拷贝后的客户端的AIDL文件包目录必须与服务器端保持一致,拷贝完后build工程,让客户端也生成对应的Java文件。

    2.3.2 启动service

    service启动方式有2种,startService和bindService,这里需要通过bind去启动service。
    服务生命周期如下图。


    https://developer.android.google.cn/guide/components/services

    左图显示使用 startService() 创建的服务的生命周期,右图显示使用 bindService() 创建的服务的生命周期。

    区别

    • startService: onCreate -> onStartCommand -> onDestory ,在多次调用startService的时候,onCreate不重复执行,而onStartConmon()会被多次调用当我们执行。startService调用了这后,Service会一直存在,直到其调用了stopService。
    • bindService : onCreate -> onBind -> onUnbind -> onDestory,多次调用bindService,onCreate及onBind都只执行一次。它生命周期跟随其调用者,调用者释放的时候,必须对该Service解绑,当 所有绑定(可以多个client绑定一个service) 全部取消后,系统即会销毁该服务。 bindService 的方式通过onServiceConnected方法,获取到Service对象,通过该对象可以直接操作到Service内部的方法,从而实现的Service 与调用者之间的交互。

    使用场景

    • 如果想要启动一个后台服务长期进行某项任务,那么使用startService
    • 如果只是短暂的使用,那么使用bindService。

    startService一般使用更广。系统服务的启动,一般都是startService。
    最终是通过ActivityManagerNative.getDefault().startService(),将Service的启动交给SystemServer进程的ActivityManagerService服务来完成。

    bindService总结来整个过程大概分为以下几步:

    • 1、首先调用bindService通知AMS绑定service,AMS会先启动service回调其onCreate函数。
    • 2、AMS启动service完成之后继续调用service的onBind函数,获取service返回的binder。
    • 3、AMS把上一步获得的Binder对象通过绑定时传入的ServiceConnection的onServiceConnected函数传给调用端,这样调用端就可以通过binder来调用service提供的相关方法。

    在适当的地方启动service,例如在Activity中绑定服务(这里只是简单写下,其实一般是用单例去写个Manage类去管理)。
    这里主要分2步:1.创建ServiceConnection 2.绑定service。 绑定服务成功会回调ServiceConnection 中的onServiceConnected,我们再通过IMyAidlInterface.Stub.asInterface(service)获取到Service的代理。

     private IMyAidlInterface aidl ;
    
     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);
        }
    
    2.3.3 使用Service

    通过bindService启动服务后,进行调用。
    在Activity中放了一个按钮,点击按钮调用。

    int result = aidl.add(12, 12);
    Log.e(TAG, "远程回调结果:" + result);
    

    3.原理

    AIDL主要是通过Binder来实现进程通信的,.aidl是一种语言,编译后它会生成.java文件,因为binder相关文件的书写不方便,所以才有了aidl——Android接口定义语言。
    IMyAidlInterface.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;
    }
    

    重点是:public static abstract class Stub

    https://www.jianshu.com/p/0641aef2c9e9/
    在客户端绑定服务的时候调用IMyAidlInterface.Stub.asInterface(service),
    Stub.asInterface这里会根据调用进程与当前进程是否一致判断返回Bbinder(Binder实体)还是BpBinder(本地Binder的代理对象,即IStatusBarService.Stub.Proxy),最后走到了return new com.yunzhou.aidlserver.IMyAidlInterface.Stub.Proxy(obj);,客户端获取的是一个远程服务的代理。
    //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);
    }
    

    Stub.Proxy中实现的add,在Stub.Proxy中我们可以看到一句核心代码

    //add
    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
    
    // frameworks/native/libs/binder/BpBinder.cpp
    status_t BpBinder::transact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
    {
       ...
       status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);  
       ...
    }
    

    然后就走到了IPCThreadState::self()->transact( mHandle, code, data, reply, flags)

    // frameworks/native/libs/binder/IPCThreadState.cpp
    status_t IPCThreadState::transact(int32_t handle,
                                      uint32_t code, const Parcel& data,
                                      Parcel* reply, uint32_t flags)
    {
       ...
    //  发送BC_TRANSACTION请求
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
       ...
    // 等待响应
            if (reply) {
                err = waitForResponse(reply);
            } else {
                Parcel fakeReply;
                err = waitForResponse(&fakeReply);
            }
          ...
    }
    
    /**
    * 将data的数据写入输出缓冲区
    */
    status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
        int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
    {
        binder_transaction_data tr;//创建一个结构体binder_transaction_data
        tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
        tr.target.handle = handle;
        tr.code = code;
        tr.flags = binderFlags;
        ……
        const status_t err = data.errorCheck();
        if (err == NO_ERROR) {
        //将Parcel类的data中的数据写入结构体tr中
            tr.data_size = data.ipcDataSize();
            tr.data.ptr.buffer = data.ipcData();
            tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
            tr.data.ptr.offsets = data.ipcObjects();
        } 
        ……
        mOut.writeInt32(cmd);
        mOut.write(&tr, sizeof(tr));//最后将结构体tr写入输出缓冲区
        return NO_ERROR;
    }
    
    status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
    {
        uint32_t cmd;
        int32_t err;
    
        while (1) {
            talkWithDriver();//与binder驱动交互
            ……
            cmd = (uint32_t)mIn.readInt32();
            ……
            switch (cmd) {
            case BR_TRANSACTION_COMPLETE:
                ……
                break;
    
            case BR_REPLY:
                {
                    binder_transaction_data tr;//创建一个结构体binder_transaction_data
                    err = mIn.read(&tr, sizeof(tr));//从输入缓冲区中将数据写入结构体tr中
                    ……
                    if (reply) {
                        if ((tr.flags & TF_STATUS_CODE) == 0) {
                        //最后将结构体tr中的数据写入reply
                            reply->ipcSetDataReference(
                                reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                                tr.data_size,
                                reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                                tr.offsets_size/sizeof(binder_size_t),
                                freeBuffer, this);
                        } 
                    }
                    ……
                }
                goto finish;
    
            ……
            }
        }
    
    finish:
        if (err != NO_ERROR) {
            ……
            if (reply) reply->setError(err);
            ……
        }
    
        return err;
    }
    
    status_t IPCThreadState::talkWithDriver(bool doReceive)
    {
        binder_write_read bwr;
        
        bwr.write_size = outAvail;
        bwr.write_buf = (long unsigned int)mOut.data(); // 取出输出缓冲区
        bwr.read_size = mIn.dataCapacity;
        bwr.read_buffer = (long unsigned int)mIn.data();//取出输入缓冲区
    
    // 把mOut写到Binder,并将返回数据读入mIn
        ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr);    
        ……
        if (bwr.read_consumed > 0) {
               mIn.setDataSize(bwr.read_consumed);
               mIn.setDataPosition(0);
        }
        ……
    }
     
    

    真正与Binder 驱动打交道的地方是talkWithDriver 中的ioctl(),整个流程中多次调用了这个函数。

    这里的流程是
    transact() --> writeTransactionData() --> waitForResponse() --> talkWithDriver() --> ioctl

    mRemote.transact最终会调用BBinder::onTransact,也就是Binder实体。
    至于函数是怎么从transact到onTransact的,需要对Binder驱动有一定的了解,可参考这2篇:
    Android10.0 Binder通信原理(四)-Native-C\C++实例分析
    Android10.0 Binder通信原理(五)-Binder驱动分析

    waitForResponse()收到BR_TRANSACTION响应码后,最终后调用executeCommand()进行命令执行,根据逻辑处理,最终会调用到Binder实体的onTransact()。
    这张图比较复杂,由于对驱动层不了解,所以就大致留个印象吧。


    image.png

    而前面生成的IMyAidlInterface.java中, onTransact中执行了add(int value1, int value2),由于在Service端重写了add,所以调用了在服务端定义Service中的add。
    Binder数据传输流程图如下所示:


    https://www.jianshu.com/p/375e3873b1f4

    Binder架构如下所示:


    https://juejin.cn/post/6857132425781182477

    4.oneway

    image.png

    oneway(单程)

    image.png

    非oneWay:


    image.png

    当收到BR_TRANSACTION_COMPLETE则程序返回,有人可能觉得好奇,为何oneway怎么还要等待回应消息? 我举个例子,你就明白了.
    你(app进程)要给远方的家人(system_server进程)邮寄一封信(transaction), 你需要通过邮寄员(Binder Driver)来完成.整个过程如下:
    你把信交给邮寄员(BC_TRANSACTION);
    邮寄员收到信后, 填一张单子给你作为一份回执(BR_TRANSACTION_COMPLETE). 这样你才放心知道邮递员已确定接收信, 否则就这样走了,信到底有没有交到邮递员手里都不知道,这样的通信实在太让人不省心, 长时间收不到远方家人的回信, 无法得知是在路的中途信件丢失呢,还是压根就没有交到邮递员的手里. 所以说oneway也得知道信是投递状态是否成功.
    邮递员利用交通工具(Binder Driver),将信交给了你的家人(BR_TRANSACTION);当你收到回执(BR_TRANSACTION_COMPLETE)时心里也不期待家人回信, 那么这便是一次oneway的通信过程.
    如果你希望家人回信, 那便是非oneway的过程,在上述步骤2后并不是直接返回,而是继续等待着收到家人的回信, 经历前3个步骤之后继续执行:
    家人收到信后, 立马写了个回信交给邮递员BC_REPLY;
    同样,邮递员要写一个回执(BR_TRANSACTION_COMPLETE)给你家人;
    邮递员再次利用交通工具(Binder Driver), 将回信成功交到你的手上(BR_REPLY)这便是一次完成的非oneway通信过程.
    oneway与非oneway: 都是需要等待Binder Driver的回应消息BR_TRANSACTION_COMPLETE.
    主要区别 在于oneway的通信收到BR_TRANSACTION_COMPLETE则返回,而不会再等待BR_REPLY消息的到来.

    AIDL的调用是一个同步阻塞的操作,执行binder相关代码的时候,APP进程会休眠,唤醒后接收BR_REPLY。
    下面来看看使用。

    // 修饰接口 
    oneway interface ICallback { 
    void onReceiveBytes(int groupId, in byte[] data); 
    }
    //修饰方法
    oneway void start()
    

    出处:https://www.jianshu.com/p/e12a6f531af4
    正常情况下Client调用AIDL接口方法时会阻塞,直到Server进程中该方法被执行完。oneway可以修饰AIDL文件里的方法,oneway修饰的方法在用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果,该关键字可以用来声明接口或者声明方法,如果接口声明中用到了oneway关键字,则该接口声明的所有方法都采用oneway方式。(注意,如果client和Server在同一进程中,oneway修饰的方法还是会阻塞)

    异步调用(单个binder请求)和串行化处理(多个binder请求)

    异步调用

    出处:https://blog.csdn.net/Jason_Lee155/article/details/116637925
    1.异步调用
    举个例子:假如Client端调用IPlayer.start(),而且Server端的start需要执行2秒,由于定义的接口是异步的,Client端可以快速的执行IPlayer.start(),不会被Server端block住2秒。
    2.同步调用
    举个例子:假如Client端调用IPlayer. getVolume(),而且Server端的getVolume需要执行1秒,由于定义的接口是同步的,Client端在执行IPlayer. getVolume()的时候,会被Server端block住1秒。
    3.为什么会有同步调用和异步调用?
    其实一般使用异步调用的时候,Client并不需要得到Server端执行Binder服务的状态或者返回值,这时候使用异步调用,可以有效的提高Client执行的效率。

    串行化处理
    假设进程A中有如下两个Binder服务IPlayer1和IPlayer2,这两个服务都有两个异步的接口start和stop。

    interface IPlayer1 {
        oneway void start();//异步,执行2秒
        oneway void stop();//异步,执行2秒
    }
    interface IPlayer2 {
        oneway void start();//异步,执行2秒
        oneway void stop();//异步,执行2秒
    }
    
    

    问题1
    如果进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer2.start(),请问进程A能否同时响应这两次Binder调用并执行?

    • 可以同时执行。

    问题2
    如果进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer1.start(),请问进程A能否同时响应这两次Binder调用并执行?

    • 不能同时执行,需要一个一个排队执行。

    问题3
    如果进程B和进程C同一时刻分别调用IPlayer1.start()和IPlayer1.end(),请问进程A能否同时响应这两次Binder调用并执行?

    • 不能同时执行,需要一个一个排队执行。

    具体代码分析看这篇关于Binder (AIDL)的 oneway 机制

    oneway 的隐患

    binder驱动对于oneway的调用是类似于handler sendmessage那样的,挨个处理,所以如果服务端的oneway接口处理太慢而客户端调用太多的话,来不及处理的调用会占满binder驱动的缓存,导致其他调用抛出上面的transaction failed。
    oneway 方法的隐患具体参考这篇。
    AIDL oneway 方法的隐患

    5.AIDL可使用的参数类型


    image.png

    注意:

    • AIDL中也要创建自定义类型所对应的文件
    • 在使用自定义Parcelable类型时,必须使用import语句进行导入,即使是在同一个包中
    • 自定义类型aidl文件名字、路径需要和自定义类名字,路径保持一致。

    还有一点需要注意的是,Android studio版本的不同,同一份代码的情况下出现编译错误问题。

    6.AIDL中关于定向tag的理解

    //in表示数据只能由客户端流向服务端
    byte SerTestIn(in byte[] pa);
    //out表示数据只能由服务端流行客户端
    byte SerTestOut(out byte[] pa);
    //inout则表示数据可在服务端与客户端之间双向流通。
    byte SerTestInout(inout byte[] pa);
    

    AIDL中的定向tag表示在跨进程通信中数据 流向

    • in表示数据只能由客户端流向服务端
    • out表示数据只能由服务端流行客户端
    • inout则表示数据可在服务端与客户端之间双向流通。

    其中,数据流向是针对客户端中的那个传入方法的对象而言。in为定向tag的话,表现为服务端将会接受到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端传参修改而发生变动;out的话表现为服务端将会接收到那个对象的参数为空的对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout为定向tag的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务对该对象的任何变动。

    根本原因是影响了.java 代码的生成逻辑。

    • tag为in时,server端其实是重新创建的一个对象,并且将client端对象的数据一一赋值给新的对象,所以这两个对象互不影响
    • tag为out时,client根本就没有将数据写入Parcel传递给server端,而server端则是重新创建的一个实例,对此实例的修改都会返回给到client端并覆盖掉client端对象的值,所以out tag会影响client端对象
    • tag为inout时,结合了in,out的特点,server端可以获取到client端对象的数据,并且server端对对象的修改可以影响到client端。

    具体代码分析可参考这篇:AIDL中的in,out,inout原理篇

    7.其他注意点


    image.png
      ...
        mClientCallBack = IRemoteCallBack.Stub.asInterface(callback);
        if (mClientDeathHandler == null) {
              mClientDeathHandler = new ClientDeathRecipient();
        }
        mClientCallBack.asBinder().linkToDeath(new ClientDeathRecipient(), 0);
        ...
    private class ClientDeathRecipient implements IBinder.DeathRecipient {
    
    
            @Override
            public void binderDied() {
                mCallbackList.unregister(mClientCallBack);
                mClientCallBack = null;
                Logger.d(TAG,"client  is died");
            }
        }
    

    8.Client注册回调接口
    client向server的通信,那如果server要调用client呢?
    一个比较容易想到的办法就是通过AIDL在server端设置一个client的回调。这样的话就相当于client端是server端的server了。
    有注册回调就肯定有解注册,但是client端与server不在一个进程,server是无法得知client解注册时传入的回调接口是哪一个(client调用解注册时,是通过binder传输到server端,所以解注册时的回调接口是新创建的,而不是注册时的回调接口)。为了解决这个问题,android提供了RemoteCallbackList这个类来专门管理remote回调的注册与解注册。
    这篇是我早期写的一篇带回调的使用的简单demo,可简单参考。
    AIDL简单使用记录

    参考链接:
    bindService启动过程分析
    bindService流程源码分析
    startService和bindService的区别,生命周期以及使用场景
    Android服务启动之StartService源码分析
    AIDL使用详解及原理
    AIDL中的in,out,inout原理篇
    [027]十分钟让你明白AIDL
    AIDL要点总结
    Android跨进程通信IPC之11——AIDL
    AIDL oneway 方法的隐患
    Android binder学习笔记4 - binder transact流程分析
    Binder之ProcessState和IPCThreadState类分析
    直接通过Binder的onTransact完成跨进程通信
    Android10.0 Binder通信原理(四)-Native-C\C++实例分析
    干货 | 彻底理解ANDROID BINDER通信架构(下)
    关于Binder (AIDL)的 oneway 机制

    相关文章

      网友评论

          本文标题:AIDL简单总结

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