Android温故而知新 - AIDL

作者: 嘉伟咯 | 来源:发表于2017-09-26 01:20 被阅读113次

    这篇文章让我们一起来复习一下aidl

    aidl的简单用法

    aidl的用法是很简单的。首先创建IDemoAidlInterface.aidl文件(在服务端工程和客户端工程中需要分别定义一个相同的aidl文件):

    package linjw.demo.aidldemo;
    
    interface IDemoAidlInterface {
        int add(int a, int b);
    }
    

    然后在service.onBind()中创建一个IDemoAidlInterface.Stub返回:

    public class DemoService extends Service {
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new IDemoAidlInterface.Stub() {
                @Override
                public int add(int a, int b) throws RemoteException {
                    return a + b;
                }
            };
        }
    }
    

    这样在bindService的时候就能获得一个IDemoAidlInterface,就可以通过它去调用其他进程中的方法获取数据了:

    public class MainActivity extends AppCompatActivity {
        public static final String TAG = "AIDLDemo";
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IDemoAidlInterface aidl = IDemoAidlInterface.Stub.asInterface(service);
                try {
                    Log.d(TAG, "1 + 2 = " + aidl.add(1, 2));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent(this, DemoService.class);
            bindService(intent, mConnection, BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(mConnection);
        }
    }
    

    aidl的原理

    但是aidl文件又是个什么东西?aidl又到底是怎样工作的呢?

    aidl是Android Interface definition language的缩写,实际上它是一中领域特定语言即domain-specific languages,简称DSL,aidl的作用领域是定义安卓接口。感兴趣的同学可以自己去找一下DSL的相关概念,这里就不展开讨论了。

    aidl底层是通过binder机制实现的,而且不同需求的binder通信实际上代码是有很大的相似性的。厉害的程序员通常是懒惰的程序员,好的ide通常也会提供各种强大的功能帮助程序员去偷懒。

    aidl就是一种帮助我们简化安卓进程间通信代码的工具。android studio会根据aidl定义的接口,帮我们自动生成安卓进程间通信的代码,而我们只需要直接使用它生成的代码就好了,而不用自己去写。

    让我们看看aidl究竟帮我们生成了什么样的代码:

    public interface IDemoAidlInterface extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements linjw.demo.aidldemo.IDemoAidlInterface {
            private static final java.lang.String DESCRIPTOR = "linjw.demo.aidldemo.IDemoAidlInterface";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an linjw.demo.aidldemo.IDemoAidlInterface interface,
             * generating a proxy if needed.
             */
            public static linjw.demo.aidldemo.IDemoAidlInterface asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof linjw.demo.aidldemo.IDemoAidlInterface))) {
                    return ((linjw.demo.aidldemo.IDemoAidlInterface) iin);
                }
                return new linjw.demo.aidldemo.IDemoAidlInterface.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 {
                switch (code) {
                    case INTERFACE_TRANSACTION: {
                        reply.writeString(DESCRIPTOR);
                        return true;
                    }
                    case TRANSACTION_add: {
                        data.enforceInterface(DESCRIPTOR);
                        int _arg0;
                        _arg0 = data.readInt();
                        int _arg1;
                        _arg1 = data.readInt();
                        int _result = this.add(_arg0, _arg1);
                        reply.writeNoException();
                        reply.writeInt(_result);
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
    
            private static class Proxy implements linjw.demo.aidldemo.IDemoAidlInterface {
                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 int add(int a, int b) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    int _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeInt(a);
                        _data.writeInt(b);
                        mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.readInt();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
            }
    
            static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    
        public int add(int a, int b) throws android.os.RemoteException;
    }
    

    它帮忙我们生成了我们在aidl中定义的IDemoAidlInterface接口,并且生成了一个抽象内部类IDemoAidlInterface.Stub去实现和安卓进程间通信相关的代码。而我们只需要继承IDemoAidlInterface.Stub实现具体的业务代码(add方法)就好:

    public interface IDemoAidlInterface extends android.os.IInterface {
        public static abstract class Stub
                extends android.os.Binder
                implements linjw.demo.aidldemo.IDemoAidlInterface {
            ...
        }
        public int add(int a, int b) throws android.os.RemoteException;
    }
    
    

    服务端通信原理

    在服务端,我们只需要继承IDemoAidlInterface.Stub并完成add方法的功能代码就可以了。当客户端通过aidl调用服务端代码的时候,服务端的add方法就会被调用:

    public IBinder onBind(Intent intent) {
        return new IDemoAidlInterface.Stub() {
            @Override
            public int add(int a, int b) throws RemoteException {
                return a + b;
            }
        };
    }
    

    但是add究竟是为什么会被调用的呢?奥秘就在IDemoAidlInterface.Stub.onTransact()方法中。onTransact是android.os.Binder的一个方法。客户端将想要调用的服务端的方法、参数等序列化之后通过系统级别的Binder驱动程序传给服务端,然后服务端在将它们反序列化获取想要调用的方法还有传入的参数。而onTransact就是这个反序列化的方法:

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_add: {
                data.enforceInterface(DESCRIPTOR);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.add(_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    

    code就代表了客户端想要执行的操作,当它是TRANSACTION_add的时候就代表客户端想调用服务端的add方法。可以从传过来的Parcel中反序列化出传入的两个相加数,然后调用实际的add方法,即this.add(_arg0, _arg1),最后将计算出来的值写入reply中序列化之后传回给客户端。客户端就可以从这个reply中反序列化中出计算的结果。

    TRANSACTION_add是一个int,是IDemoAidlInterface接口定义的第一个方法:

    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    

    客户端原理

    客户端的代码比服务端会复杂一点点。首先从IDemoAidlInterface.Stub.asInterface方法开始看,我们可以通过它获取到一个IDemoAidlInterface:

    IDemoAidlInterface aidl = IDemoAidlInterface.Stub.asInterface(service);
    

    它的代码是这样的:

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

    它会判断传进来的IBinder是不是IDemoAidlInterface的一个实例,如果是的话就直接将它返回,不是的会就会用它去创建一个代理。

    但这个判断有什么用呢?什么时候IBinder它会是一个IDemoAidlInterface的实例什么时候又不是呢?

    我们写的service不外乎给其他应用使用和给应用内部使用。给其他应用使用的service因为进程不同不能直接传递对象,所以需要将一个对象先序列化再反序列化去实现进程间的传递。但是有一些服务比如播放器的播放服务,很多时候就只是应用内部在使用而已,是进程内的通信(或者说只是线程间的通信)。其实不涉及跨进程通信,可以直接传递,不用经过序列化和反序列化这样耗时的操作。

    如果是进程内的通信,传入的IBinder其实是IDemoAidlInterface的一个实例,所以直接返回将它返回就好。但如果是进程间的通信,就不会是是IDemoAidlInterface的实例了,而是一个用于进程间通信的对象了(具体是什么我们可以不用关心)。这个对象没有实现IDemoAidlInterface.add()方法,所以需要通过一些特殊的手段调用到服务端的add方法:

    private static class Proxy implements linjw.demo.aidldemo.IDemoAidlInterface {
            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 int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
    
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    

    它最重要的代码是IDemoAidlInterface.Stub.Proxy.add()这个方法。它将传入的a、b参数序列化到_data这个Parcel中,然后再通过之前传入的IBinder的transact()将它们传递到service中。注意看,我们还指定了调用Stub.TRANSACTION_add这个方法。上一节服务端获取到的code就是这里指定的。然后服务端将计算到的结果序列化到_reply中,客户的这里再将_reply反序列化得到计算结果返回。

    aidl原理图

    aidl的原理可以用下面的图来表示:

    aidl原理图.png

    使用aidl传递复杂数据类型

    有时候我需要传递一些复杂的数据类型比如自定义的类,aidl也是支持的。但是因为aidl传递数据都是通过序列化实现的,所以aidl要求传递的类必须实现Parcelable接口。比如我们定义一个Data类:

    public class Data implements Parcelable {
        public String data;
    
        public Data() {
        }
    
        public Data(String data) {
            this.data = data;
        }
    
        protected Data(Parcel in) {
            data = in.readString();
        }
    
        public static final Creator<Data> CREATOR = new Creator<Data>() {
            @Override
            public Data createFromParcel(Parcel in) {
                return new Data(in);
            }
    
            @Override
            public Data[] newArray(int size) {
                return new Data[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
        }
    
        public void readFromParcel(Parcel in) {
            data = in.readString();
        }
    }
    

    然后需要新建一个Data.aidl文件声明这个类:

    package linjw.demo.aidldemo;
    parcelable Data;
    

    最后在IDemoAidlInterface.aidl中添加接口:

    package linjw.demo.aidldemo;
    import linjw.demo.aidldemo.Data;
    
    interface IDemoAidlInterface {
        int add(int a, int b);
    
        void setData(in Data data);
    
        void getData(out Data data);
    }
    

    输入参数和输出参数

    相信大家都看到了in、out这两个关键字了,他们是用来标识这个参数是输入参数还是输出参数的。我们直接可以看一下生成的代码可以很容易看出他们的作用,先看看服务端的Stub. onTransact()方法:

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_add: {
                data.enforceInterface(DESCRIPTOR);
                int _arg0;
                _arg0 = data.readInt();
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.add(_arg0, _arg1);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            case TRANSACTION_setData: {
                data.enforceInterface(DESCRIPTOR);
                linjw.demo.aidldemo.Data _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = linjw.demo.aidldemo.Data.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.setData(_arg0);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_getData: {
                data.enforceInterface(DESCRIPTOR);
                linjw.demo.aidldemo.Data _arg0;
                _arg0 = new linjw.demo.aidldemo.Data();
                this.getData(_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;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    

    setData只是简单的将参数返序列化出来传给功能代码,但是getData除了调用功能代码之外,还会将返回值写入reply中传回给客户端。

    同时我们也注意到了服务端所有的调用都是在onTransact中分配的,所以需要一个code去标识客户端到底想要调用的是哪一个方法。

    我们再来看看客户端的生成代码,也能看到getData方法有从_reply中反序列化出Data来:

    private static class Proxy implements linjw.demo.aidldemo.IDemoAidlInterface {
        
        ...
        
        @Override
        public void setData(linjw.demo.aidldemo.Data data) 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 ((data != null)) {
                    _data.writeInt(1);
                    data.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_setData, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    
        @Override
        public void getData(linjw.demo.aidldemo.Data data) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getData, _data, _reply, 0);
                _reply.readException();
                if ((0 != _reply.readInt())) {
                    data.readFromParcel(_reply);
                }
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:Android温故而知新 - AIDL

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