前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android进程间通信(二):通过AIDL介绍Binder的工作机制

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

作者头像
103style
发布2022-12-19 13:46:17
3740
发布2022-12-19 13:46:17
举报

转载请以链接形式标明出处: 本文出自: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

代码语言:javascript
复制
package aidl;
parcelable Book;

IBookManager.aidl

代码语言:javascript
复制
package aidl;
import aidl.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

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

代码语言:javascript
复制
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
Make Project

然后切换到 Android 视图,在 java(generated) /aidl/ 下会自动创建一个 IBookManager 的文件.

image.png
image.png

自动创建的 IBookManager 大致的结构如下,省略了部分内容:

代码语言:javascript
复制
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, 客户端即请求失败,所以我们用这个特性来做权限验证。

代码语言:javascript
复制
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 中的数据。

代码语言:javascript
复制
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#addBookgetBookList() 流程差不多,因为没有返回值,所以不需要从 _reply 中获取返回值。

代码语言:javascript
复制
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代码,并重新绑定远程服务。客户端示例如下:

代码语言:javascript
复制
//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
Binder的工作机制.png

小结

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

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


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

以上


本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • Binder介绍
  • AIDL示例
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档