美文网首页
aidl基础用法和原理解析

aidl基础用法和原理解析

作者: 坤_RTFSC | 来源:发表于2020-11-10 16:18 被阅读0次

    aidl是android中非常重要的进程间通信的方式,底层还是用的binder来实现的。今天就重点讲解一下aidl的使用和原理;

    基础使用

    aidl本质上也是采用的c/s架构,首先创建一个server端:

    server端

    • 1.右键生成aidl文件
    interface IMyAidlInterface {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
        String getName();
        void setName(String name);
    }
    

    basicTypes这个可以删掉的。这个方法是介绍了基础的用法,可以用哪些变量;后面就可以自定义方法了。

    • 2.创建service
    @Override
        public IBinder onBind(Intent intent) {
            return new Like();
        }
    
        class Like extends IMyAidlInterface.Stub{
            @Override
            public String getName() throws RemoteException {
                return "i'm yours";
            }
    
            @Override
            public void setName(String name) throws RemoteException {
            }
        }
    

    创建一个内部类,继承自aidl.stub。然后重写方法;
    然后在onBind方法中return 这个内部类的对象。

    Client端

    client端的写法就很简单了。

    Intent intent= new Intent(action);
            intent.setPackage(packagename);
            bindService(intent, new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            }, Context.BIND_AUTO_CREATE);
    

    就是一个bindservice的过程;这样两个app就能通信啦。怎么样,是不是很简单,接下来讲下实现的原理;

    原理分析

    要知道原理首先肯定得知道在编译aidl文件时,发生了什么。观察看目录知道其实是生成了一个同名的java文件,下面我们来研究这个java文件里都有啥。

    /**
        public static class Default implements com.qikun.demo.IMyAidlInterface {
            @Override
            public void setName(java.lang.String name) throws android.os.RemoteException {
            }
    
            @Override
            public java.lang.String getName() throws android.os.RemoteException {
                return null;
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return null;
            }
        }
    

    首先生成了一个默认的实现了此接口的类,叫default;
    重点看stub类,此类实现了IMyAidlInterface的接口并继承了binder。

    public static com.qikun.demo.IMyAidlInterface asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.qikun.demo.IMyAidlInterface))) {
                    return ((com.qikun.demo.IMyAidlInterface) iin);
                }
                return new com.qikun.demo.IMyAidlInterface.Stub.Proxy(obj);
            }
    

    将服务端的ibinder对象转化为客户端所需要的接口对象。

    如果有必要的话,产生一个代理对象Proxy。否则直接查询本地的对象,就解决了同一个进程里使用aidl的困境)
    来分析一下这个proxy代理对象是怎么回事:
    首先,proxy构造需要传入一个ibinder对象;

    @Override
                public java.lang.String getName() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    java.lang.String _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        boolean _status = mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
                        if (!_status && getDefaultImpl() != null) {
                            return getDefaultImpl().getName();
                        }
                        _reply.readException();
                        _result = _reply.readString();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    

    一个data,一个reply,都是parcel对象。注意最核心的代码:

    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
    

    mRemote其实就是proxy初始化的时候传入进来的binder对象。transact是什么方法呢?

    /**
         * Perform a generic operation with the object.
         * 
         * @param code The action to perform.  This should
         * be a number between {@link #FIRST_CALL_TRANSACTION} and
         * {@link #LAST_CALL_TRANSACTION}.
         * @param data Marshalled data to send to the target.  Must not be null.
         * If you are not sending any data, you must create an empty Parcel
         * that is given here.
         * @param reply Marshalled data to be received from the target.  May be
         * null if you are not interested in the return value.
         * @param flags Additional operation flags.  Either 0 for a normal
         * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
         *
         * @return Returns the result from {@link Binder#onTransact}.  A successful call
         * generally returns true; false generally means the transaction code was not
         * understood.
         */
        public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
            throws RemoteException;
    

    这里就从官方文档注释大概知道意思:

    • 1.code表示要执行什么动作;
    • 2.data和reply就是一个传入值一个返回值;
    • 3.flag只有0和flag_oneway,0表示client需要等待server返回,而flag_oneway表示这是一个单项的活动,无需等待。所以参数为0时表示可能会有阻塞,这里要多注意.

    okay,让我们再回到stub里面来,关注一下onTransact方法:

     @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 INTERFACE_TRANSACTION: {
                        reply.writeString(descriptor);
                        return true;
                    }
                    case TRANSACTION_setName: {
                        data.enforceInterface(descriptor);
                        java.lang.String _arg0;
                        _arg0 = data.readString();
                        this.setName(_arg0);
                        reply.writeNoException();
                        return true;
                    }
                    case TRANSACTION_getName: {
                        data.enforceInterface(descriptor);
                        java.lang.String _result = this.getName();
                        reply.writeNoException();
                        reply.writeString(_result);
                        return true;
                    }
                    default: {
                        return super.onTransact(code, data, reply, flags);
                    }
                }
            }
    

    可以看到stub根据不同的code去执行不同的方法;

    小结

    通过以上的源码分析能够得出一次aidl的IPC(inter-process communication)大致流程:

    • a.客户端通过bindService绑定服务端的service
    • b.拿到service之后拿到ibinder对象,再通过接口的Stub.asInterface将ibinder对象转化为接口对象。
    • c.拿到接口对象之后调用接口的api,api里通过binder.transact方法调起远程通信,如果flag是0,等待结果回来。
    • d.远程接收到后,通过写入参数到reply参数中去,然后再调用binder.transact方法返回。

    这里再稍微提一下binder机制。因为这个实在是太重要了对于android系统来说。
    首先android系统不同进程是不共享空间的,但是内核空间是共享的,因此binder 驱动层肯定是在内核空间的,相当于一个底层中转站的概念。后续将花大篇幅介绍binder。

    相关文章

      网友评论

          本文标题:aidl基础用法和原理解析

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