一、核心原理
- 监听端:ContentObserver 用于监听
ContentProvider数据增删改,数据变动时系统回调onChange()。 - 发送端:ContentResolver.notifyChange () 当 Provider 内数据发生 insert/update/delete 后,主动调用此方法发送变更通知,所有注册该 URI 的 Observer 都会收到回调。
- 通信模型:跨进程通知,适用于多应用共享数据、系统数据监听。
二、完整流程:Provider 发通知 + App 监听
(一)发送端:自定义 ContentProvider 主动推送变更通知
在 insert / update / delete 方法末尾调用 notifyChange,通知全局观察者。
java
运行
typescript
@Override
public Uri insert(Uri uri, ContentValues values) {
// 1. 插入数据库逻辑
long id = db.insert("table", 0, values);
if (id == -1) return null;
Uri newUri = ContentUris.withAppendedId(uri, id);
// 2. 发送变更通知(关键)
// 参数1:变更对应的Uri;参数2:触发的Observer(一般传null,通知所有)
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = db.update("table", values, selection, selectionArgs);
if (count > 0) {
// 通知数据变化
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = db.delete("table", selection, selectionArgs);
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
notifyChange 重载说明
java
运行
java
// 基础版
void notifyChange(Uri uri, ContentObserver observer)
// 完整版(控制是否同步、是否触发自身Observer)
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)
observer:传null→ 通知所有注册该 URI 的观察者;传 Observer 对象 → 跳过该观察者(避免自身操作重复回调)syncToNetwork:true 代表同步数据到云端,系统同步类 Provider 使用,普通本地数据库传 false
(二)监听端:ContentObserver 完整监听流程
1. 自定义观察者
必须构造传入 Handler,回调运行在 Handler 绑定线程
kotlin
kotlin
class DataObserver(handler: Handler) : ContentObserver(handler) {
/**
* @param selfChange 是否是当前应用自己操作触发的通知(notifyChange 过滤自身时生效)
* @param uri 发生变化的Uri
*/
override fun onChange(selfChange: Boolean, uri: Uri?) {
super.onChange(selfChange, uri)
uri ?: return
// 过滤自身修改,只监听其他应用/系统改动
if (selfChange) return
// 子线程查询最新数据,主线程禁止耗时IO
CoroutineScope(Dispatchers.IO).launch {
queryNewData(uri)
}
}
private suspend fun queryNewData(uri: Uri) {
// resolver.query 查询最新数据
}
}
2. 注册监听 registerContentObserver
kotlin
kotlin
// 主线程Handler
private val mainHandler = Handler(Looper.getMainLooper())
private lateinit var dataObserver: DataObserver
// 注册(Activity onStart / Service onCreate)
fun registerObserver() {
dataObserver = DataObserver(mainHandler)
val targetUri = Uri.parse("content://com.xxx.provider/user")
// 参数:监听Uri、是否监听子Uri、观察者实例
contentResolver.registerContentObserver(
targetUri,
notifyForDescendants = true, // true:匹配所有子路径
dataObserver
)
}
参数详解:
-
uri:要监听的资源地址 -
notifyForDescendants- true:监听该 URI 所有子节点(
content://a会匹配content://a/1、content://a/2) - false:仅完全匹配当前 URI
- true:监听该 URI 所有子节点(
-
observer:自定义观察者实例
3. 注销监听(强制必须调用)
kotlin
kotlin
// Activity onStop / Service onDestroy
fun unRegisterObserver() {
if (::dataObserver.isInitialized) {
contentResolver.unregisterContentObserver(dataObserver)
}
}
三、selfChange 自身变更过滤原理
- 场景:当前 App 自己调用 insert/update,触发 notifyChange,会收到重复回调
- 解决方案:发送通知时传入当前 Observer,跳过自身回调
java
运行
scss
// Provider 中获取监听者实例,传入notifyChange
resolver.notifyChange(uri, mObserver);
此时 onChange(selfChange=true),可直接过滤:
kotlin
kotlin
if (selfChange) return
四、系统内置 Provider 监听(无需自己发 notify)
系统短信、通讯录、媒体库、设置等系统 Provider,系统内部自动调用 notifyChange,我们只需要注册监听即可,不用手动发送通知。示例:监听系统亮度变化
kotlin
ini
val brightnessUri = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS)
contentResolver.registerContentObserver(brightnessUri, false, observer)
五、核心注意事项(发送 + 监听双端)
监听端注意点
- Handler 不可为空回调运行在 Handler 线程,主线程 Handler 不能做数据库查询,会 ANR;建议使用子线程 Handler。
- 成对注册注销,防止内存泄漏Activity/Service 销毁必须 unregister,Observer 持有页面引用会导致页面无法回收;后台进程被杀监听直接失效。
- onChange 只通知变更,不携带变更数据回调仅告知 "数据变了",必须手动 query 查询最新数据,无法直接获取新增 / 修改条目。
- 高频变更会多次回调,需要防抖短时间多次更新会连续触发 onChange,加布尔锁、延时处理避免重复查询。
- 权限限制监听系统数据需要动态权限,无权限时 onChange 永远不回调;Android13+ 媒体、短信权限收紧。
- 后台冻结限制省电模式、电池优化、应用冻结会暂停 Observer 回调;长期后台监听需要前台 Service。
发送端(ContentProvider)注意点
- 增删改操作必须调用 notifyChange,否则外部 Observer 收不到任何回调。
- 精准传递 Uri:新增单条数据用
ContentUris.withAppendedId生成带 id 的 Uri,监听细分条目更精准。 - 多表操作:多张表更新时,分别调用多次 notifyChange 对应不同 Uri。
- 跨进程同步延迟:通知是异步跨进程消息,存在短暂延迟,不能即时读取变更数据。
六、常见问题
-
onChange 不触发
- Provider 未调用 notifyChange
- URI 不匹配、notifyForDescendants=false 无法匹配子路径
- 缺少对应读写权限、应用被后台冻结
-
多次重复回调
- 高频更新未防抖
- 未过滤 selfChange,自身操作重复触发
-
内存泄漏
- 页面销毁未注销 Observer
-
ANR 卡顿
- onChange 主线程执行数据库查询、IO 耗时操作
七、使用场景区分
- 多应用共享数据库:自定义 ContentProvider + notifyChange + ContentObserver
- 监听系统数据(通讯录 / 短信 / 设置):仅注册 Observer,系统自动发通知
- 单应用本地数据库:优先 Room Flow,无需 ContentObserver,性能更好
- 跨进程实时数据同步:ContentObserver 是官方标准方案