0
点赞
收藏
分享

微信扫一扫

深入浅出Android-Binder机制(图文+源码深入剖析

Binder 产生的背景

首先我们说说为什么会出现 Binder 这个东西。作为 iOS 开发者,我还是情不自禁地想去谈谈 iOS app,事实上,iOS 的每一个 app 都是一个独立的进程,它没有 Android 那种比较开放的多进程通讯能力,甚至 App 与 Extension (如通知中心插件)之间都不能有一种非常直接的通讯方式,当然不是说 iOS 没有 IPC 技术,其实 mach 内核也是有着不错的 IPC 技术的,但这不是本文的重点。Android 则不太一样,Android apps 基本上都需要各式各样的 IPC 需求,甚至启动一个 Activity 也需要用到 IPC,有一些 IPC 调用也许你并不知晓,可能对开发者最可见的就是用 AIDL 去写一个 Remote Service 接口了。

Android 很多核心功能都是由一系列 Services 支持的,比如 ActivityService、WindowService 等等等等,应用需要频繁地与这些 Services 发生交互,正是基于这种场景,就亟需一种好的 IPC 解决方案。

你可能会想,为什么不是 Local Socket?或者 Shared Memory,那是因为安全性无法得到保障。Android 的权限系统需要一种可靠的方式来保证各种 Services 的访问是在权限系统的监控下进行的,上述提到的解决方案就做不到了,因为不管是套接字还是共享内存,现有的 Linux 内核都不存在一种检验双方身份的方法存在,任何通过套接字或者共享内存走的数据都可以伪造,而在这个基础上做任何验证,代价都是相当高的。Android 的选择是基于内核,重新开发一套 IPC 机制,让它固有这些特性,也就是让系统可以在 Ring0 级保障交互双方身份的正确性,并且这种基于内核的方案效率还很高。

既然要基于内核,就一定要对内核动手脚,Android 采用驱动的方式实现这个技术,而不是直接修改 Linux 内核。这样你就可以假设,手机中有一个“设备”,应用之间通过这个设备来交互,而这个设备自身有一套身份校验机制,这样就比基于用户态的 IPC 方案来的安全得多,也快得多了。

Binder 是怎么工作的

我们暂且不需要深入理解 Binder 驱动底层的实现,也不需要知道 Binder 驱动提供了什么接口,我们就来看看一个 app 是如何通过 Binder 这个机制来实现跨进程通信的。

到这里,你可以把 Binder 驱动看作一个机器,它连接着所有 Services(假设 app 只调用 Services 提供的接口)。

一个 app(客户端)想要找一个 Service 办点事,它就要去操作这个机器,而这个机器会检测 app 的身份,如果身份合法,则可以继续操作。假设 app 要调用 A 服务的 foo 函数,那么它可以告诉这个机器,这个机器随后就会检查连在它身上的所有 Services,找到 app 需要的那一个,让它执行 foo 函数,并且再将返回值告诉 app,这样 app 就成功隔着进程操作到 A 服务了。

这当然是很抽象的说法,系统在 Framework 层做了很好地封装,让我们可以友好地使用 Binder 驱动来进行 IPC 操作,下面我们就直接看应用层所提供的接口是如何工作的。

探究与 Binder 相关的 Java 类

要说这些类,我们首先要用到它们,最简单的方式就是去创建一个 Service,让它运行在单独的进程中,然后编写 AIDL,实现一个接口,再到 Activity 中去使用这个接口。(注意:本文不介绍 Remote Service 与 AIDL 的相关知识,如果读者对这部分内容还不够了解,请先将它们弄懂再回来看本文)

AIDL 代码生成器为我们创建了一个 java 文件,这里面涉及到几个类:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qd0nGa6j-1650450755554)(//upload-images.jianshu.io/upload_images/1507403-90e51bfe85a79136.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)]

这些就是在应用层使用 Binder 所需的所有类了,看类图有些错综,但实际还是很简单的。

AIDL 生成的就是 IMyService 这个接口,而 StubProxy 则是这个接口的两个内部类,分别是 Binder 类和 BinderProxy 类的子类(Proxy 类虽然是用组合方式来持有 BinderProxy 的,但实际就是在直接用这个类,只不过做了一层封装,让其更易使用而已),StubProxy 都实现了 IMyService

所以 IInterface 到底是什么,它就是一个用于表达 Service 提供的功能的一个契约,也就是说 IInterface 里有的方法,Service 都能提供,调用者你别管用的是 BinderProxy 还是什么,只要拿到 IInterface,你就可以直接调用里面的方法,它就是一个接口。

同时 Stub 虽然实现了 IMyService,但是并没有实现厘米的任何方法,它是一个抽象类,开发者需要自己子类化 Stub 去实现具体的功能。
Proxy 实现了 IMyService,并且实现了里面的方法,这些方法的实现我们下面再说。

为什么 IMyService 要分 StubProxy 呢?这是为了要适用于本地调用和远程调用两种情况。如果 Service 运行在同一个进程,那就直接用 Stub,因为它直接实现了 Service 提供的功能,不需要任何 IPC 过程。如果 Service 运行在其他进程,那客户端使用的就是 Proxy,这里这个 Proxy 的功能大家应该能想到了吧,就是把参数封装后发送给 Binder 驱动,然后执行一系列 IPC 操作最后再取出结果返回。

好,到这里请求用 Proxy 发出去了,Service 怎么接受请求并作出响应呢,这就需要 Stub 了,还记得我们的 Stub 是继承自 Binder 的吗?Binder 有一个 onTransact 方法,而 Stub 重写了这个函数,这个函数三个重要参数:int codeParcel dataParcel reply,分别对应了被调函数编号、参数包、响应包。当 Proxy 发起了一个请求,服务端中相应的响应线程就会通过 JNI 调用到 Stub 类,然后执行里面的 execTransact 方法,进而转到 onTransact 方法。(这一系列调用链大家可以从源码中分析出来,我这里作为抛砖引玉,就不带大家分析 native 层的代码了)

我们来看一眼这个 onTransact

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_increaseCounter:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
int _result = this.increaseCounter(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

是不是非常清 Android开源项目《ali10 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》开源 24.coding.net/public/P7/Android/git》 晰易懂,就是根据传过来的数据包来做相应的操作,然后把结果写回数据包,Binder 驱动会来帮我们做好这些数据包的分发工作。而这段代码是运行在 Service 本地进程中的,它可以直接调用实现好的 Stub 类中的相关方法(本例子中是 increaseCounter 方法)。

下面我们趁热打铁再来看一眼 Proxy 类中的 increaseCounter 是怎么实现的:

@Override
public int increaseCounter(int increment) throws RemoteException{
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);

写在最后

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从哪里入手去学习,对此我整理了一些资料

如果你熟练掌握以下列出的知识点,相信将会大大增加你通过前两轮技术面试的几率!这些内容都供大家参考,互相学习。

举报

相关推荐

0 条评论