美文网首页Android进阶
IPC机制之AIDL的使用与原理

IPC机制之AIDL的使用与原理

作者: 小鱼爱记录 | 来源:发表于2017-04-19 15:11 被阅读395次

    AIDL概述

    AIDL是一个用于快速创建Binder的工具,没有AIDL文件也可以自己写Binder文件。

    Binder的工作机制

    各种IPC方式的优缺点和适用场景

    需求目标

    1. 客户端向服务端添加一本书,获取图书列表
    2. 服务提供注册和解除注册监听新书服务,并且每5秒添加一本书
    3. 客户端注册监听新书服务,同时将新书打印出来

    服务端

    1. 首先,创建一个Service用来监听客户端的连接请求
    2. 然后,创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明
    3. 最后,在Service中实现这个AIDL接口即可
    package com.soubu.ipcdemo.aidl;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.RemoteCallbackList;
    import android.os.RemoteException;
    import android.os.SystemClock;
    import android.support.annotation.Nullable;
    
    import com.soubu.ipcdemo.LogUtil;
    
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    /**
     * 作者:余天然 on 2017/4/19 上午10:45
     */
    public class BookManagerService extends Service {
    
        private AtomicBoolean isServiceDestoryed = new AtomicBoolean(false);
    
        private CopyOnWriteArrayList<Book> books = new CopyOnWriteArrayList<>();
    
        private RemoteCallbackList<IOnNewBookArrivedListener> listeners = new RemoteCallbackList<>();
    
        private Binder binder = new IBookManager.Stub() {
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                SystemClock.sleep(5000);
                return books;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                books.add(book);
            }
    
            @Override
            public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
                listeners.register(listener);
                LogUtil.print("listener:" + listener);
            }
    
            @Override
            public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
                listeners.unregister(listener);
                LogUtil.print("listener:" + listener);
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            books.add(new Book(1, "Androird"));
            books.add(new Book(2, "ios"));
            new Thread(new ServiceWorker()).start();
        }
    
        private class ServiceWorker implements Runnable {
    
            @Override
            public void run() {
                while (!isServiceDestoryed.get()) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int bookId = books.size() + 1;
                    Book newBook = new Book(bookId, "newBook#" + bookId);
                    try {
                        onNewBookArrived(newBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        private void onNewBookArrived(Book newBook) throws RemoteException {
            books.add(newBook);
            final int N = listeners.beginBroadcast();
            for (int i = 0; i < N; i++) {
                IOnNewBookArrivedListener listener = listeners.getBroadcastItem(i);
                if (listener != null) {
                    listener.onNewBookArriced(newBook);
                }
            }
            listeners.finishBroadcast();
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return binder;
        }
    
    }
    

    客户端

    1. 首先,绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型
    2. 接着就可以调用AIDL中的方法
    package com.soubu.ipcdemo.aidl;
    
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.IBinder;
    import android.os.Message;
    import android.os.RemoteException;
    import android.support.annotation.Nullable;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.TextView;
    
    import com.soubu.ipcdemo.LogUtil;
    import com.soubu.ipcdemo.R;
    
    import java.util.List;
    
    /**
     * 作者:余天然 on 2017/4/19 上午10:51
     */
    public class BookManagerActivity extends AppCompatActivity {
    
        private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
    
        private IBookManager bookManager;
    
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MESSAGE_NEW_BOOK_ARRIVED:
                        LogUtil.print("received new book :" + msg.obj);
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    
        private IOnNewBookArrivedListener onNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
            @Override
            public void onNewBookArriced(Book newBook) throws RemoteException {
                handler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
            }
        };
    
        private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
            @Override
            public void binderDied() {
                if (bookManager == null) {
                    return;
                }
                bookManager.asBinder().unlinkToDeath(deathRecipient, 0);
                bookManager = null;
                bindBookManagerService();
            }
        };
    
        private void bindBookManagerService() {
            Intent intent = new Intent(this, BookManagerService.class);
            bindService(intent, connection, Context.BIND_AUTO_CREATE);
        }
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                bookManager = IBookManager.Stub.asInterface(service);
                try {
                    service.linkToDeath(deathRecipient, 0);
                    bookManager.registerListener(onNewBookArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView tv = (TextView) findViewById(R.id.tv_send);
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    try {
                        invocateRemoteMethod();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            });
            bindBookManagerService();
        }
    
        private void invocateRemoteMethod() throws RemoteException {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (bookManager != null) {
                        List<Book> list = null;
                        try {
                            list = bookManager.getBookList();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        LogUtil.print("query book list :" + list.getClass().getCanonicalName());
                        LogUtil.print("query book list :" + list.toString());
                    }
    
                }
            }).start();
    
        }
    
        @Override
        protected void onDestroy() {
            if (bookManager != null && bookManager.asBinder().isBinderAlive()) {
                try {
                    bookManager.unregisterListener(onNewBookArrivedListener);
                    LogUtil.print("unregister listener:" + onNewBookArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            unbindService(connection);
            super.onDestroy();
        }
    }
    

    AIDL使用的注意点

    1. AIDL支持的数据类型:基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL;
    2. 某些类即使和AIDL文件在同一个包中也要显式import进来;
    3. AIDL中除了基本数据类,其他类型的参数都要标上方向:in、out或者inout;
    4. AIDL接口中支持方法,不支持声明静态变量;
    5. 为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。
    6. AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以要在AIDL方法中处理线程同步。
    7. RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。
    8. 客户端调用远程服务方法时,因为远程方法运行在服务端的binder线程池中,同时客户端线程会被挂起,所以如果该方法过于耗时,而客户端又是UI线程,会导致ANR,所以当确认该远程方法是耗时操作时,应避免客户端在UI线程中调用该方法。同理,当服务器调用客户端的listener方法时,该方法也运行在客户端的binder线程池中,所以如果该方法也是耗时操作,请确认运行在服务端的非UI线程中。另外,因为客户端的回调listener运行在binder线程池中,所以更新UI需要用到handler。
    9. 客户端通过IBinder.DeathRecipient来监听Binder死亡,也可以在onServiceDisconnected中监听并重连服务端。区别在于前者是在binder线程池中,访问UI需要用Handler,后者则是UI线程。
    10. AIDL可通过自定义权限在onBind或者onTransact中进行权限验证。

    测试

    我们可以看到Service是在Binder_2线程,每隔5秒,客户端确实会收到服务端的通知。


    当我们退出客户端时,确实会解除绑定。


    AIDL实际生成的代码

    我们创建的AIDL文件如下:


    Book.aidl

    package com.soubu.ipcdemo.aidl;
    
    parcelable Book;
    

    IBookManager.aidl

    package com.soubu.ipcdemo.aidl;
    
    import com.soubu.ipcdemo.aidl.Book;
    import com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener;
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);
        void registerListener(IOnNewBookArrivedListener listener);
        void unregisterListener(IOnNewBookArrivedListener listener);
    }
    

    IOnNewBookArrivedListener.aidl

    package com.soubu.ipcdemo.aidl;
    
    import com.soubu.ipcdemo.aidl.Book;
    
    interface IOnNewBookArrivedListener {
         void onNewBookArriced(in Book newBook);
    }
    

    然后,AIDL帮我们生成的代码如下:


    IBookManager.class

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.soubu.ipcdemo.aidl;
    
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.IInterface;
    import android.os.Parcel;
    import android.os.RemoteException;
    import com.soubu.ipcdemo.aidl.Book;
    import com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener;
    import java.util.ArrayList;
    import java.util.List;
    
    public interface IBookManager extends IInterface {
        List<Book> getBookList() throws RemoteException;
    
        void addBook(Book var1) throws RemoteException;
    
        void registerListener(IOnNewBookArrivedListener var1) throws RemoteException;
    
        void unregisterListener(IOnNewBookArrivedListener var1) throws RemoteException;
    
        public abstract static class Stub extends Binder implements IBookManager {
            private static final String DESCRIPTOR = "com.soubu.ipcdemo.aidl.IBookManager";
            static final int TRANSACTION_getBookList = 1;
            static final int TRANSACTION_addBook = 2;
            static final int TRANSACTION_registerListener = 3;
            static final int TRANSACTION_unregisterListener = 4;
    
            public Stub() {
                this.attachInterface(this, "com.soubu.ipcdemo.aidl.IBookManager");
            }
    
            public static IBookManager asInterface(IBinder obj) {
                if(obj == null) {
                    return null;
                } else {
                    IInterface iin = obj.queryLocalInterface("com.soubu.ipcdemo.aidl.IBookManager");
                    return (IBookManager)(iin != null && iin instanceof IBookManager?(IBookManager)iin:new IBookManager.Stub.Proxy(obj));
                }
            }
    
            public IBinder asBinder() {
                return this;
            }
    
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                IOnNewBookArrivedListener _arg0;
                switch(code) {
                case 1:
                    data.enforceInterface("com.soubu.ipcdemo.aidl.IBookManager");
                    List _arg02 = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_arg02);
                    return true;
                case 2:
                    data.enforceInterface("com.soubu.ipcdemo.aidl.IBookManager");
                    Book _arg01;
                    if(0 != data.readInt()) {
                        _arg01 = (Book)Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg01 = null;
                    }
    
                    this.addBook(_arg01);
                    reply.writeNoException();
                    return true;
                case 3:
                    data.enforceInterface("com.soubu.ipcdemo.aidl.IBookManager");
                    _arg0 = com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
                    this.registerListener(_arg0);
                    reply.writeNoException();
                    return true;
                case 4:
                    data.enforceInterface("com.soubu.ipcdemo.aidl.IBookManager");
                    _arg0 = com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
                    this.unregisterListener(_arg0);
                    reply.writeNoException();
                    return true;
                case 1598968902:
                    reply.writeString("com.soubu.ipcdemo.aidl.IBookManager");
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
                }
            }
    
            private static class Proxy implements IBookManager {
                private IBinder mRemote;
    
                Proxy(IBinder remote) {
                    this.mRemote = remote;
                }
    
                public IBinder asBinder() {
                    return this.mRemote;
                }
    
                public String getInterfaceDescriptor() {
                    return "com.soubu.ipcdemo.aidl.IBookManager";
                }
    
                public List<Book> getBookList() throws RemoteException {
                    Parcel _data = Parcel.obtain();
                    Parcel _reply = Parcel.obtain();
    
                    ArrayList _result;
                    try {
                        _data.writeInterfaceToken("com.soubu.ipcdemo.aidl.IBookManager");
                        this.mRemote.transact(1, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(Book.CREATOR);
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
    
                    return _result;
                }
    
                public void addBook(Book book) throws RemoteException {
                    Parcel _data = Parcel.obtain();
                    Parcel _reply = Parcel.obtain();
    
                    try {
                        _data.writeInterfaceToken("com.soubu.ipcdemo.aidl.IBookManager");
                        if(book != null) {
                            _data.writeInt(1);
                            book.writeToParcel(_data, 0);
                        } else {
                            _data.writeInt(0);
                        }
    
                        this.mRemote.transact(2, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
    
                }
    
                public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
                    Parcel _data = Parcel.obtain();
                    Parcel _reply = Parcel.obtain();
    
                    try {
                        _data.writeInterfaceToken("com.soubu.ipcdemo.aidl.IBookManager");
                        _data.writeStrongBinder(listener != null?listener.asBinder():null);
                        this.mRemote.transact(3, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
    
                }
    
                public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
                    Parcel _data = Parcel.obtain();
                    Parcel _reply = Parcel.obtain();
    
                    try {
                        _data.writeInterfaceToken("com.soubu.ipcdemo.aidl.IBookManager");
                        _data.writeStrongBinder(listener != null?listener.asBinder():null);
                        this.mRemote.transact(4, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
    
                }
            }
        }
    }
    

    IOnNewBookArrivedListener.class

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    package com.soubu.ipcdemo.aidl;
    
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.IInterface;
    import android.os.Parcel;
    import android.os.RemoteException;
    import com.soubu.ipcdemo.aidl.Book;
    
    public interface IOnNewBookArrivedListener extends IInterface {
        void onNewBookArriced(Book var1) throws RemoteException;
    
        public abstract static class Stub extends Binder implements IOnNewBookArrivedListener {
            private static final String DESCRIPTOR = "com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener";
            static final int TRANSACTION_onNewBookArriced = 1;
    
            public Stub() {
                this.attachInterface(this, "com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener");
            }
    
            public static IOnNewBookArrivedListener asInterface(IBinder obj) {
                if(obj == null) {
                    return null;
                } else {
                    IInterface iin = obj.queryLocalInterface("com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener");
                    return (IOnNewBookArrivedListener)(iin != null && iin instanceof IOnNewBookArrivedListener?(IOnNewBookArrivedListener)iin:new IOnNewBookArrivedListener.Stub.Proxy(obj));
                }
            }
    
            public IBinder asBinder() {
                return this;
            }
    
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                switch(code) {
                case 1:
                    data.enforceInterface("com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener");
                    Book _arg0;
                    if(0 != data.readInt()) {
                        _arg0 = (Book)Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
    
                    this.onNewBookArriced(_arg0);
                    reply.writeNoException();
                    return true;
                case 1598968902:
                    reply.writeString("com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener");
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
                }
            }
    
            private static class Proxy implements IOnNewBookArrivedListener {
                private IBinder mRemote;
    
                Proxy(IBinder remote) {
                    this.mRemote = remote;
                }
    
                public IBinder asBinder() {
                    return this.mRemote;
                }
    
                public String getInterfaceDescriptor() {
                    return "com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener";
                }
    
                public void onNewBookArriced(Book newBook) throws RemoteException {
                    Parcel _data = Parcel.obtain();
                    Parcel _reply = Parcel.obtain();
    
                    try {
                        _data.writeInterfaceToken("com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener");
                        if(newBook != null) {
                            _data.writeInt(1);
                            newBook.writeToParcel(_data, 0);
                        } else {
                            _data.writeInt(0);
                        }
    
                        this.mRemote.transact(1, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
    
                }
            }
        }
    }
    

    如果你觉得自己很牛逼的话,其实也是可以自己手写Binder的。不过要想自己写,还是得先好好地学习下AIDL自动生成代码的具体逻辑。

    其实我们查看这个根据AIDL自动生成的代码就会发现,就是在AIDL定义的接口之外,又增加了两个实现接口的类,Proxy/Stub。

    这里呢,我们以IOnNewBookArrivedListener生成的代码为例,来分析下这种Proxy/Stub模式。

    首先看看Proxy的实现,首先是将远程的Binder作为参数传入进来,再来看看 onNewBookArriced() 这个方法里面的下面几个步骤。首先是将newBook的参数写入到_data中去,同时在 远程binder 调用结束后,得到返回的 _reply ,在没有异常的情况下,返回_reply的result结果。(这里,onNewBookArriced()方法没有返回值,不过,前面的getBookList()是有返回值的,大家可以看看它的实现。)

    _data.writeInterfaceToken("com.soubu.ipcdemo.aidl.IOnNewBookArrivedListener");
    newBook.writeToParcel(_data, 0);
    this.mRemote.transact(1, _data, _reply, 0);
    _reply.readException();
    

    这里可以粗略地将 Binder 看做远程服务抛出的句柄,通过这个句柄就可以执行相应的方法了。这里需要额外说明的是,写入和传输的数据都是 Parcelable,Android Framework 中提供的。

    再来看看Stub里面的实现,Stub本身继承了Binder抽象类,本身将作为一个句柄,实现在服务端,但传递给客户端使用。同样也看看 onTransact里面的方法,首先是通过 (Book)Book.CREATOR.createFromParcel(data) 读取 _arg0 参数,在this.onNewBookArriced(_arg0)计算过后,将结果写入到reply中。

    细心的读者,可以从上面的描述中,发现一些有意思的东西:

    Proxy 是写入参数,读取值;
    Stub 是读取参数,写入值。

    正好是一对,那因此我们是不是可以做出这样的论断呢?Proxy 和 Stub 操作的是一份数据?恭喜你,答对了。

    用户空间和内核空间是互相隔离的,客户端和服务端在同一用户空间,而 Binder Driver 在内核空间中,常见的方式是通过 copy_from_user 和 copy_to_user 两个系统调用来完成,但 Android Framework 考虑到这种方式涉及到两次内存拷贝,在嵌入式系统中不是很合适,于是 Binder Framework 通过 ioctl 系统调用,直接在内核态进行了相关的操作,节省了宝贵的空间。

    可能大家也注意到 Proxy 这里是private权限的,外部是无法访问的,但这里是 Android 有意为之,抛出了 asInterface 方法,这样避免了对 Proxy可能的修改。

    总结一下Proxy/Stub的具体流程,其实,客户端主要就是调用了Proxy,而服务端主要就是实现了Stub:

    我们需要注意到如果客户端和服务端在同一个进程下,那么此时就没有跨进程通讯了,asInterface()将返回Stub对象本身,否则才返回Stub.Proxy对象。

    上面的分析都是基于代码细节的,可能对Proxy/Stub没有一个整体性的把握的话,很难吃透。这里再举一个生活点的栗子。

    自动售货机的故事

    夏天一到,天气也变得炎热,地铁旁边的自动售货机开始有更多的人关顾了。那么售货机是怎么工作的了?通过对自动售货机的分析,可以对Binder的Proxy/Stub 模式更好地理解。

    和我们打交道得是售货机,而不是背后的零售商,道理很简单,零售商的位置是固定的,也就意味着有很大的交通成本,而和售货机打交道就轻松很多,毕竟售货机就在身边。因而从客户端的角度上看,只需要知道售货机即可。

    再从零售商的角度来看,要和为数众多的售货机打交道也是不容易的事情,需要大量的维护和更新成本,如果将其交由另一个中介公司,就能够省心不少。零售商只关心这个中介公司,按时发货,检查营收,这就是所有它应该完成的工作。

    如上图所示,在 Binder Framework 中也采用了类似的结构,

    Proxy=售货机
    Stub=中介公司

    在这样的设计下,客户端只需要和 Proxy 打交道,服务端只需要和 Stub 打交道,条理清晰很多。这种模式又被称为 Proxy / Stub 模式,这种模式也值得我们在日后的开发中好好借鉴一下。

    参考目录:

    1. Android Binder 全解析(3) -- AIDL原理剖析
    2. 《Android开发艺术探索》笔记(二)——IPC进程间通信
    3. 从IBinder接口学习Proxy-Stub设计模式

    相关文章

      网友评论

        本文标题:IPC机制之AIDL的使用与原理

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