前言
在 android 中只要对象实现了 Parcelable 接口就可以使用 binder 进行传递。下面是一个 aidl 生成的 java 示例,可以看出是如何传递的:
Bitmap 传递
所有人都知道通过 Intent 传 bitmap 时很大可能报 TransactionTooLargeException 异常。但 bitmap 本身又实现了 Parceable 接口,理论上是可以通过 binder 传递的。下面是通过 startService() 传输 bitmap 时的调用流程
上图中一共有两个核心点。一个是 ContextImpl#startServiceCommon()
// service 为 Intent 对象
service.prepareToLeaveProcess(this);
// Intent.prepareToLeaveProcess() 方法
setAllowFds(false); // 最终会将 false 传递到 Intent 内部的 bundle 中
第二点是 Bundle#writeToParcel(),这里面的 parcel 就是通过 aidl 一路传递下来的 parcel 对象
从第一张截图看出 btp 的传递入口在 Bitmap#writeToParcel() 中,该方法调用了 nativeWriteToParcel(),同时会将上面的 Parcel 对象传递到 native 层。在 native 层是 Bitmap.cpp 中的 Bitmap_writeToParcel() 方法
最核心的逻辑就是调用了最下面的 writeBlob()
跨进程传 bitmap
既然已经知道了 bitmap 在 startActivity/startService 时无法传输的因为是系统禁用了 fd 的传输,而 bitmap 本身是可以跨进程的(借助 ashmem)。同时 ibinder 对象也可以跨进程传输(传的是地址),且不受 startActivity/startService() 限制 。那么通过 startActivity/startService 传输一个 IBinder 对象,再通过 ibinder 对象获取 bitmap(此时会绕过对 fd 的禁用), 就可以跨进程传输 btp 了。说到底,上面其实是 aidl 的自我实现版
首先定义一个 IBinder 对象
// Binder 实现了 IBinder 接口,同时作了一些处理,所以这里使用 Binder
abstract class TestItf : Binder() {
abstract fun btp(): Bitmap
}
其实把 ibinder 对象传到其他进程
// 以 startActivity 为例
startActivity(Intent(this, SecondActivity::class.java).apply {
putExtra("aa", "aa")
// 新建 Bundle() 对象,因为 Intent 无法直接传输 IBinder 对象
val b = Bundle()
b.putBinder("btpKey", object : TestItf() {
// 重写该方法,因为跨进程调用时当前 IBinder 对象该方法首先收到请求
// 并且应该在该方法中根据请求 code 调用不同的方法
// 这里为了简单,不区分 code 了
override fun onTransact(
code: Int,
data: Parcel,
reply: Parcel?,
flags: Int
): Boolean {
// 写到对方传递的 Parcel 中,相当于写完调用进程
reply?.writeParcelable(btp(), 0)
super.onTransact(code, data, reply, flags)
// 这里要返回 true,表示本次调用成功
return true
}
override fun btp(): Bitmap {
return BitmapFactory.decodeResource(resources, R.drawable.tt)
}
})
// 将 Bundle 塞到 intent 中
putExtras(b)
})
对方进程逻辑如下:
IBinder 传递
查看 Parcel 中的 write 系列方法可以发现,除了常用的类型及 Parcelable 的子类外,还可以直接传输 IBinder/IInterface 对象,这两个并没有实现 Parcelable 接口。 传送 IBinder/IInterface 最终都是调用了 nativeWriteStrongBinder,它最终会调用 Parcel::writeStrongBinder(),内部调用的是 Parcel::flattenBinder()
总结
从上面可以看出,IBinder 对象跨进程传输时其实传输的是地址。
从前面也可以知道,binder 驱动会建立一个 binder_node 引用该地址,使用进程使用 binder_ref 指向 binder_node。使用时通过 binder_ref 找到 binder_node 从而拿到实际地址,再转成指针形式就可以调用 IBinder 对象的方法
fd 的传输
在传递 bitmap 时说过它实质上传递的是文件描述符 fd,目标进程使用 fd 可以读取到 bitmap 的实际数据。所以这里有必要说一下 fd 的传输原理,这样才能明白为什么对方进程可使用 fd 读取到 bitmap 数据。
我们知道,fd 实质是数组下标,数组元素的类型是 file*。调用 open() 方法打开某个文件时,会生成一个 file 对象,这些 file 对象由系统维护,各进程共享。
所以 fd 的传输很简单:在调用进程中通过 fd 拿到对应的 file 指针,再从目标进程中申请一个 fd,然后将 fd 与 file 指针进行绑定,并将申请到的 fd 返回给目标进程,这样目标进程使用该 fd 就可读取到调用进程想要传递的数据。毕竟两个 fd 指向的是同一个 file,内容肯定是一样的。
从上面也可看出,目标进程拿到的 fd 与调用进程传递的 fd 很可能不一样,但两者指向系统级的同一个文件。
Flattenable 对象
只有 native 层有该接口,java 层没有
翻看 Parcel.cpp 代码可以发现除了上面两种类型外,还可以传递 Flattenable 对象,具体示例可参考 GraphicBuffer
传输的具体流程如下:
上面地 FlattenableHelper 可以理解为一个代理对象,所有方法的调用基本上都直接转发到传入的 val 参数中。
继续跟踪上面代码:
从上面内容可以看出,对 Flattenable 对象的传输,主要由对象自身的 flatten 方法实现。同样,基于常识 Flattenable 对象的反序列化交由对象的 unflatten 方法实现