ContentObserver的使用:

一、核心原理

  1. 监听端:ContentObserver 用于监听 ContentProvider 数据增删改,数据变动时系统回调 onChange()
  2. 发送端:ContentResolver.notifyChange () 当 Provider 内数据发生 insert/update/delete 后,主动调用此方法发送变更通知,所有注册该 URI 的 Observer 都会收到回调。
  3. 通信模型:跨进程通知,适用于多应用共享数据、系统数据监听。

二、完整流程: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
    )
}

参数详解:

  1. uri:要监听的资源地址

  2. notifyForDescendants

    • true:监听该 URI 所有子节点(content://a 会匹配 content://a/1content://a/2
    • false:仅完全匹配当前 URI
  3. observer:自定义观察者实例

3. 注销监听(强制必须调用)

kotlin

kotlin 复制代码
// Activity onStop / Service onDestroy
fun unRegisterObserver() {
    if (::dataObserver.isInitialized) {
        contentResolver.unregisterContentObserver(dataObserver)
    }
}

三、selfChange 自身变更过滤原理

  1. 场景:当前 App 自己调用 insert/update,触发 notifyChange,会收到重复回调
  2. 解决方案:发送通知时传入当前 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)

五、核心注意事项(发送 + 监听双端)

监听端注意点

  1. Handler 不可为空回调运行在 Handler 线程,主线程 Handler 不能做数据库查询,会 ANR;建议使用子线程 Handler。
  2. 成对注册注销,防止内存泄漏Activity/Service 销毁必须 unregister,Observer 持有页面引用会导致页面无法回收;后台进程被杀监听直接失效。
  3. onChange 只通知变更,不携带变更数据回调仅告知 "数据变了",必须手动 query 查询最新数据,无法直接获取新增 / 修改条目。
  4. 高频变更会多次回调,需要防抖短时间多次更新会连续触发 onChange,加布尔锁、延时处理避免重复查询。
  5. 权限限制监听系统数据需要动态权限,无权限时 onChange 永远不回调;Android13+ 媒体、短信权限收紧。
  6. 后台冻结限制省电模式、电池优化、应用冻结会暂停 Observer 回调;长期后台监听需要前台 Service。

发送端(ContentProvider)注意点

  1. 增删改操作必须调用 notifyChange,否则外部 Observer 收不到任何回调。
  2. 精准传递 Uri:新增单条数据用 ContentUris.withAppendedId 生成带 id 的 Uri,监听细分条目更精准。
  3. 多表操作:多张表更新时,分别调用多次 notifyChange 对应不同 Uri。
  4. 跨进程同步延迟:通知是异步跨进程消息,存在短暂延迟,不能即时读取变更数据。

六、常见问题

  1. onChange 不触发

    • Provider 未调用 notifyChange
    • URI 不匹配、notifyForDescendants=false 无法匹配子路径
    • 缺少对应读写权限、应用被后台冻结
  2. 多次重复回调

    • 高频更新未防抖
    • 未过滤 selfChange,自身操作重复触发
  3. 内存泄漏

    • 页面销毁未注销 Observer
  4. ANR 卡顿

    • onChange 主线程执行数据库查询、IO 耗时操作

七、使用场景区分

  1. 多应用共享数据库:自定义 ContentProvider + notifyChange + ContentObserver
  2. 监听系统数据(通讯录 / 短信 / 设置):仅注册 Observer,系统自动发通知
  3. 单应用本地数据库:优先 Room Flow,无需 ContentObserver,性能更好
  4. 跨进程实时数据同步:ContentObserver 是官方标准方案
相关推荐
casual_clover1 小时前
【Android 控件】使用 Navigation 实现底部导航功能时左右滑动退出页面功能
android·底部导航滑动退出
urkay-1 小时前
Kotlin Flow分类
android·开发语言·kotlin
阿pin2 小时前
Android随笔-MVC/MVP/MVVM/MVI
android·mvc·mvvm·mvp·mvi
阿pin3 小时前
Android随笔-View绘制流程
android·开发语言·view
2501_916008893 小时前
iOS 证书管理最佳实践 从创建到续期的完整指南
android·ios·小程序·https·uni-app·iphone·webview
私人珍藏库12 小时前
[Android] PeakFinder AR v4.8.89 (山峰全景识别+增强现实山峰查看器)
android·人工智能·智能手机·ar·工具·软件
researcher-Jiang12 小时前
高性能计算之OpenMP——超算习堂学习1
android·java·学习
alexhilton13 小时前
Kotlin DSL深度解析:从Gradle脚本到构建你自己的DSL
android·kotlin·android jetpack
烽火聊员13 小时前
查看Android Studio错误日志
android·ide·android studio