概述
这篇文章记录一下之前在阅读 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 的用法如下:
-
首先创建一个字符数组,字符数组的值对应着表的字段:
val COLUMN_NAME = arrayOf("_id", "name", "age") 复制代码
-
利用MatrixCursor的构造方法,构造一个MatrixCursor,传入的参数即是步骤1中创建的字段数组:
matrixCursor = MatrixCursor(COLUMN_NAME) 复制代码
-
通过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 年本科毕业的,在专业知识和工作经验上都有些不足,很多文章可能不太有深度,文中内容如有错误欢迎指出,共同进步!觉得不错的留个赞
再走哈~