Android进程间通信(二):通过AIDL介绍Binder的工作机制

转载请以链接形式标明出处:
本文出自:103style的博客

《Android开发艺术探索》 学习记录

base on AndroidStudio 3.5.1


目录

  • Binder介绍
  • AIDL示例
  • 小结

Binder介绍

  • 直观来说,BinderAndroid 中的一个类,它实现了 IBinder 接口.
  • IPC 上来说,BinderAndroid 实现进程间通信的一种1方式.
  • Android Framework 角度来说,BinderServiceManager 连接各种 Manager(ActivityManager、WindowManager) 和相应的 ManagerService 的桥梁.
  • 从应用层来说,Binder 是 客户端和服务端通信的媒介.

Android开发中,Binder 主要用在 Service 中,包括 AIDLMessenger,普通 Service 中的 Binder 不涉及进程间通信。
Messenger 底层也是基于 AIDL 的, 所以我们以 AIDL 来介绍 Binder 的工作机制。


AIDL示例

目录结构:

  • app/src/main 下创建 aidl 的文件目录,然后创建包名 aidl,添加 Book.aidlIBookManager.aidl.
  • app/src/main/java 目录下 创建包名 aidl,然后添加 Book.java 文件。
    目录结构

Book.aidl

package aidl;
parcelable Book;

IBookManager.aidl

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

Book.java: 只要添加 bookIdbookName 属性,然后实现 Parcelable 接口,然后按照 AS 的报红提示实现对应方法就好了。

package aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
    public int bookId;
    public String bookName;
    //....
}

然后点击 菜单栏 的 BuildMake Project.
Make Project

然后切换到 Android 视图,在 java(generated) /aidl/ 下会自动创建一个 IBookManager 的文件.
image.png
自动创建的 IBookManager 大致的结构如下,省略了部分内容:

package aidl;
public interface IBookManager extends android.os.IInterface {
    public java.util.List<aidl.Book> getBookList() throws android.os.RemoteException;
    public void addBook(aidl.Book book) throws android.os.RemoteException;

    public static abstract class Stub extends android.os.Binder implements aidl.IBookManager {
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        private static final java.lang.String DESCRIPTOR = "aidl.IBookManager";
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        public static aidl.IBookManager asInterface(android.os.IBinder obj) {
            //...
        }
        public android.os.IBinder asBinder() {
            return this;
        }
        public boolean onTransact(...) throws android.os.RemoteException {
            //...
        }
        private static class Proxy implements aidl.IBookManager {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
            public android.os.IBinder asBinder() {
                return mRemote;
            }
            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
            public java.util.List<aidl.Book> getBookList() throws android.os.RemoteException {
                //...
            }
            public void addBook(aidl.Book book) throws android.os.RemoteException {
                //...
        }
    }
}

我们可以看到结构其实很简单,
首先声明了我们在 IBookManager.aidl 中 声明的两个方法 getBookList()addBook(aidl.Book book)
然后在Stub中 声明了两个整形的值用于标记这两个方法,用于在 onTransact 对应具体的方法;
Stub类是一个 Binder 类,当客户端和服务端在同一进程,方法调用不会走 onTransact,只有在不同进程才会走 onTransact,这个逻辑是由 Stub 的内部类 Proxy 来完成的。

下面介绍 IBookManager 相关属性的含义:

  • DESCRIPTOR
    Binder的唯一标识,一般用类名表示,例如本例中的 aidl.IBookManager

  • asInterface(android.os.IBinder obj)
    用于服务端的 Binder 对象转化成客户端所需的 AIDL 接口类型的对象,转换过程是区分进程的,同一进程返回 Stub 对象本身,不同进程返回 Stub.Proxy 对象。

  • asBinder()
    返回当前的Binder对象。

  • boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
    运行在服务端的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后的交由此方法来处理。服务端通过 code 来确定客户端请求的方法是哪个;从 data 中取出需要的参数;然后执行目标方法,将结果写入 reply, 如果方法返回 false, 客户端即请求失败,所以我们用这个特性来做权限验证。

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 
      throws android.os.RemoteException {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            //...
            case TRANSACTION_getBookList: {
                data.enforceInterface(descriptor);
                java.util.List<aidl.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
    }
    
  • Proxy#getBookList()
    运行在客户端,当客户端远程调用此方法:首先创建方法所需输入型 Parcel 对象 _data,输入型 Parcel 对象 _reply 和返回对象 _result,然后把方法的参数写入 _data 中,然后调用 transact 方法发起 RPC (远程过程调用) 请求,同时线程挂起,然后服务端的 onTransact(...) 被调用,知道 RPC 过程返回后,当前线程继续执行,并从 _reply 中获取返回结果,最后返回 _reply 中的数据。

    public java.util.List<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<aidl.Book> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(aidl.Book.CREATOR);
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
    
  • Proxy#addBook
    getBookList() 流程差不多,因为没有返回值,所以不需要从 _reply 中获取返回值。

    public void addBook(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();
        }
    }
    

接下来我们介绍两个很重要的方法 linkToDeathunlinkToDeath.

我们知道 Binder 运行在服务端进程,如果服务端意外终止,这时到服务端的连接就会断开,从而导致远程调用失败,从而导致客户端功能收到影响。

为了解决这个问题,Binder 给我们提供了 linkToDeathunlinkToDeath 这两个配对方法。通过 linkToDeath 可以给 Binder 设置一个死亡代理,在意外终止的时候,代理就会收到通知,我们就可以重新发起连接请求从而恢复连接。

那如何设置这个代理呢?
首先,声明一个 DeathRecipient 对象。 DeathRecipient 是一个接口,其内部只有一个 binderDied 方法,当Binder死亡时,就会调用这个方法,我们就可以在这里 移出之前绑定的 binder代码,并重新绑定远程服务。客户端示例如下:

//binder意外终止的代理
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (remoteBookManager==null){
            return;
        }
        remoteBookManager.asBinder().unlinkToDeath(deathRecipient,0);
        remoteBookManager = null;
        Intent intent = new Intent(BookManagerActivity.this, BookManagerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }
};
//服务连接
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        IBookManager iBookManager = IBookManager.Stub.asInterface(service);
        try {
            service.linkToDeath(deathRecipient,0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        ...
    }
};

通过以上的介绍我们大概了解了 Binder 的工作机制,不过还有两点要注意下:

  • 因为客户端发起 RPC 会被挂起,所以 耗时较久 的操作不能在 UI线程 中发起。
  • 由于 Binder 是 运行在服务端的 Binder 线程池中,所以 Binder 方法不管是否耗时都应 采用同步 的方式去实现。

Binder机制图 如下:
Binder的工作机制.png


小结

本文我们主要通过一个 AIDL 的示例,通过 IDE 自动生成 IBookManager, 然后分析了里面对应属性、方法和类,了解了Binder机制的工作流程。

下一节介绍 Android中实现进程间通信(IPC)的方式


如果觉得不错的话,请帮忙点个赞呗。

以上


扫描下面的二维码,关注我的公众号 Android1024, 点关注,不迷路。
Android1024

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值