美文网首页Android Service及aidl
Android Service aidl分析

Android Service aidl分析

作者: ITRenj | 来源:发表于2020-11-07 21:22 被阅读0次

    Android Service 代码地址

    在介绍正文之前,我们先看看几个知识点进程、线程和Android中的进程间通讯(IPC)。

    • 线程:线程时CPU调度的最小单元,同时线程也是一种有限的系统资源。
    • 进程:进程一般表示一个执行单元,在PC和移动设备中一般可以表示一个程序或者应用(在一般情况下,一个应用程序是在同一进程中运行,但是也有可能一个应用有多个进程,比如:Android中可以在清单文件中为四大组件指定运行进程(process属性),而不是运行在默认进程。)。
    • Android中的进程间通讯方式
      • Bundle:通过Intent打开四大组件时,Bundle可以用来传递数据(四大组件之间传递,数据类型有限)
      • 文件共享:通过共享文件传递数据(不适合高并发,且进程之间的数据传递实时性不强)
      • AIDL方式:功能强大,支持并发和实时性,支持一对多传输(使用较为复杂)
      • Messenger:AIDL的一种简化形式(不支持并发,请求串行处理,且为一对一,数据类型有限,和Bundle一样)
      • ContentProvider:Android四大组件之一,强大的数据源处理能力,支持并发和一对多(主要用于对数据源的CURD操作)
      • Socket:套接字,功能强大,通过网络传输字节流数据,支持一对多并发实时通讯(实现较为复杂,主要用于网络数据交换)

    在前面我们已经说过了使用AIDL进行IPC通讯,这篇文章主要说明一下我们使用aidl后缀时,系统帮我们做了哪些事情。我们根据以下 IRemoteBookBinder.aidl 文件来做分析:

    interface IRemoteBookBinder {
        void addBookIn(in BookBean bookBean);
    
        void addBookOut(out BookBean bookBean);
    
        void addBookInOut(inout BookBean bookBean);
    
        List<BookBean> getBookList();
    }
    

    我们创建了 IRemoteBookBinder.aidl 文件,重新 build 后,Android studio会帮我们自动生成 IRemoteBookBinder.java 文件,Android Studio自动生成的java文件路径在 app\build\generated\aidl_source_output_dir\debug\out\包名 中。文件内容比较长,这里就不直接将代码完全粘贴出来了,在下面分步说明。下图为 IRemoteBookBinder.java 类组织图:

    IRemoteBookBinder结构.png

    强烈建议:在看具体分析时,结合Android Studio中自动生成的类来看,效果要好于查看贴出来的代码。

    内部类 Default

    public static class Default implements com.renj.service.aidl.IRemoteBookBinder {
        @Override
        public void addBookIn(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException {
        }
    
        @Override
        public void addBookOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException {
        }
    
        @Override
        public void addBookInOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException {
        }
    
        @Override
        public java.util.List<com.renj.service.bean.BookBean> getBookList() throws android.os.RemoteException {
            return null;
        }
    
        @Override
        public android.os.IBinder asBinder() {
            return null;
        }
    }
    

    该类实现了 IRemoteBookBinder 类,作为IRemoteBookBinder接口的默认实现,在实际中并无太多实际意义。

    IRemoteBookBinder.aidl 文件中声明的方法

    public void addBookIn(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException;
    
    public void addBookOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException;
    
    public void addBookInOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException;
    
    public java.util.List<com.renj.service.bean.BookBean> getBookList() throws android.os.RemoteException;
    

    这几个方法都是在 IRemoteBookBinder.aidl 进行了声明的,直接作为接口的声明方法,具体有子类实现。

    内部类 Stub

    public static abstract class Stub extends android.os.Binder implements com.renj.service.aidl.IRemoteBookBinder {
        private static final java.lang.String DESCRIPTOR = "com.renj.service.aidl.IRemoteBookBinder";
    
    
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
    
        public static com.renj.service.aidl.IRemoteBookBinder asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.renj.service.aidl.IRemoteBookBinder))) {
                return ((com.renj.service.aidl.IRemoteBookBinder) iin);
            }
            return new com.renj.service.aidl.IRemoteBookBinder.Stub.Proxy(obj);
        }
    
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
    
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case TRANSACTION_addBookIn: {
                    data.enforceInterface(descriptor);
                    com.renj.service.bean.BookBean _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.renj.service.bean.BookBean.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBookIn(_arg0);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_addBookOut: {
                    data.enforceInterface(descriptor);
                    com.renj.service.bean.BookBean _arg0;
                    _arg0 = new com.renj.service.bean.BookBean();
                    this.addBookOut(_arg0);
                    reply.writeNoException();
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addBookInOut: {
                    // ...
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.renj.service.bean.BookBean> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }
    
        private static class Proxy implements com.renj.service.aidl.IRemoteBookBinder {
            // ...
        }
    
        static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    
        public static boolean setDefaultImpl(com.renj.service.aidl.IRemoteBookBinder impl) {
            if (Stub.Proxy.sDefaultImpl == null && impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }
    
        public static com.renj.service.aidl.IRemoteBookBinder getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }
    

    以上代码,去掉了 Proxy 内部类的实现,在后面再说,然后就是去掉了 TRANSACTION_addBookInOut 部分的实现,因为这部分就是TRANSACTION_addBookInTRANSACTION_addBookOut的结合。

    Stub 类继承了 android.os.Binder 并且实现了 IRemoteBookBinder 接口,她就是真正的Binder类,是在Service中的onBind()方法返回给客户端的Binder类(一般我们在onBind()方法中返回的都是 .Stub 的子类,如 RemoteBookBinderImpl extends IRemoteBookBinder.Stub),也是客户端 ServiceConnection 回调方法 onServiceConnected() 中的参数 service

    private ServiceConnection bindConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iRemoteBookBinder = IRemoteBookBinder.Stub.asInterface(service);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    

    DESCRIPTOR

    Binder 的唯一标识,一般使用当前类的全路径名表示,如上面的 "com.renj.service.aidl.IRemoteBookBinder"。

    构造方法

    将本身对象(this)和唯一标识(DESCRIPTOR)通过 attachInterface() 传递给父类 Binder 类,这里需要注意,后面会有地方用到。

    public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    

    asInterface(android.os.IBinder obj)

    将服务端的Binder对象转换成客户端所需要的AIDL接口对象, ServiceConnection 回调方法 onServiceConnected() 中就是使用这个方法对 service 参数进行转换。这个方法内部的转换是区分进程的,如果客户端和服务端在同一个进程,那么返回的就是服务端的Stub对象本身,否则返回的就是系统封装后的 IRemoteBookBinder.Stub.Proxy 类。注意创建 IRemoteBookBinder.Stub.Proxy(obj) 对象时,参数 obj ,就是 onBind() 方法返回的Binder对象,也就是回调 onServiceConnected() 方法中的参数 service。

    asInterface()方法内部是通过 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 方法判断客户端和服务端是否在同一进程的,queryLocalInterface() 方法在 Binder 中的实现:

    public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }
    

    上述方法中的 mDescriptormOwner 就是通过 Stub 的构造方法设置的值。

    asBinder() 方法

    该方法返回当前的类对象,也就是Binder

    方法id

    static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
    

    这里就是aidl接口中的方法,为他们都声明一个id,用于在 onTransact() 方法中确定调用的是哪个方法。

    onTransact() 方法

    该方法运行在服务端的Binder线程池中,当客户端跨进程调用服务器方法时,远程请求会通过系统的封装(Proxy)来调用该方法,然后由该方法根据方法id调用对应的方法,该方法的声明为:

    boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
    
    • code:调用的方法id
    • data:传递进来的数据
    • reply:需要返回的数据
    • flags:额外标记,正常传0(1:单向RPC,无返回值)
    • 返回值:Boolean类型,true表示调用成功,false表示调用失败,可以在这个方法内做权限控制。

    我们在《Android Service aidl使用及进阶》说过,非基本数据类型的数据的走向标记in、out、inout的作用,这里再次进行说明一下,因为以下内容需要明白他们的作用才能更好的理解。

    • in:服务端只能接收输入,不能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息,但是服务端进行修改之后,客户端不会发生改变,还是原来的。

    • out:服务端不能接收输入,只能输出给客户端。当客户端传递BookBean给服务端时,服务端获取到的BookBean对象之后,并不能获取BookBean里面的字段信息(都是默认值),但是服务端修改了这些值后,客户端的信息会发生改变,变成服务端修改之后的值。

    • inout:服务端既能接收输入,也能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息;服务端修改了这些值后,客户端的信息也会发生改变,变成服务端修改之后的值。

    下面对onTransact()内对方法 addBookIn()addBookOut() 方法具体说明:

    • TRANSACTION_addBookIn

      表示调用的是 addBookIn() 方法,该方法在 IRemoteBookBinder.aidl 中的定义是 void addBookIn(in BookBean bookBean);,注意关键字 in

        data.enforceInterface(descriptor); // 服务端标识,这个数据该由谁处理
        com.renj.service.bean.BookBean _arg0; // 声明一个新的对象
        if ((0 != data.readInt())) { // 判断data是否有数据需要读取
            // 根据传递进来的data创建一个新的对象并赋值给 _arg0
            _arg0 = com.renj.service.bean.BookBean.CREATOR.createFromParcel(data);
        } else {
            _arg0 = null;
        }
        this.addBookIn(_arg0); // 调用我们定义方法,具体由子类实现,将新的对象 _arg0 作为参数传递给方法
        reply.writeNoException(); // 没有发生异常
        return true; // 返回成功
      

      当确定调用的 addBookIn() 方法,先将 Binder 的唯一标识对数据标记,然后服务器创建一个新的对象,因为使用了 in 关键字,所以需要读取老的数据,然后调用抽象 addBookIn() 方法(由子类实现),并且将新建的对象作为参数传递,并没有将新的数据写入到 reply 中,因为没有 out 关键字。

    • TRANSACTION_addBookOut

      表示调用的是 addBookOut() 方法,该方法在 IRemoteBookBinder.aidl 中的定义是 void addBookOut(out BookBean bookBean);,注意关键字 out

        data.enforceInterface(descriptor); // 服务端标识,这个数据该由谁处理
        com.renj.service.bean.BookBean _arg0; // 声明一个新的对象
        _arg0 = new com.renj.service.bean.BookBean(); // 因为只有out关键字,所以不会传递老的对象中的数据
        this.addBookOut(_arg0); // 调用我们定义的方法,传递新的对象,所有字段都是默认值
        reply.writeNoException(); // 没有发生异常
        if ((_arg0 != null)) {
            reply.writeInt(1); // 当数据发送变化后,修改标记
            // 把变化后的数据写入 reply 中
            _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); 
        } else {
            reply.writeInt(0);
        }
        return true; // 返回成功
      

      当确定调用的 addBookOut() 方法,先将 Binder 的唯一标识对数据标记,然后服务器创建一个新的对象,因为使用了 out 关键字,所以只会创建一个包含默认值的东西,而不会读取老的数据,然后调用抽象 addBookOut() 方法(由子类实现),并且将新建的对象作为参数传递,方法调用完成后,就修改的数据写入 reply 中。

      对于IRemoteBookBinder.aidl 中的定义的 addBookInOut() 方法,也差不多。只是有 in 和 out 两个关键字,所以既能传入原始数据,也会返回修改后的数据。

    • TRANSACTION_getBookList

      表示调用的是 getBookList() 方法,该方法在 IRemoteBookBinder.aidl 中的定义是 List<BookBean> getBookList();

        data.enforceInterface(descriptor);
        java.util.List<com.renj.service.bean.BookBean> _result = this.getBookList();
        reply.writeNoException();
        reply.writeTypedList(_result);
      

      这个方法直接调用抽象 getBookList() 方法(由子类实现),然后将结果写入到 reply 中返回。

    setDefaultImpl()getDefaultImpl() 方法

    setDefaultImpl() 方法是设置默认aidl接口实现,当远程调用 transact() 方法(实际最终调用的就是内部类 Stub 的 onTransact() 方法)中的对应方法,返回结果为false,并且通过 getDefaultImpl() 方法获取到的默认实现不为 null 就会调用默认实现的对应方法。

    内部类 Stub 的内部类 Proxy

    private static class Proxy implements com.renj.service.aidl.IRemoteBookBinder {
            private android.os.IBinder mRemote;
    
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }
    
            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
    
            @Override
            public void addBookInOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((bookBean != null)) {
                        _data.writeInt(1);
                        bookBean.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    boolean _status = mRemote.transact(Stub.TRANSACTION_addBookInOut, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        getDefaultImpl().addBookInOut(bookBean);
                        return;
                    }
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        bookBean.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
    
            @Override
            public java.util.List<com.renj.service.bean.BookBean> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.renj.service.bean.BookBean> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getBookList();
                    }
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.renj.service.bean.BookBean.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
    
            public static com.renj.service.aidl.IRemoteBookBinder sDefaultImpl;
        }
    

    首先使用 Proxy 的情况是因为我们在 StubasInterface() 方法中判断了客户端和服务端是不是在同一个进程,不是在同一个进程时,才返回 Proxy 对象。

    构造方法

    在解析 asInterface() 方法时,就说明了Proxy 的构造方法需要一个参数 obj ,就是 onBind() 方法返回的Binder对象,也就是回调 onServiceConnected() 方法中的参数 service。

    asBinder()getInterfaceDescriptor()

    • asBinder()方法返回通过构造方法传递进来的 Binder 对象
    • getInterfaceDescriptor()方法返回 Stub 的 DESCRIPTOR 字段,Binder 的唯一标识。

    方法调用

    就是当我们在客户端通过 service 调用方法时,实际上调用的就是 Proxy 中的方法,理由通过前面的分析我们已经知道了(对asInterface(android.os.IBinder obj) 方法分析)。

    对于 Stub 类,我们分析了 addBookIn()addBookOut() 方法,那么在 Proxy 类中,我们分析一下 addBookInOut() 方法。

    • void addBookInOut(com.renj.service.bean.BookBean bookBean) 方法

        android.os.Parcel _data = android.os.Parcel.obtain();  // 创建参数对象
        android.os.Parcel _reply = android.os.Parcel.obtain(); // 创建返回值对象
        try {
            _data.writeInterfaceToken(DESCRIPTOR); // 唯一标识
            if ((bookBean != null)) { // 参数不为null时,将客户端传递的参数数据写到新建对象中(因为关键字为 inout)
                _data.writeInt(1);
                bookBean.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            // 通过 mRemote 调用 transact() 方法,传递调用方法id(Stub中定义的方法id),输入输出对象和标志位,
            // 这些参数就时 Stub#onTransact()方法所接收的参数
            boolean _status = mRemote.transact(Stub.TRANSACTION_addBookInOut, _data, _reply, 0);
            // Stub#onTransact()方法返回失败,并且设置了默认aidl接口实现,就调用默认的实现
            if (!_status && getDefaultImpl() != null) {
                getDefaultImpl().addBookInOut(bookBean);
                return;
            }
            _reply.readException();
            // 因为有是 inout 关键字,所以需要将修改后的结果重写更新到客户端的对象中
            if ((0 != _reply.readInt())) {
                bookBean.readFromParcel(_reply);
            }
        } finally {
            _reply.recycle();
            _data.recycle();
        }
      

      上面的代码中,已经对代码做了注释了,我们发现在 Proxy 中的方法内,其实就是对参数和结果数据的处理,并没有其他的逻辑,然后直接调用了 mRemotetransact() 方法,mRemote 就是 Binder,这个我们是知道的,那看看 Binder#transact() 方法做了什么操作了

        public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
                int flags) throws RemoteException {
            if (false) Log.v("Binder", "Transact: " + code + " to " + this);
      
            if (data != null) {
                data.setDataPosition(0);
            }
            // 调用了 onTransact 方法
            boolean r = onTransact(code, data, reply, flags);
            if (reply != null) {
                reply.setDataPosition(0);
            }
            return r;
        }
      

      Binder#transact() 中调用了 onTransact() 方法,这个方法也就是我们刚刚在分析 Stub 类时分析的方法。到了这里,我们就已经知道了客户端在跨进程调用服务端方法时的流程,以及 in、out和inout关键字作用的原理了。

    • java.util.List<com.renj.service.bean.BookBean> getBookList() 方法

        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.renj.service.bean.BookBean> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
            if (!_status && getDefaultImpl() != null) {
                return getDefaultImpl().getBookList();
            }
            _reply.readException();
            // 将返回 _reply 对象封装成客户端需要的类型
            _result = _reply.createTypedArrayList(com.renj.service.bean.BookBean.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
      

      addBookInOut() 方法类似,封装参数,然后调用mRemotetransact() 方法,最后将结果 _reply 对象封装成客户端需要的类型返回。

    最后放一张图表示跨进程调用的过程,如果了解了上面的内容,应该能够很容易看明白。

    Binder流程.png

    相关文章

      网友评论

        本文标题:Android Service aidl分析

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