美文网首页
Android AIDL(Binder) 原理分析

Android AIDL(Binder) 原理分析

作者: christian_zs | 来源:发表于2018-07-10 22:17 被阅读145次

    首先介绍 Android 序列化机制、Binder 工作原理,然后再通过创建 AIDL 进行进程间通信,一步步分析 AIDL 原理还有工作流程。

    前言

    在这里首先介绍 Android 序列化机制、Binder 主要是因为 AIDL 与这两个家伙密切相关,所以我们先了解一下他们,这样有助于我们更好的了解 AIDL 工作原理。

    Android 序列化机制

    在 Android 系统中关于序列化的方法一般有两种,分别是实现 Serializable 接口和 Parcelable 接口

    • Serializable 是来自 Java 中的序列化接口
    • Parcelable 是 Android 自带的序列化接口

    上述的两种序列化接口都有各自不同的优缺点,我们在实际使用时需根据不同情况而定。Serializable 的使用比较简单,创建一个版本号即可;而 Parcelable 则相对复杂一些,会有四个方法需要实现。一般在保存数据到 SD 卡或者网络传输时建议使用 Serializable 即可,虽然效率差一些,好在使用方便。而在运行时数据传递时建议使用 Parcelable,比如 Intent,Bundle 等,Android 底层做了优化处理,效率很高。因此我们下面主要看一下 Parcelable。

    Parcelable 使用与分析

    首先我们创建一个 Book 对象并且实现 Parcelable接口。

    package com.example.zs.ipcdemo.aidl;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Book implements Parcelable {
        private String bookId;
        private String bookName;
    
        public String getBookId() {
            return bookId;
        }
    
        public void setBookId(String bookId) {
            this.bookId = bookId;
        }
    
        public String getBookName() {
            return bookName;
        }
    
        public void setBookName(String bookName) {
            this.bookName = bookName;
        }
    
        public static Creator<Book> getCREATOR() {
            return CREATOR;
        }
    
        public Book(String bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        protected Book(Parcel in) {
            bookId = in.readString();
            bookName = in.readString();
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(bookId);
            dest.writeString(bookName);
        }
    
        public void readFromParcel(Parcel in) {
            bookId = in.readString();
            bookName = in.readString();
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        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];
            }
        };
    }
    
    

    我们通过代码可以发现,在实现序列化的过程中主要的功能有序列化、反序列、描述内容,我们可以看出来其实内部都是 Parcel 一系列的 readStringwriteString 方法来实现的,描述内容大部分都返回 0 。Parcel 就是一个存放读取数据的容器,可以在 Binder 中自由传输。这里不对 Parcel 做详细的讲解了有兴趣的可以看看这这篇博客 Android 中 Parcel的分析以及使用

    Binder

    简单来说 Binder 是 Android 中的一个类,它继承了 IBinder 接口。下面我们从不同角度来说一下 Binder。

    • 从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,该通信方式在 linux 中没有。

    • 从 Android Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager,etc)和相应 ManagerService 的桥梁。

    • 从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当你 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 的服务。

    分析 Binder 组成结构

    摘自田维术的博客

    图中的 ServiceManager,负责把 Binder Server 注册到一个容器中,当客户端调用 Server 时 ,Binder驱动去先去 Binder Server 容器中找你要调用的 Server 通知他做那些那些事情。

    分析 Binder 通讯流程

    摘自田维术的博客

    从图中我们可以看到,Client 想要直接调用 Server 的 add 方法,是不可以的,因为它们在不同的进程中,这时候就需要 Binder 来帮忙了。

    • 第一步 Server 在 ServiceManager 这个容器中注册。
    • 第二部 Client 想要调用 Server 的 add 方法,就需要先获取 Server 对象, 但是 ServiceManager 不会把真正的 Server 对象返回给 Client,而是把 Server 的一个代理对象返回给 Client ,也就是 Proxy 。
    • 第三部 Client 调用 Proxy 的 add 方法,ServiceManager 会帮他去调用 Server 的 add 方法,并把结果返回给 Client 。

    AIDL

    我们的主角 AIDL 终于登场了,我们先创建一个简单的 AIDL 然后逐步分析它的工作原理。

    创建 Book 类

    在主包下面建一个 aidl 包专门存放进程通讯用到的数据, Book 类继承 Parcelable 接口(原因:AIDL 只能传送继承 Parcelable 接口的类)

    package com.example.zs.ipcdemo.aidl;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Book implements Parcelable {
        private String bookId;
        private String bookName;
    
        public String getBookId() {
            return bookId;
        }
    
        public void setBookId(String bookId) {
            this.bookId = bookId;
        }
    
        public String getBookName() {
            return bookName;
        }
    
        public void setBookName(String bookName) {
            this.bookName = bookName;
        }
    
        public static Creator<Book> getCREATOR() {
            return CREATOR;
        }
    
        public Book(String bookId, String bookName) {
            this.bookId = bookId;
            this.bookName = bookName;
        }
    
        protected Book(Parcel in) {
            bookId = in.readString();
            bookName = in.readString();
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(bookId);
            dest.writeString(bookName);
        }
    
        public void readFromParcel(Parcel in) {
            bookId = in.readString();
            bookName = in.readString();
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        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];
            }
        };
    }
    
    

    创建 Book.aidl 、IBookManager.aidl

    为什么要有 Book.aidl 类,因为只有将类在 aidl 中声明时候,AIDL 才能调用 Book 类。所以说要让 AIDL 能够传送自定义类需要 1、继承 Parcelable 接口 2、创建同名 .aidl 文件声明自己。

    package com.example.zs.ipcdemo.aidl;
    
    parcelable Book;
    

    IBookManager.aidl 设置让客户端允许调用的接口。

    package com.example.zs.ipcdemo.aidl;
    
    import com.example.zs.ipcdemo.aidl.Book;
    
    interface IBookManager{
        List<Book> getBookList();
        void addBook(in Book book);
    }
    

    这里面有个地方要注意一下 addBook 方法中有 in 这个类型,下面我们简单说一下 AIDL 文件中 in 类型out 类型和 inout 数据的区别。

    • in :客户端的参数输入:是把实参的值赋值给行参 那么对行参的修改,不会影响实参的值 。
    • out :服务端的参数输入:传递以后,行参和实参都是同一个对象,只是他们名字不同而已,对行参的修改将影响实参的值。
    • inout :这个可以叫输入输出参数,客户端可输入、服务端也可输入。客户端输入了参数到服务端后,服务端也可对该参数进行修改等,最后在客户端上得到的是服务端输出的参数。

    我们手动 ReBuild 一下我们的项目系统在这个目录下app\build\generated\source\aidl\debug 为我们自动生成 IBookManger.java ,接下来让我们看一下 IBookManger.java 的庐山真面目。

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: E:\\GitHub\\IPCDemo\\app\\src\\main\\aidl\\com\\example\\zs\\ipcdemo\\aidl\\IBookManager.aidl
     */
    package com.example.zs.ipcdemo.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.example.zs.ipcdemo.aidl.IBookManager {
            private static final java.lang.String DESCRIPTOR = "com.example.zs.ipcdemo.aidl.IBookManager";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an com.example.zs.ipcdemo.aidl.IBookManager interface,
             * generating a proxy if needed.
             */
            public static com.example.zs.ipcdemo.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.example.zs.ipcdemo.aidl.IBookManager))) {
                    return ((com.example.zs.ipcdemo.aidl.IBookManager) iin);
                }
                return new com.example.zs.ipcdemo.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.example.zs.ipcdemo.aidl.Book> _result = this.getBookList();
                        reply.writeNoException();
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addBook: {
                        data.enforceInterface(DESCRIPTOR);
                        com.example.zs.ipcdemo.aidl.Book _arg0;
                        if ((0 != data.readInt())) {
                            _arg0 = com.example.zs.ipcdemo.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.example.zs.ipcdemo.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.example.zs.ipcdemo.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.example.zs.ipcdemo.aidl.Book> _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(com.example.zs.ipcdemo.aidl.Book.CREATOR);
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    
                @Override
                public void addBook(com.example.zs.ipcdemo.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();
                    }
                }
    
                @Override
                public void registerListener(com.example.zs.ipcdemo.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
                        mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
                        _reply.readException();
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                }
    
                @Override
                public void unregisterListener(com.example.zs.ipcdemo.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        _data.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
                        mRemote.transact(Stub.TRANSACTION_unregisterListener, _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.example.zs.ipcdemo.aidl.Book> getBookList() throws android.os.RemoteException;
    
        public void addBook(com.example.zs.ipcdemo.aidl.Book book) throws android.os.RemoteException;
    }
    
    

    刚看有点蒙逼,别急我们一步一步分析,IBookManager 文件中,包括 IBookManager.aidl 的两个接口,以及 Stub 和 Proxy 两个实现了 IBookManager 接口的类,其中 Stub 是定义在 IBookManager 接口中的,而 Proxy 则定义在 Stub 类中。


    IBookManager 类型结构分析

    Client 执行流程

    1、当服务连接建立完成后,客户端执行代码。IBookManager.Stub.asInterface(service) 这句话方法的作用是判断传入的参数 IBinder 对象和自己是否在同一个进程如果不是则把这个IBinder 参数包装成一个 Proxy 对象,这时调用 Stub 的 sum 方法,间接调用 Proxy 的 getBookList() 方法,我看一下代码。

    // 连接服务
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(final ComponentName name, IBinder service) {
                //  IBinder 通过 asInterface 判断
                //  asInterface方法的作用是判断参数——也就是IBinder对象,和自己是否在同一个进程:
                //  是: 则直接转换、直接使用,接下来就跟 Binder 跨进程通信无关啦
                //  否: 则把这个IBinder参数包装成一个 Proxy 对象,这时调用 Stub 的方法,间接调用Proxy的方法
                IBookManager bookManager = IBookManager.Stub.asInterface(service);
                try {
                    mBookManager = bookManager;
                    bookManager.getBookList();
                    Log.d(TAG, "get books count:" + bookManager.getBookList().size());
                    bookManager.registerListener(arrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mBookManager = null;
            }
        };
    
    public static com.example.zs.ipcdemo.aidl.IBookManager asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                // 作用是判断传入的参数 IBinder 对象和自己是否在同一个进程
                if (((iin != null) && (iin instanceof com.example.zs.ipcdemo.aidl.IBookManager))) {
                    return ((com.example.zs.ipcdemo.aidl.IBookManager) iin);
                }
                return new com.example.zs.ipcdemo.aidl.IBookManager.Stub.Proxy(obj);
            }
    

    2、Proxy 在自己的 sumgetBookList() 方法中,会使用 Parcelable 来准备数据,把函数名称、函数参数都写入 _data,让 _reply 接收函数返回值。最后使用 IBinder 的 transact() 方法,把数据就传给 Binder 的 Server 端了。

     @Override
                public java.util.List<com.example.zs.ipcdemo.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.example.zs.ipcdemo.aidl.Book> _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        // 通知服务器调用该方法
                        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(com.example.zs.ipcdemo.aidl.Book.CREATOR);
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    
    

    Server 执行流程

    Server 则是通过 onTransact() 方法接收 Client 进程传过来的数据,包括函数名称、函数参数,找到对应的函数,这里是 getBookList() ,把参数喂进去,得到结果,返回。所以 onTransact() 函数经历了读数据、执行要调用的函数、把执行结果再写数据的过程。

     @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;
                    }
                    // 客户端调用指令 getBookList()
                    case TRANSACTION_getBookList: {
                        data.enforceInterface(DESCRIPTOR);
                        // 调用本地服务 getBookList() 方法
                        java.util.List<com.example.zs.ipcdemo.aidl.Book> _result = this.getBookList();
                        reply.writeNoException();
                        // 将数据传给客户端
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addBook: {
                        data.enforceInterface(DESCRIPTOR);
                        com.example.zs.ipcdemo.aidl.Book _arg0;
                        if ((0 != data.readInt())) {
                            _arg0 = com.example.zs.ipcdemo.aidl.Book.CREATOR.createFromParcel(data);
                        } else {
                            _arg0 = null;
                        }
                        this.addBook(_arg0);
                        reply.writeNoException();
                        return true;
                    }
                return super.onTransact(code, data, reply, flags);
            }
    

    以上就是 AIDL 的具体原理以及工作流程的分析,希望对大家有所帮助。代码已经更新了GitHub:https://github.com/christian-zs/IPCDemo

    相关文章

      网友评论

          本文标题:Android AIDL(Binder) 原理分析

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