10、ContentObserver 监听:注册观察者、数据变化通知、自动刷新 UI
好,咱们今天聊点实在的——ContentObserver。说白了,它就是 ContentProvider 的“小喇叭”。数据一变,它就叫你一声。你想想看,如果没有它,你每次都得轮询数据库,那效率得多低?
我个人习惯把 ContentObserver 理解成一个“监听器”。它不干活,但它盯着数据源。一旦数据有风吹草动,它就通知你。嗯,这里要注意,它通知的是“数据变了”,而不是“数据变成啥样了”。想知道具体变化?你得自己去查。
10.1 为什么需要 ContentObserver?
我在项目中遇到过这样一个场景:App 里的联系人列表,用户可能在系统通讯录里新增了一个联系人。如果没有 ContentObserver,你只能每隔几秒去查一次数据库。这叫什么?这叫“轮询”,既耗电又费流量。
ContentObserver 解决了这个问题。它采用“观察者模式”,数据变化时主动通知你。你只需要在收到通知后刷新 UI 就行。说白了,这是一种“被动更新”的优雅方案。
10.2 注册观察者:三步走
注册一个 ContentObserver 其实不复杂。我总结了三步:
- 创建观察者对象:继承 ContentObserver,重写 onChange 方法。
- 注册到 ContentResolver:调用 getContentResolver().registerContentObserver()。
- 别忘了取消注册:在 Activity 或 Fragment 销毁时,调用 unregisterContentObserver()。
来看代码:
// 第一步:创建观察者
class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
// 数据变了,这里处理刷新逻辑
Log.d("Observer", "数据发生变化,准备刷新 UI");
}
}
// 第二步:注册
MyObserver observer = new MyObserver(new Handler());
Uri uri = Uri.parse("content://com.example.provider/table");
getContentResolver().registerContentObserver(uri, true, observer);
// 第三步:取消注册(通常在 onDestroy 中)
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(observer);
}
这里有个细节:第二个参数 true 表示“是否监听子路径”。如果你传 true,那么 uri 下的所有子路径变化都会通知你。我个人建议,除非你确定需要,否则传 false 就好,避免不必要的回调。
10.3 数据变化通知:谁在喊我?
ContentObserver 的 onChange 方法,其实有两个重载版本:
| 方法签名 | 说明 |
|---|---|
| onChange(boolean selfChange) | 旧版本,只告诉你是不是自己改的 |
| onChange(boolean selfChange, Uri uri) | 新版本,还告诉你具体哪个 Uri 变了 |
你可能会问:“为什么要有两个?” 嗯,这是历史遗留问题。早期 Android 版本只传一个 boolean,后来发现不够用,就加了 Uri 参数。我建议你尽量用第二个版本,因为知道具体哪个 Uri 变了,可以避免全量刷新。
举个例子:
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
// 根据 uri 判断是哪个表变了
if (uri.toString().contains("table_a")) {
// 只刷新 table_a 对应的 UI
} else if (uri.toString().contains("table_b")) {
// 只刷新 table_b 对应的 UI
}
}
我曾经踩过一个坑:在 onChange 里直接做了数据库查询,结果因为数据还没完全写入,查到了旧数据。后来我加了个小延迟,或者用 Handler 做一下 postDelayed,问题就解决了。
10.4 自动刷新 UI:别让用户等
数据变了,UI 怎么自动刷新?说白了,就是 onChange 里发一个消息给主线程,然后调用 adapter.notifyDataSetChanged() 或者重新加载数据。
我常用的模式是这样的:
// 在 Activity 中
private MyObserver observer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注册观察者
observer = new MyObserver(new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 主线程刷新 UI
loadData();
}
});
getContentResolver().registerContentObserver(
MyProvider.CONTENT_URI, true, observer);
}
private void loadData() {
// 重新查询数据
Cursor cursor = getContentResolver().query(...);
// 更新 adapter
adapter.swapCursor(cursor);
}
你看,这样用户就不需要手动下拉刷新了。数据一变,界面自动更新。用户体验会好很多。
10.5 知识体系图
下面这张图,是我梳理的 ContentObserver 核心逻辑。你看一眼,基本就全明白了:
10.6 避坑指南
最后,我分享几个实战中容易踩的坑:
- 忘记取消注册:Activity 销毁了,观察者还在,下次回调时可能操作已销毁的 View,导致崩溃。我曾经因为这个被测试小姐姐追着骂了一下午。
- onChange 里做耗时操作:它是在 Binder 线程回调的,你如果在里面做网络请求或大量数据库操作,会阻塞其他 Binder 调用。我建议只发消息,不干活。
- 重复注册:如果你在 onResume 里注册,在 onPause 里取消,那没问题。但如果你在 onCreate 里注册,又在 onResume 里再注册一次,就会收到两次回调。嗯,别问我怎么知道的。
- Uri 匹配不精确:注册时传的 Uri 和 ContentProvider 通知时用的 Uri 不一致,会导致收不到回调。建议两边用同一个常量。
好了,ContentObserver 的内容就这些。说白了,它就是 ContentProvider 和 UI 之间的桥梁。用好了,你的 App 会变得非常“聪明”——数据一变,界面自动跟着变。用户会觉得你的 App 反应很快,体验很好。