共计 2184 个字符,预计需要花费 6 分钟才能阅读完成。
在开发多模块 Android 应用时,我们经常遇到需要跨进程共享数据的场景。比如电商 App 的购物车模块运行在独立进程,但需要实时同步用户数据到订单模块。传统做法是使用 AIDL,但每次新增接口都要重新定义.aidl 文件,维护成本较高。本文将介绍更轻量的 ContentProvider 方案,它能同时解决数据共享和函数调用需求。

主流 IPC 方案对比
- AIDL:功能强大但实现复杂,适合高频 RPC 调用
- 优点:支持双向通信,自动线程池管理
-
缺点:需要维护接口文件,参数序列化成本高
-
Messenger:基于消息队列的轻量方案
- 优点:实现简单,天然线程安全
-
缺点:不支持同步调用,数据包大小受限
-
ContentProvider:本文推荐方案
- 优点:统一数据访问接口,内置权限控制
- 缺点:需要适配 CRUD 语义,函数调用需要封装
核心实现详解
1. 基础 ContentProvider 实现
首先创建继承 ContentProvider 的类,注意使用 AndroidX 注解:
class CrossProcessProvider : ContentProvider() {
// Uri 匹配器配置
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {addURI("com.example.provider", "user/#", USER_CODE)
addURI("com.example.provider", "function/call", FUNCTION_CALL_CODE)
}
override fun insert(uri: Uri, values: ContentValues?): Uri {return when (uriMatcher.match(uri)) {
USER_CODE -> {
// 实际数据库操作
val id = db.insert("user", null, values)
ContentUris.withAppendedId(uri, id)
}
else -> throw IllegalArgumentException("Unsupported URI: $uri")
}
}
// query/update/delete 方法同理
}
2. 函数调用功能扩展
通过 ContentResolver.call() 实现远程调用:
// 服务端注册函数
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {return when (method) {
"calculatePrice" -> {val params = extras?.getString("params")
Bundle().apply { putDouble("result", PriceCalculator.execute(params)) }
}
else -> super.call(method, arg, extras)
}
}
// 客户端调用示例
fun callRemoteFunction() {
val result = contentResolver.call(Uri.parse("content://com.example.provider/function"),
"calculatePrice",
null,
Bundle().apply { putString("params", "{...}") }
)?.getDouble("result")
}
安全与性能优化
权限控制配置
在 AndroidManifest.xml 中声明细粒度权限:
<provider
android:name=".CrossProcessProvider"
android:authorities="com.example.provider"
android:exported="true"
android:readPermission="com.example.READ_DATA"
android:writePermission="com.example.WRITE_DATA" />
防 SQL 注入实践
所有查询使用参数化:
fun querySafe(uri: Uri, selection: String?, selectionArgs: Array<String>?) {
db.query("user",
null,
"$selection AND valid=1", // 固定条件拼接
selectionArgs, // 用户输入只出现在参数
null, null, null)
}
大对象传输优化
对于超过 1MB 的数据:
- 使用 ParcelFileDescriptor 传输文件
- 实现自定义 Parcelable 时重写 describeContents()
- 考虑分页查询设计
生产环境检查清单
- 并发控制 :
- 对写操作使用 SQLite 事务
-
读操作考虑 ContentProviderClient 稳定连接
-
性能监控 :
- 在 Application.onCreate() 启动预加载
-
使用 StrictMode 检测主线程查询
-
缓存策略 :
- 客户端设置 Cursor.setNotificationUri()
- 对静态数据使用内存缓存
通过本文方案,我们实现了:
1. 统一的数据访问入口
2. 安全的权限管控体系
3. 灵活的远程函数调用能力
实际项目中可根据业务需求,进一步封装为更易用的 SDK。比如为常用操作提供 Kotlin 扩展函数,或者结合协程实现异步调用。
正文完
发表至: 移动开发
近两天内
