一个专注音视频领域问答的小圈子

Binder 是什么?在英文中 Binder 是 粘合剂 的意思,表示将两样东西粘在一起。而在 Android 开发中,Binder 的意思多了去了。不同的角度有着不同的解释。

它既可以是 Android 中实现了 IBinder 接口的一个单纯的类,也可以是 Android 中进程跨进程通信(IPC)的一种方式,还可以看作是工作在内核态的 Linux 驱动 /dev/binder

Android 应用层面的 Binder

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

比如,我们在写一个无须跨进程的本地服务 LocalService 时,需要在 Service 定义一个类继承自 Binder ,然后在 onBind() 方法中返回该 Binder 对象。这样在 ServiceConnection 接口的 onServiceConnection 方法中就会收到该 Binder 对象,通过该 Binder 对象就可以执行 LocalService 中的公共方法。

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
       mLocalBinder = (LocalService.LocalBinder) iBinder;
}

那么 onServiceConnection 是如何收到该 Binder 对象的呢?这个就是由 ActivityManagerService 来完成的了。AMS 在启动 Service 的过程中,就会回调 onServiceConnection 方法,其中的 IBinder 参数就是 Service 的 Binder 引用。由于绑定的是无须跨进程的本地服务 LocalService ,那么返回的 Binder 也就是 Service 的 Binder本地对象, 执行的方法也是在主线程中了。

而当 Service 组件是一个跨进程的 RemoteService 时,在 onServiceConnected 方法中再如上代码所示的调用返回的 IBinder 对象,则会报如下的转型错误:

java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.glumes.ipc_binder.service.LocalService$LocalBinder

这是因为 RemoteService 在另外的进程中,而不同的进程内存资源是不能共享的,服务端不能将自己的 Binder 本地对象返回过去,只能返回一个 代理对象 Binder ,客户端 Client 通过这个代理对象 Binder 来调用 RemoteService 的相关方法。这个代理对象的类型就是 BinderProxy

在 onServiceConnection 方法中通过 asInterface 方法来将 Service 返回的代理对象 Binder 转换成代理对象 Proxy,其中,Proxy 持有 Binder 的引用,其变量名为 mRemote

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // ActivityManagerService 在 bindService 的过程中会回调该方法,将 Service 的 Binder 对象传回来
            // 再调用 asInterface 方法根据是否在同一进程进行转换。
            // 如果在同一进程,返回的是本地对象 Binder 
            // 如果不在同一进程,返回的是代理对象 Binder
            mService = IBookManager.Stub.asInterface(iBinder);
}

此时,Client 调用 RemoteService 的方法就是通过跨进程的方式了。而两个进程之间的通信就是通过 Binder 驱动来完成的。这是一个类似 U 型 的结构。Client 和 Server 分别在 U 型 的两头,而 Binder 驱动则在 U 型 的底部连接两者。

盗图一张,如下所示:

binder_ipc

Binder 框架

所以,当进行跨进程通信时,Binder 就可以看作是一种架构,这种架构提供了 服务端接口Binder 驱动客户端接口 三个模块,如下图所示:

binder_kuangjia

服务端接口

从 Java 层面来看,一个 Binder 服务端实际上就是一个 Binder 类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收 Binder 驱动发送的消息,收到消息后,会执行 Binder 对象中的 onTransact 函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个 Binder 服务,就必须重载 onTransact 方法。

Binder 驱动

任意一个服务端 Binder 对象被创建时,同时会在 Binder 驱动中创建一个 mRemote 对象,该对象的类型也是 Binder 类。客户端要访问远程服务时,都是通过 mRemote 对象。

mRemote 对象的 transact 方法最终会调用一个 nativetransactNative方法,将数据写入 Binder 驱动,并由驱动去完成底层的通信过程。

客户端接口

客户端想要访问远程服务,必须获取远程服务在 Binder 驱动中对应的 mRemote 引用。获得该 mRemote 对象后,就可以调用其 transact 方法,而在 Binder 驱动中,mRemote 对象也重载了 transact 方法,重载内容主要包括以下几项:

  • 以线程间通信的模式,向服务端发送客户端传递过来的参数。
  • 挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知。
  • 接收到服务端线程的通知,然后继续执行客户端线程,并返回客户端代码区。

而客户端获取远程服务在 Binder 驱动中对应的 mRemote 引用,也是在 onServiceConnection 方法中得到的。

onServiceConnection 的方法原型如下,由系统回调该方法,其中的参数 iBinder 就是 Service 的 Binder 代理对象。

public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

而在 onServiceConnection 方法内执行 asInterface 方法,返回的内部代理类 Proxy 。

// asInterface 方法原型为,其实参数 obj 为 onServiceConnected 方法的参数 iBinder 。
public static IBookManager asInterface(IBinder obj){

返回的内部代理类 Proxy 构造方法如下,其函数调用都是通过该 mRemote 对象的 transact 方法来完成的,此 mRemote 对象就是远程服务在 Binder 驱动中对应的 mRemote 引用,它的类型就是 BinderProxy,而 Proxy 类只是对 mRemote 对象组合了一下而已。

   private static class Proxy implements IBookManager{
        private IBinder mRemote ;
        public Proxy(IBinder remote) {
            mRemote = remote;
        }

事实上在跨进程的情况下,我们可以不需要使用 Proxy 对象,直接调用 onServiceConnection 方法中 iBinder 对象的 transact 方法也可以完成跨进程通信。

小结

对上述内容简单的小结一下:

  • Android 中的跨进程通信是一个 U 型的过程,由底层的 Binder 驱动来完成双方通信。
  • 客户端 Client 无法得到 Server 的 Binder 本地对象,onServiceConnected 方法返回的是一个 BinderProxy 类型的 Binder 代理对象。
  • 客户端 Client 通过 Binder 代理对象的 transact 方法调用 Binder 驱动,驱动再调用服务端 Server 的 Binder 本地对象的 onTransact 方法来执行对应的操作。
  • 如果服务端 Server 不需要再向客户端 Client 返回数据,则 IPC 调用结束。如需要返回数据,服务端 Server 再调用 Binder 驱动写入数据,客户端 Client 接收到对应数据,IPC 调用结束。

附《Android艺术开发探索》图: binder_note_art

注意事项:

  • 所有可以在 Binder 中传输的接口都需要继承 IInterface 接口。
  • 客户端通过 Binder 代理对象发起远程请求时,当前线程会被挂起,所以不应该在 UI 线程中执行。
  • 另一进程的服务端 Binder 运行在 Binder 线程池中,无需再异步执行,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现。
  • AIDL 支持的数据类型: * 基本数据类型(int,long,char,boolean,double) * String 和 CharSequence 。 * List:只支持 ArrayList,里面每个元素都必须能够被 AIDL 支持。 * Map:只支持 HashMap,里面每个元素都必须被 AIDL 支持,包括 key 和 value 。 * Parcelable:所有实现了 Parcelable 接口的对象。 * AIDL :所有的 AIDL 接口本身也可以在 AIDL 文件中使用。

代码地址: https://github.com/glumes/BinderLearn

参考

  1. 《Android内核剖析》
  2. 《Android开发艺术探索》
  3. http://www.codeceo.com/article/aidl-android-binder.html
  4. http://weishu.me/2016/01/12/binder-index-for-newer/

知识星球

一个专注音视频领域问答的小圈子

公众号音视频开发进阶对应的知识星球,一个编程开发领域的专业圈子,贩卖知识和技巧!

※ 入群须知:了解该星球能提供的价值和帮助,在提问时务必阐述好背景,附带相关的信息。

iOS 用户可以加我微信 ezglumes 邀请你进星球,有疑问也可以加我微信咨询。

※ 星球内容:

基础教程:

在知识星球连载的干货教程,可以在专栏中找到,随着时间的推移,教程也会越来越多:

- 音视频基础概念
- WebRTC 入门教程及源码实践
- 播放器教程及源码实践
- OpenGL 和特效开发教程
- Vulkan 入门教程

部分内容可以在博客 https://glumes.com 中检索到,后面会在星球里持续更新.

干货分享:

涵盖了移动开发和音视频工程领域的绝大部分,从项目实战角度出发,提升能力,包括但不限于以下领域:

- Android/iOS 移动开发
- Camera 开发
- 短视频编辑 SDK 项目实践
- 在线直播和推流
- WebRTC 开发
- 播放器基础和提高
- OpenGL 图像渲染及特效开发
- C++ 基础和提高
- FFmpeg 使用和分析
- 干货资源和书籍分享

不止于技术方面的,各种 IT 新闻、茶余饭后、生活趣事也欢迎大家分享!!!

技术答疑解惑:

针对上述基础教程和干货分享的答疑,另外还有音视频和 IT 开发中的各种交流讨论。

- 基础知识点答疑
- 工业项目实践答疑
- 问题排查思路分析

一个 BUG 排查很久,不如来星球里提个问题,效率提升百倍。

求职和面试辅导:

一站式职场服务,每份工作都值得用心对待!!!

- 面试题和面试经验分享
- 简历修改和模拟面试
- 大厂内推和信息同步
- 职场经验分享
- 职业规划和发展分析

※ 星主和合伙人介绍

星主是公众号音视频开发进阶的作者,也是网站 https://glumes.com 的作者,曾参与过抖音、剪映等头部音视频 APP 底层 SDK 的开发。

合伙人也是在头条、快手从事音视频架构师的职位,具有多年的音视频开发经验,能力圈覆盖了音视频的绝大多数领域,资深音视频从业人员为你保驾护航。

微信公众号

扫描下面的二维码关注我的微信公众号《音视频开发进阶》,推送更多精彩内容!

添加我的微信 ezglumes 拉你入音视频与图形图像技术群一起交流学习~

wechat-account-qrcode

原创文章,转载请注明来源:    Android Binder Summary