阅读 1513

Android Binder组合ContentProvider跨进程调用

概述

这篇文章记录一下之前在阅读 360 RePlugin 插件化框架源码的时候,偶然发现的一种比较有意思的 IPC 方式,即将 ContentProvider 和 Binder 结合起来,在任意两个进程间通过传递 Binder 代理对象来实现跨进程通信,而不需要像 Service 一样,需要在两个进程间有这个 bindService() 动作后再通信,当然,本质上都是获取到服务端进程的 Binder 代理对象,只不过在 Service 的运行逻辑里,AMS 帮我们完成了这个传递的过程。

MatrixCursor

在介绍上面的方式之前,可以先看一下 MatrixCursor 这个 Cursor 对象。

在一些使用 Cursor 的场景里,如果想得到一个 Cursor 对象,但又没有数据库返回一个 Cursor,此时可以通过 MatrixCursor 来返回一个伪造的 Cursor。比如一个程序在一般情况下用 context.getContentResolver().query() 从 ContentProvider 中查询数据,但是在一些特殊的场景里,需要返回的只有几条固定的已知记录,不需要从数据库查询,此时可以使用 MatrixCursor 来根据这些已知的记录构造一个 Cursor。

MatrixCursor 的用法如下:

  1. 首先创建一个字符数组,字符数组的值对应着表的字段:

    val COLUMN_NAME = arrayOf("_id", "name", "age")
    复制代码
  2. 利用MatrixCursor的构造方法,构造一个MatrixCursor,传入的参数即是步骤1中创建的字段数组:

    matrixCursor = MatrixCursor(COLUMN_NAME)
    复制代码
  3. 通过matrixCursor的addRow方法添加一行值,相当于向数据库中插入一条记录:

    matrixCursor?.addRow(arrayOf<Any>(1, "hearing", 24))
    // 也可以通过构造一个MatrixCursor.RowBuilder来实现
    matrixCursor?.newRow()?.add(2)?.add("hhh")?.add(22)
    复制代码

通过上面三步即可完成 MatrixCursor 的构造,从 MatrixCursor 中取出数据的过程与 Cursor 相同。

接下来通过一个实例来看看怎么具体地借助 ContentProvider 和 Binder 来进行便捷的跨进程通信。

Server进程

服务端接口定义

在服务端进程或者服务端 App 中,定义 AIDL 接口文件:

interface IMyAidlInterface {
    int getCount();
}
复制代码

make 之后实现接口:

class CountServiceImpl : IMyAidlInterface.Stub() {
    override fun getCount(): Int {
        println("Server: getCount")
        return 100
    }
}
复制代码

ContentProvider定义

接着注册并定义一个运行在服务端进程的 ContentProvider 对象:

class BinderProvider : ContentProvider() {
    override fun onCreate(): Boolean = true

    override fun query(
        uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?
    ): Cursor {
        return BinderCursor.binderCursor
    }

    override fun getType(uri: Uri): String? = null

    override fun insert(uri: Uri, values: ContentValues?): Uri? = null

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int = 0
}
复制代码

这里 query 方法中返回的 Cursor 对象是一个 BinderCursor.binderCursor, BinderCursor 继承自 MatrixCursor 类,其内保存有服务端 Binder 对象:

class BinderCursor private constructor() : MatrixCursor(arrayOf("service")) {
    companion object {
        private const val KEY_BINDER_COUNT = "key_binder_count"
        val binderCursor = BinderCursor()

        fun getCountService(cursor: Cursor?): IBinder? {
            return cursor?.extras?.getBinder(KEY_BINDER_COUNT)
        }
    }

    private val mBinderExtra = Bundle()

    init {
        mBinderExtra.putBinder(KEY_BINDER_COUNT, CountServiceImpl())
    }

    override fun getExtras(): Bundle {
        return mBinderExtra
    }
}
复制代码

然后在 Manifest 中注册:

<provider
    android:name=".server.BinderProvider"
    android:authorities="com.hearing.binder.demo"
    android:exported="true"
    android:grantUriPermissions="true"
    android:process=":provider" />
复制代码

Client进程

客户端进程通过以下方法可以获取到 Binder 代理对象,并远程调用服务端接口:

val cursor = contentResolver.query(Uri.parse("content://com.hearing.binder.demo"), null, null, null, null)
try {
    val service = IMyAidlInterface.Stub.asInterface(BinderCursor.getCountService(cursor))
    println("Client: ${service?.count}")
} catch (e: Exception) {
    e.printStackTrace()
} finally {
    cursor?.close()
}
复制代码

总结

通过这种方式,可以比较方便地在两个进程间进行 IPC 调用,也可以用来实现一些 通过一个AIDL文件实现IPC调用 之类的逻辑。

之所以能通过这种方式来进行两个进程间的通信,本质上是因为 Binder 是一个能跨进程传输的对象。当客户端与服务端不在同一个进程时,客户端拿到的将是一个 Binder 代理对象(Framework 层是 BinderProxy 对象),调用该代理对象的方法将会借助 Binder 驱动实现跨进程通信;当客户端与服务端处于同一个进程时,客户端会直接拿到其引用,进而直接调用相关方法。

对于 Android 开发来说 Binder 应该是必须要掌握的知识点了,之前阅读过 Binder 原理相关的 Framework, Native 及 Binder 驱动层的大概源码,记录在 Android-Binder 里,由于整块源码流程比较复杂,而且涉及到底层的驱动源码,我本身也有些知识处于一知半解的状态,有机会的话想把这部分的逻辑整理一下,加深理解。

最近金三银四忙着找工作,以及整理复习文档,个人博客 一直在更新,不过掘金上连着几个月没有写新的文章了,因为我对掘金上自己的文章有一定的要求,不想单纯让它成为一个记录自己笔记的地方,所以只有我觉得有一些价值的内容才会放到这里,最近新工作差不多告一段落了,于是重新开始写文章,回头把一些面经整理一下分享给大家哈。我是 19 年本科毕业的,在专业知识和工作经验上都有些不足,很多文章可能不太有深度,文中内容如有错误欢迎指出,共同进步!觉得不错的留个再走哈~

文章分类
Android
文章标签