Binder 在应用层的使用和原理分析

一、前言

IPC 是 Inter Process Communication 的缩写,也就是进程间通信。Android 系统是基于 Linux 内核开发的,而 Linux 已经提供了丰富的 IPC 机制,如:管道(pipe)、信号量、信号、消息队列、共享内存和socket。但是 Android 并没有采用以上这些传统的进程间通信机制,而是开发了一套新的进程间通信机制,即 Binder。与传统的进程间通信机制相比,使用Binder 进程间通信机制在进程间传输数据时,只需要执行一次拷贝操作,因此它即提高了效率又节省了内存。关于 Linux 的进程间通信机制请参考Android跨进程通信IPC之1——Linux基础

Binder 进程间通信机制也不是 Android 独创的,而是 google 在 OpenBinder 的基础上进行改造并应用到 Android 系统中的。OpenBinder 的详情请参考wiki

本篇文章主要是介绍 Android 进程间通信机制在应用层的实现和应用,主要是 Binder 在应用层应用。

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

二、AIDL 实现 Srvice 跨进程通信

2.1、创建 AIDL 相关文件

在 Android 开发中,Binder 主要用在 Service 中,包括 AIDL(Android Interface Defination Language) 和 Messenger,而 Messenger 的底层其实是 AIDL,所以这里先选择 AIDL 来分析 Binder 的工作机制。

在使用 AIDL 接口的时候,经常会接触到一下这些类:

  1. IBinder :它代表了一种跨进程传输的能力,只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别 IBinder 类型的数据,从而自动完成不同进程 Binder 本地对象以及 Binder 代理对象的转换。

  2. IInterface:提供了类型转化的功能,将服务或者服务代理类转为 IBinder 类型。实际的类型转换是由 BnInterface、BpInterface 两个实现类完成的,BnInterface 将服务类转换成 IBinder 类型,而 BpInterface 则将代理服务类转换成 IBinder 类型。在通过Binder Driver传递Binder对象时,必须进行类型转换。

  3. Java 层的 Binder 类:代表的其实就是 Binder 本地对象。BinderProxy 代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

  4. 在使用 AIDL 的时候,编译工具会给我们生成一个 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,并实现了 IInterface 接口。

下面通过 AIDL 来实现进程间通信,首先创建三个文件:Book.java,Book.aidl 和 BookManager.aidl,代码如下:

Book.java:

public class Book implements Parcelable{

    private String BookName;
    private int bookId;

    public Book(String bookName, int bookId) {
        BookName = bookName;
        this.bookId = bookId;
    }

    protected Book(Parcel in) {
        BookName = in.readString();
        bookId = in.readInt();
    }

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(BookName);
        dest.writeInt(bookId);
    }
}

Book.aidl:

// Book.aidl
package com.example.seraphzxz.ipcbasic.entity;

parcelable Book;

BookManager.aidl:

package com.example.seraphzxz.ipcbasic;
import com.example.seraphzxz.ipcbasic.entity.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

注意:在 IBookManager.aidl 中要手动导入 Book 类,AIDL 不支持自动导入。

Rebuild Project 后在 /IPCBasic/app/build/generated/source/aidl/debug 目录下生成 IBookManager,代码如下:

public interface IBookManager extends android.os.IInterface { // 继承自 IInterface
    // Stub 继承自 Binder 并实现 IBookManager
    public static abstract class Stub extends android.os.Binder implements com.example.seraphzxz.ipcbasic.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.example.seraphzxz.ipcbasic.IBookManager";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static com.example.seraphzxz.ipcbasic.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.seraphzxz.ipcbasic.IBookManager))) {
                return ((com.example.seraphzxz.ipcbasic.IBookManager) iin);
            }
            return new com.example.seraphzxz.ipcbasic.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.seraphzxz.ipcbasic.entity.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.seraphzxz.ipcbasic.entity.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.example.seraphzxz.ipcbasic.entity.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.seraphzxz.ipcbasic.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.seraphzxz.ipcbasic.entity.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.seraphzxz.ipcbasic.entity.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.seraphzxz.ipcbasic.entity.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.example.seraphzxz.ipcbasic.entity.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();
                }
            }
        }
        // 标识方法的 id
        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.seraphzxz.ipcbasic.entity.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.example.seraphzxz.ipcbasic.entity.Book book) throws android.os.RemoteException;
}

2.2、分析系统生成类 IBookManager

系统帮我们生成了这个文件之后,我们只需要继承 IBookMananger.Stub 这个抽象类,实现它的方法,然后在 Service 的 onBind 方法里面返回就实现了 AIDL。这个 Stub 类非常重要,具体看看它做了什么。

Stub 类继承自 Binder,意味着这个 Stub 其实自己是一个 Binder 本地对象,然后实现了 IBookMananger IBookMananger 本身是一个 IInterface,因此他携带某种客户
端需要的能力(这里是方法 addBook 和 getBookList)。此类有一个内部类 Proxy,也就是 Binder 代理对象。

在接着看它的 asInterface 方法,我们在 bind 一个 Service 之后,在 onServiceConnection 的回调里面,就是通过这个方法拿到一个远程的 service 的,看一下它的实现:

/**
 * Cast an IBinder object into an com.example.seraphzxz.ipcbasic.IBookManager interface,
 * generating a proxy if needed.
 */
public static com.example.seraphzxz.ipcbasic.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.seraphzxz.ipcbasic.IBookManager))) {
        return ((com.example.seraphzxz.ipcbasic.IBookManager) iin);
    }
    return new com.example.seraphzxz.ipcbasic.IBookManager.Stub.Proxy(obj);
}

asInterface 的参数类型为 IBinder,要注意的是,这个对象是驱动给我们的,如果是 Binder 本地对象,那么它就是 Binder 类型,如果是 Binder 代理对象,那就是BinderProxy 类型;然后,它会试着查找 Binder 本地对象,如果找到,说明 Client 和 Server 都在同一个进程,这个参数直接就是本地对象,直接强制类型转换然后返回,如果找不到,说明是远程对象,这个 IBinder 参数类型实际上是 BinderProxy。

接着看接口中定义的的方法的具体实现,以 addBook 为例;在 Stub 中 addBook 是一个抽象方法,我们需要继承这个类并实现它;如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。那么,如果是远程调用,会通过 Binder 代理完成的,在这个例子里面就是 Proxy 类:

    private static class Proxy implements com.example.seraphzxz.ipcbasic.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.seraphzxz.ipcbasic.entity.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.seraphzxz.ipcbasic.entity.Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.example.seraphzxz.ipcbasic.entity.Book.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public void addBook(com.example.seraphzxz.ipcbasic.entity.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();
            }
        }
    }

先看一下 addBook 方法,它首先用 Parcel 把数据序列化了,然后调用了 transact 方法,这个 Proxy 类在 asInterface 方法里面被创建,前面提到过,如果是 Binder 代理那么说明驱动返回的 IBinder 实际是 BinderProxy,因此的 Proxy 类里面的 mRemote 实际类型应该是 BinderProxy;BinderProxy 的 transact 方法实现如下:

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
    if (Binder.isTracingEnabled()) { Binder.getTransactionTracker().addTrace(); }
    return transactNative(code, data, reply, flags);
}

public native boolean transactNative(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException;

这是一个本地方法,它的实现在 native 层,具体来说在
frameworks/base/core/jni/android_util_Binder.cpp 文件,里面进行了一系列的函数调用,它最终调用到了 talkWithDriver 函数;看这个函数的名字就知道,通信过程要交给驱动完成了;这个函数最后通过 ioctl 系统调用,Client 进程陷入内核态,Client 调用 addBook 方
法的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用了 Server 进程本地对象的 onTransact 函数(实际上由 Server 端线程池完成)。

再看 Binder 本地对象的 onTransact 方法(Stub 里面的方法):

    @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.seraphzxz.ipcbasic.entity.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                com.example.seraphzxz.ipcbasic.entity.Book _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.example.seraphzxz.ipcbasic.entity.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

在 Server 进程里面,onTransact 根据调用号(每个 AIDL 函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数)调用相关函数;在这个例子里面,调用了 Binder 本地对象的 addBook 方法;

这个方法将结果返回给驱动,驱动唤醒挂起的 Client 进程里面的线程并将结果返回。于是一次跨进程调用就完成了。

通过 以上分析可以看出 AIDL 是一种固定的模式:一个需要跨进程传递的对象一定继承自 IBinder,如果是 Binder 本地对象,那么一定继承 Binder 实现 IInterface,如果是代理对象,那么就实现了 IInterface 并持有了 IBinder 引用。

2.3、完整调用流程

首先构建项目,项目分为客户端和服务端。

(1) 服务端

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

(2) 客户端

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

(3) AIDL 接口的创建

AIDL 接口的创建上文中已经给出,要注意的是,不同应用之间进行传输的实体类要位于相同的包中,先看一下项目结构:

服务端:

aidlserver.png

客户端:

aidlclient.png

服务端代码:

public class BookManageService extends Service {

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        Book android = new Book("Android",1);
        Book ios = new Book("IOS",2);
        mBookList.add(android);
        mBookList.add(ios);
    }

    public BookManageService() {
    }

    private IBookManager.Stub stub = new IBookManager.Stub() {
        // 实现了AIDL文件中定义的方法
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }
}

客户端代码:

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    private ServiceConnection connection = new ServiceConnection() {

        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // 从连接中获取Stub对象
            IBookManager manager = IBookManager.Stub.asInterface(iBinder);
            try {
                List<Book> books = manager.getBookList();
                for(int i = 0; i < books.size(); i++) {
                    Log.e(TAG,books.get(i).getBookName() + " | " + books.get(i).getBookId());
                }
                manager.addBook(new Book("JAVA",3));
                List<Book> allBook = manager.getBookList();
                for(int i = 0; i < allBook.size(); i++) {
                    Log.e(TAG,allBook.get(i).getBookName() + " | " + allBook.get(i).getBookId());
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setClassName("com.example.seraphzxz.aidlserver", "com.example.seraphzxz.aidlserver.BookManageService");
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
}

打开客户端之后就可以收到来自服务端的消息了,到这里使用 AIDL 实现进程间通信的完整调用流程就完成了。

参考文献:

Android 开发艺术探索

Android跨进程通信IPC之5——Binder的三大接口

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要点...
    指尖流逝的青春阅读 2,601评论 0 13
  • 毫不夸张地说,Binder是Android系统中最重要的特性之一;正如其名“粘合剂”所喻,它是系统间各个组件的桥梁...
    weishu阅读 17,764评论 29 246
  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,878评论 1 23
  • 一、IPC简介 (1)IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨...
    遥遥的远方阅读 7,160评论 0 3
  • Binder简析 直观来说,Binder 是 Android 中的一个类,是继承了 IBinder 接口;从 IP...
    任教主来也阅读 2,016评论 0 3