美文网首页
Android的AIDL介绍

Android的AIDL介绍

作者: ComeAsExpected | 来源:发表于2018-08-17 17:43 被阅读0次

    AIDL:Android Interface Definition Language,即Android接口定义语言。

    AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具,仅此而已。

    AIDL进行进程间通信的流程:

    1. 服务端
      服务端首先要创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

    2. 客户端
      客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

    3. AIDL接口的创建

    // IBookManager.aidl
    package com.wonder.myapp.tests.aidl;
    import com.wonder.myapp.tests.aidl.Book;
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);
    }
    

    系统为我们自动生成的Binder类如下:

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: C:\\workspace\\TestApp\\app\\src\\main\\aidl\\com\\wonder\\myapp\\tests\\aidl\\IBookManager.aidl
     */
    package com.wonder.myapp.tests.aidl;
    
    public interface IBookManager extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements com.wonder.myapp.tests.aidl.IBookManager {
            private static final java.lang.String DESCRIPTOR = "com.wonder.myapp.tests.aidl.IBookManager";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an com.wonder.myapp.tests.aidl.IBookManager interface,
             * generating a proxy if needed.
             */
            public static com.wonder.myapp.tests.aidl.IBookManager asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof com.wonder.myapp.tests.aidl.IBookManager))) {
                    return ((com.wonder.myapp.tests.aidl.IBookManager) iin);
                }
                return new com.wonder.myapp.tests.aidl.IBookManager.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_getBookList: {
                        data.enforceInterface(DESCRIPTOR);
                        java.util.List<com.wonder.myapp.tests.aidl.Book> _result = this.getBookList();
                        reply.writeNoException();
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addBook: {
                        data.enforceInterface(DESCRIPTOR);
                        com.wonder.myapp.tests.aidl.Book _arg0;
                        if ((0 != data.readInt())) {
                            _arg0 = com.wonder.myapp.tests.aidl.Book.CREATOR.createFromParcel(data);
                        } else {
                            _arg0 = null;
                        }
                        this.addBook(_arg0);
                        reply.writeNoException();
                        return true;
                    }
                }
                return super.onTransact(code, data, reply, flags);
            }
    
            private static class Proxy implements com.wonder.myapp.tests.aidl.IBookManager {
                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 java.util.List<com.wonder.myapp.tests.aidl.Book> 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.wonder.myapp.tests.aidl.Book> _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(com.wonder.myapp.tests.aidl.Book.CREATOR);
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    
                @Override
                public void addBook(com.wonder.myapp.tests.aidl.Book book) 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 ((book != null)) {
                            _data.writeInt(1);
                            book.writeToParcel(_data, 0);
                        } else {
                            _data.writeInt(0);
                        }
                        mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
            }
    
            static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        }
    
        public java.util.List<com.wonder.myapp.tests.aidl.Book> getBookList() throws android.os.RemoteException;
    
        public void addBook(com.wonder.myapp.tests.aidl.Book book) throws android.os.RemoteException;
    }
    
    Binder类结构图.png

    Binder工作机制分析:
    类的结构其实很简单,首先它声明了两个方法getBookList和addBook,正是在IBookManager.aidl中所声明的方法,同时还声明了两个整型的id标识这两个方法,用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走跨进程的transact过程,这个逻辑有Stub的内部代理类Proxy来完成。下面详细每个方法的含义。

    DESCRIPTOR
    Binder的唯一标识,一般用当前Binder的类名标识。

    asInterface(android.os.IBinder obj)
    用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么该方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象。

    asBinder()
    此方法用于返回当前Binder对象。

    onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
    这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(目标方法有参的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(目标方法有返回值的话)。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此可以利用这个特性做权限验证。

    Proxy # getBookList
    这个方法运行在客户端,当客户端远程调用此方法时,内部实现如下:首先创建该方法所需的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(有参的话),接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后,返回_reply中的数据。

    Proxy # addBook
    这个方法运行在客户端,执行过程和getBookList是一样的,不过这个方法没有返回值,不需要从_reply中取出数据。

    注意:

    • 客户端发起远程请求时,当前线程会挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,则不能在UI线程发起此远程请求;
    • 服务端的Binder方法运行在Binder的线程池中,所以不管Binder方法是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
    Binder的工作机制.jpg

    Binder是可能意外死亡的,这往往是由于服务端进程意外停止,为了程序的健壮性,需要重新连接服务。有如下方法:

    • linkToDeath()方法可用于注册一个DeathRecipient监听。DeathRecipient是一个接口,内部只有一个方法binderDied,当Binder死亡的时候,系统会回调该方法。该方法在客户端的Binder线程池中被回调;
    • 在onServiceDisconnected中重连远程服务,该方法在UI线程被回调。

    从安全性考虑,远程服务需要加入权限验证功能,避免客户端随意连接。AIDL的权限验证常用方法介绍:

    • onBind中进行验证,验证不通过返回null;
    • 在服务端的onTransact方法中进行验证,验证失败就返回false。

    补充知识:

    1. IBinder是轻量级远程调用机制的核心,高性能地进行进程内和进程间通信。不要直接实现这个接口,从Binder进行扩展。

    2. AIDL支持的所有数据类型

    • 基本数据类型(int、long、char、boolean、double等);
    • String 和 CharAequence;
    • List:只支持ArrayList,里面每个元素都必须被AIDL支持;
    • Map:只支持HashMap,里面每个元素都必须被AIDL支持;
    • Parcelable:所有实现Parcelable接口的对象;
    • AIDL:所有的AISL接口本身也可以在AIDL文件中使用。
      自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内;
      如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
      AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。要根据实际情况指定参数类型,不同的类型在底层实现上有不同的开销。
      AIDL接口中只支持方法,不支持声明静态常量。
    1. AIDL的包结构在服务端和客户端要保持一致,否则会运行出错。因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就不能成功反序列化,程序也就无法正常运行。

    2. 服务端有List、Map的时候,可以用CopyOnWriteArrayList、ConcurrentHashMap
      CopyOnWriteArrayList、ConcurrentHashMap支持并发读/写。
      前面有提到,AIDL中只支持ArrayList型的List,在这却推荐使用CopyOnWriteArrayList(不是继承自ArrayList),为什么能正常工作呢?因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端。

    3. 服务端管理客户端注册的监听器的时候,可以用RemoteCallbackList
      RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。它是一个泛型,支持管理任意的AIDL接口。它的工作原理为:在它的内部有一个Map结构,专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型。
      RemoteCallbackList可以在客户端进程终止后,自动移除客户端所注册的listener;RemoteCallbackList内部自动实现了线程同步的功能。
      出现原因:对象不能跨进程直接传输,对象的跨进程传输本质上是反序列化的过程。跨进程传输,客户端的同一个对象会在服务端生成一个新的不同的对象,但它们底层的Binder对象是同一个,所以RemoteCallbackList可以解决此问题。

    4. 客户端的onServiceConnected和onServiceDisconnected方法运行在UI线程。

    5. 服务端Binder的方法本身就运行在Binder线程池中,可以执行大量耗时操作,这个时候就不要在服务端方法中开启子线程去执行异步任务了,除非有特殊需求,否则不建议这么做。

    Demo代码

    package com.wonder.myapp.tests.service;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    /**
     * Created by zxh on 2018/7/23.     
     * Book.java
     */
    
    public class Book implements Parcelable {
    
        private int bookId;
        private String bookName;
    
        public Book(int bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        public int getBookId() {
            return bookId;
        }
    
        public void setBookId(int bookId) {
            this.bookId = bookId;
        }
    
        public String getBookName() {
            return bookName;
        }
    
        public void setBookName(String bookName) {
            this.bookName = bookName;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(bookId);
            dest.writeString(bookName);
        }
    
        public static final Creator<Book> CREATOR = new Creator<Book>() {
            @Override
            public Book createFromParcel(Parcel in) {
                return new Book(in);
            }
    
            @Override
            public Book[] newArray(int size) {
                return new Book[size];
            }
        };
    
        protected Book(Parcel in) {
            bookId = in.readInt();
            bookName = in.readString();
        }
    
        @Override
        public String toString() {
            return "Book{" +
                    "bookId=" + bookId +
                    ", bookName='" + bookName + '\'' +
                    '}';
        }
    }
    
    // Book.aidl
    package com.wonder.myapp.tests.service;
    
    parcelable Book;
    
    // IBookListChangeListener.aidl
    package com.wonder.myapp.tests.service;
    import com.wonder.myapp.tests.service.Book;
    
    interface IBookListChangeListener {
        void onNewBookArrived(in Book book);
    }
    
    // IBookManager.aidl
    package com.wonder.myapp.tests.service;
    import com.wonder.myapp.tests.service.Book;
    import com.wonder.myapp.tests.service.IBookListChangeListener;
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);
        void registerListener(IBookListChangeListener listener);
        void unregisterListener(IBookListChangeListener listener);
    }
    
    package com.wonder.myapp.tests.service;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.Looper;
    import android.os.RemoteCallbackList;
    import android.os.RemoteException;
    
    import com.wonder.myapp.bean.LogTag;
    import com.wonder.myapp.utils.LogUtils;
    
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class MyService extends Service {
    
        private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>();
        private RemoteCallbackList<IBookListChangeListener> callbackList = new RemoteCallbackList<>();
    
        //IBookManager.Stub的四个方法都是在子线程执行
        private Binder mBinder = new IBookManager.Stub() {
            @Override
            public List<Book> getBookList() throws RemoteException {
                LogUtils.d(LogTag.TAG_SERVICE, "getBookList " + (Looper.myLooper() == Looper.getMainLooper()));
                return bookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                bookList.add(book);
                int count = callbackList.beginBroadcast();
                for (int i = 0; i < count; i ++) {
                    callbackList.getBroadcastItem(i).onNewBookArrived(book);
                }
                callbackList.finishBroadcast();
            }
    
            @Override
            public void registerListener(IBookListChangeListener listener) throws RemoteException {
                callbackList.register(listener);
            }
    
            @Override
            public void unregisterListener(IBookListChangeListener listener) throws RemoteException {
                callbackList.unregister(listener);
            }
        };
    
        public MyService() {
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            LogUtils.d(LogTag.TAG_SERVICE, "onCreate " + (Looper.myLooper() == Looper.getMainLooper()));
    
            bookList.add(new Book(1, "语文书"));
            bookList.add(new Book(2, "数学书"));
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int j = 0; j < 10; j++) {
                            Thread.sleep(10 * 1000);
                            Book book = new Book(bookList.size() + 1, "Book # " + (bookList.size() + 1));
                            bookList.add(book);
                            int count = callbackList.beginBroadcast();
                            for (int i = 0; i < count; i++) {
                                try {
                                    callbackList.getBroadcastItem(i).onNewBookArrived(book);
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                            }
                            callbackList.finishBroadcast();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            LogUtils.d(LogTag.TAG_SERVICE, "onStartCommand startId = " + startId);
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            LogUtils.d(LogTag.TAG_SERVICE, "onBind");
            return mBinder;
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            LogUtils.d(LogTag.TAG_SERVICE, "onUnbind");
            return super.onUnbind(intent);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            LogUtils.d(LogTag.TAG_SERVICE, "onDestroy");
        }
    }
    
    package com.wonder.myapp.tests.service;
    
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.IBinder;
    import android.os.Looper;
    import android.os.RemoteException;
    import android.view.View;
    
    import com.wonder.myapp.R;
    import com.wonder.myapp.bean.LogTag;
    import com.wonder.myapp.ui.common.BaseActivity;
    import com.wonder.myapp.utils.LogUtils;
    
    import java.util.List;
    
    import butterknife.OnClick;
    
    public class ServiceTestActivity extends BaseActivity {
    
        private IBookManager mBinder;
        private IBookListChangeListener bookListChangeListener;
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                LogUtils.d(LogTag.TAG_SERVICE, "ServiceConnection onServiceConnected " + (Looper.myLooper() == Looper.getMainLooper()));
                mBinder = IBookManager.Stub.asInterface(service);
                try {
                    bookListChangeListener = new IBookListChangeListener.Stub() {
                        @Override
                        public void onNewBookArrived(Book book) throws RemoteException {
                            LogUtils.d(LogTag.TAG_SERVICE, "onNewBookArrived " + book.toString());
                        }
                    };
                    mBinder.registerListener(bookListChangeListener);
                    List<Book> list = mBinder.getBookList();
                    for (Book book : list) {
                        LogUtils.d(LogTag.TAG_SERVICE, "bookList :" + book.toString());
                    }
                    mBinder.addBook(new Book(3, "人民法院细则"));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                LogUtils.d(LogTag.TAG_SERVICE, "ServiceConnection onServiceDisconnected");
            }
        };
    
        @Override
        protected void setContentView() {
            setContentView(R.layout.activity_service_test);
        }
    
        @OnClick({R.id.service_start, R.id.service_stop, R.id.service_bind, R.id.service_unbind})
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.service_start:
                    LogUtils.d(LogTag.TAG_SERVICE, "click to startService");
                    startService(new Intent(this, MyService.class));
                    break;
                case R.id.service_stop:
                    LogUtils.d(LogTag.TAG_SERVICE, "click to stopService");
                    stopService(new Intent(this, MyService.class));
                    break;
                case R.id.service_bind:
                    LogUtils.d(LogTag.TAG_SERVICE, "click to bindService");
                    bindService(new Intent(this, MyService.class), connection, BIND_AUTO_CREATE);
                    break;
                case R.id.service_unbind:
                    LogUtils.d(LogTag.TAG_SERVICE, "click to unbindService");
                    //没有bindService直接unbindService会报错:java.lang.IllegalArgumentException: Service not registered:
                    unbindService(connection);
                    if (mBinder != null)
                        try {
                            mBinder.unregisterListener(bookListChangeListener);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    break;
                default:
                    break;
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Android的AIDL介绍

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