跨进程传递对象

1,527 阅读4分钟

前言

在 android 中只要对象实现了 Parcelable 接口就可以使用 binder 进行传递。下面是一个 aidl 生成的 java 示例,可以看出是如何传递的:

Xnip2022-03-10_10-00-53.png

Bitmap 传递

所有人都知道通过 Intent 传 bitmap 时很大可能报 TransactionTooLargeException 异常。但 bitmap 本身又实现了 Parceable 接口,理论上是可以通过 binder 传递的。下面是通过 startService() 传输 bitmap 时的调用流程

image.png

上图中一共有两个核心点。一个是 ContextImpl#startServiceCommon()

// service 为 Intent 对象
service.prepareToLeaveProcess(this);
// Intent.prepareToLeaveProcess() 方法
setAllowFds(false); // 最终会将 false 传递到 Intent 内部的 bundle 中

第二点是 Bundle#writeToParcel(),这里面的 parcel 就是通过 aidl 一路传递下来的 parcel 对象

image.png

从第一张截图看出 btp 的传递入口在 Bitmap#writeToParcel() 中,该方法调用了 nativeWriteToParcel(),同时会将上面的 Parcel 对象传递到 native 层。在 native 层是 Bitmap.cpp 中的 Bitmap_writeToParcel() 方法

image.png

最核心的逻辑就是调用了最下面的 writeBlob()

image.png

跨进程传 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)
})

对方进程逻辑如下:

image.png

IBinder 传递

查看 Parcel 中的 write 系列方法可以发现,除了常用的类型及 Parcelable 的子类外,还可以直接传输 IBinder/IInterface 对象,这两个并没有实现 Parcelable 接口。 传送 IBinder/IInterface 最终都是调用了 nativeWriteStrongBinder,它最终会调用 Parcel::writeStrongBinder(),内部调用的是 Parcel::flattenBinder()

image.png

总结

从上面可以看出,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

传输的具体流程如下:

image.png

上面地 FlattenableHelper 可以理解为一个代理对象,所有方法的调用基本上都直接转发到传入的 val 参数中。

继续跟踪上面代码:

image.png

从上面内容可以看出,对 Flattenable 对象的传输,主要由对象自身的 flatten 方法实现。同样,基于常识 Flattenable 对象的反序列化交由对象的 unflatten 方法实现