ContentObserver Loader 回调
ContentObserver 和 Loader 经常被放在一起讲,但它们解决的是两个不同问题: ContentObserver 把跨进程内容变更转换成指定 Handler/Executor 上的 onChange(); CursorLoader 再用这个变更观察者触发新的查询,并把 worker 结果交回 UI 线程。Android 17 的源码还保留了这些 deprecated framework 类,因此可以清楚看到观察、查询、取消和结果回收 如何衔接。
本文面向已经读过 Handler消息发送、 AsyncTask 任务生命周期、 HandlerExecutor 提交语义 和 Service Handler 组合 的读者。本文不把 Loader 当作现代架构 推荐,也不复述 ContentService 的完整 ObserverNode 树;重点追踪真实源码中的四个边界: Transport 如何到达 observer、Handler 如何决定回调线程、CursorLoader 如何监听 Cursor、 以及停止/取消后谁负责关闭结果。
读完后,读者应能定位一个 notifyChange() 事件的回调线程,解释为什么 ContentObserver 的 Handler 不等于查询 worker,判断 CursorLoader 的旧结果为何会被取消或关闭,并区分“内容变更 已通知”和“新查询结果已交付”。
1. 观察者归属
源码文件:frameworks/base/core/java/android/database/ContentObserver.java
构造函数保存 Handler 或 Executor;getContentObserver() 延迟创建 Binder Transport, releaseContentObserver() 则断开 Transport 与 observer 的关系。
public abstract class ContentObserver {
private final Object mLock = new Object();
private Transport mTransport;
Handler mHandler;
private final Executor mExecutor;
public ContentObserver(Handler handler) {
mHandler = handler;
mExecutor = null;
}
public IContentObserver getContentObserver() {
synchronized (mLock) {
if (mTransport == null) {
mTransport = new Transport(this);
}
return mTransport;
}
}
}Handler/Executor 是回调调度 owner;Transport 是注册到 ContentService 的 Binder 对象; ContentObserver 本身拥有回调实现。三者不是同一个生命周期,注销时必须释放注册关系。
2. 注册通道
源码文件:frameworks/base/core/java/android/content/ContentResolver.java
注册接口取得 observer 的 Transport,再通过 ContentService Binder 注册 URI、descendant 选项、 用户和 target SDK 信息。
public final void registerContentObserver(Uri uri, boolean notifyForDescendants,
ContentObserver observer, int userHandle) {
try {
getContentService().registerContentObserver(uri, notifyForDescendants,
observer.getContentObserver(), userHandle, mTargetSdkVersion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}这一步只建立“URI 变更到 Transport”的注册,不指定查询,也不把 observer 放入某个 Handler 队列。查询由 CursorLoader 另外拥有。
3. Transport 回调
源码文件:frameworks/base/core/java/android/database/ContentObserver.java
ContentService/Provider 通过 Binder 调用 Transport。Transport 保存 observer 引用并把 onChangeEtc() 转给 dispatchChange();具体回调线程由 observer 的 Handler/Executor 决定。
private static final class Transport extends IContentObserver.Stub {
private ContentObserver mContentObserver;
@Override
public void onChangeEtc(boolean selfChange, Uri[] uris,
int flags, int userId) {
ContentObserver observer = mContentObserver;
if (observer != null) {
observer.dispatchChange(selfChange,
Arrays.asList(uris), flags, userId);
}
}
}Transport 不直接调用用户的 onChange();它把跨进程 Binder 入口交给 observer 的调度协议。 若 Transport 已 release,observer 不再接收正常回调。
4. 回调线程
源码文件:frameworks/base/core/java/android/database/ContentObserver.java
dispatchChange() 有三条路径:Executor 非空时调用 Executor,Handler 非空时直接 post, 两者都为空时在当前 Transport 调用线程同步执行。源码特意说明 Handler 路径不包装成 HandlerExecutor,以避免旧代码在 post 失败时出现 RejectedExecutionException。
public final void dispatchChange(boolean selfChange,
@NonNull Collection<Uri> uris, @NotifyFlags int flags, int userId) {
if (mExecutor != null) {
mExecutor.execute(() -> onChange(selfChange, uris, flags, userId));
} else if (mHandler != null) {
mHandler.post(() -> onChange(selfChange, uris, flags, userId));
} else {
onChange(selfChange, uris, flags, userId);
}
}| 构造参数 | onChange() 执行位置 | 失败/生命周期边界 |
|---|---|---|
| Handler | Handler 绑定 Looper | post 失败时旧实现不抛 RejectedExecutionException |
| Executor | Executor 决定 | 由 Executor 自己定义拒绝 |
| 都为空 | 当前 Transport 线程 | 可能是 Binder 回调线程 |
因此“ContentObserver 总在主线程回调”只有在构造时传入主线程 Handler/Executor 时才成立。
5. 内容变更
源码文件:frameworks/base/core/java/android/content/ContentResolver.java
Provider 修改数据后调用 notifyChange(),ContentResolver 把 URI、发起 observer 和 flags 交给 ContentService;ContentService 再按注册关系调用 Transport。本文不展开 ContentService 内部匹配树,但要保留跨进程边界。
public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer,
@NotifyFlags int flags) {
try {
getContentService().notifyChange(
uri, observer == null ? null : observer.getContentObserver(),
observer != null && observer.deliverSelfNotifications(),
flags, mTargetSdkVersion, UserHandle.myUserId(),
mContext.getAttributionSource());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}notifyChange() 成功表示通知请求进入 ContentService,不表示 observer 的 onChange() 已经 执行,更不表示 CursorLoader 已经重新查询。
6. Loader 状态
源码文件:frameworks/base/core/java/android/content/Loader.java
Loader 的公开状态机不是 StateMachine 类,而是 mStarted、mAbandoned、 mContentChanged/mProcessingChange 等字段与 startLoading()、stopLoading()、 forceLoad()、reset() 方法共同维护。Loader 方法契约要求从进程主线程调用。
public void startLoading() {
mStarted = true;
onStartLoading();
}
public void stopLoading() {
mStarted = false;
onStopLoading();
}
public void forceLoad() {
onForceLoad();
}Loader 的 Handler 不一定是 ContentObserver 的 Handler;Loader 负责生命周期和结果监听, 具体 worker/回调线程由 AsyncTaskLoader 和 AsyncTask 决定。
7. 查询执行
源码文件:frameworks/base/core/java/android/content/CursorLoader.java
CursorLoader 创建 ForceLoadContentObserver,在 worker 的 loadInBackground() 查询 Cursor, 并让 Cursor 注册这个 observer。查询完成后,结果回到 Loader 的 UI 线程交付路径。
/* Runs on a worker thread */
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
try {
Cursor cursor = getContext().getContentResolver().query(
mUri, mProjection, mSelection, mSelectionArgs,
mSortOrder, mCancellationSignal);
if (cursor != null) {
cursor.getCount();
cursor.registerContentObserver(mObserver);
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
}CursorLoader 的 observer 监听的是 Cursor 对应的数据变化;变更通知触发 Loader 的 onContentChanged(),不直接替换 UI 数据。真正的重新查询还要经过 Loader 的 force/cancel 状态。
8. 查询执行
源码文件:frameworks/base/core/java/android/content/AsyncTaskLoader.java
AsyncTaskLoader 使用 LoadTask extends AsyncTask,doInBackground() 调用 onLoadInBackground();默认执行器和结果回调分别由 AsyncTask/Loader 处理。它不是 HandlerThread,而是 Executor + Handler 的旧式组合。
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
@Override
protected D doInBackground(Void... params) {
try {
return AsyncTaskLoader.this.onLoadInBackground();
} catch (OperationCanceledException ex) {
if (!isCancelled()) throw ex;
return null;
}
}
@Override
protected void onPostExecute(D data) {
dispatchOnLoadComplete(this, data);
}
}这里的 onPostExecute() 运行在 AsyncTask 的回调 Handler 线程,通常是主线程;worker 查询 完成并不表示结果已交付,Loader 还要检查当前 task、abandoned 和 content-change 状态。
9. 变更触发
源码文件:frameworks/base/core/java/android/content/Loader.java
ForceLoadContentObserver 收到变化后会调用 Loader 的内容变化路径。若 Loader 当前正在 started 状态,变更可以触发重新加载;若 stopped,则先记下变化,等下次 start 时处理。
protected void onContentChanged() {
if (mStarted) {
forceLoad();
} else {
mContentChanged = true;
}
}这解释了“数据已通知但查询没有立即执行”:Loader 生命周期可能是 stopped,或当前 task 正在 取消/节流。通知和加载是两条不同状态机。
10. 取消查询
源码文件:frameworks/base/core/java/android/content/AsyncTaskLoader.java
cancelLoad() 按三种情况处理:延迟等待中的 task 直接移除 Handler Runnable;已有 cancelling task 时丢弃新的等待 task;正在执行的 task 调用 AsyncTask.cancel(false) 并触发 cancelLoadInBackground()。
protected boolean onCancelLoad() {
if (mTask != null) {
if (mCancellingTask != null) {
if (mTask.waiting) {
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
}
mTask = null;
return false;
} else if (mTask.waiting) {
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
mTask = null;
return false;
} else if (mTask.cancel(false)) {
mCancellingTask = mTask;
cancelLoadInBackground();
mTask = null;
return true;
}
}
return false;
}取消不强制中断任意查询;CursorLoader 通过 CancellationSignal.cancel() 请求底层查询停止。 若查询已返回,结果回收仍由 onCanceled() 负责。
11. 结果交付
源码文件:frameworks/base/core/java/android/content/AsyncTaskLoader.java
dispatchOnLoadComplete() 会比较完成 task 与当前 mTask。旧 task 的结果进入取消路径; 当前 task 若已 abandoned 则关闭/取消数据;只有正常 started 状态才调用 deliverResult()。
void dispatchOnLoadComplete(LoadTask task, D data) {
if (mTask != task) {
dispatchOnCancelled(task, data);
} else if (isAbandoned()) {
onCanceled(data);
} else {
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
deliverResult(data);
}
}结果交付的 owner 是 Loader listener;ContentObserver 只负责“内容变化”信号,不能越过 task 身份检查直接把旧 Cursor 交给 UI。
12. Cursor 回收
源码文件:frameworks/base/core/java/android/content/CursorLoader.java
CursorLoader 在 deliverResult() 中处理 reset、旧 Cursor 和当前 Cursor 的所有权; onCanceled() 和 onReset() 也会关闭不再使用的 Cursor。
public void deliverResult(Cursor cursor) {
if (isReset()) {
if (cursor != null) cursor.close();
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) cursor.close();
}“回调到 UI”与“Cursor 生命周期结束”不是同一个时点;旧结果可能在交付前被取消,也可能在 新结果替换后才关闭。
13. 查询节流
源码文件:frameworks/base/core/java/android/content/AsyncTaskLoader.java
设置 update throttle 后,AsyncTaskLoader 使用 Handler 在主线程延迟执行同一个 LoadTask;到期 前取消会移除这个 Runnable,避免立即启动查询。
if (mUpdateThrottle > 0) {
long now = SystemClock.uptimeMillis();
if (now < mLastLoadCompleteTime + mUpdateThrottle) {
mTask.waiting = true;
mHandler.postAtTime(mTask,
mLastLoadCompleteTime + mUpdateThrottle);
return;
}
}
mTask.executeOnExecutor(mExecutor, (Void[]) null);这里的 Handler 只负责节流计时;查询执行仍由 mExecutor 运行。节流到期表示“可以提交”, 不表示查询结果已经可用。
14. 验证输入
源码文件:frameworks/base/core/java/android/database/ContentObserver.java
第一组验证构造三个 observer:主线程 Handler、worker Handler 和无 Handler,触发 dispatchChange(),断言 onChange() 所在线程分别匹配;同时验证已 release Transport 的 observer 不再接受正常回调。
第二组验证 CursorLoader:启动查询后让 Cursor 发送内容变更,断言 started 时触发新的 load, stopped 时只记录 mContentChanged;取消正在运行的查询,断言 CancellationSignal 被调用 且结果进入 onCanceled()。
第三组验证旧结果:连续启动两个 LoadTask,让第一个晚于第二个完成,断言第一个结果走 dispatchOnCancelled(),当前 Cursor 不被旧结果覆盖。测试需要受控 Executor/ContentProvider, 不能仅凭 Handler.post 成功推断结果顺序。
15. 复查命令
源码文件:
frameworks/base/core/java/android/database/ContentObserver.javaframeworks/base/core/java/android/content/ContentResolver.javaframeworks/base/core/java/android/content/Loader.javaframeworks/base/core/java/android/content/AsyncTaskLoader.javaframeworks/base/core/java/android/content/CursorLoader.java
rg -n "dispatchChange|mHandler|mExecutor|Transport|releaseContentObserver" \
frameworks/base/core/java/android/database/ContentObserver.java
rg -n "registerContentObserver|notifyChange|getContentObserver" \
frameworks/base/core/java/android/content/ContentResolver.java
rg -n "onContentChanged|startLoading|stopLoading|deliverResult|mContentChanged" \
frameworks/base/core/java/android/content/Loader.java
rg -n "LoadTask|cancelLoad|executePendingTask|dispatchOnLoadComplete|postAtTime" \
frameworks/base/core/java/android/content/AsyncTaskLoader.java \
frameworks/base/core/java/android/content/CursorLoader.java阅读一条变更路径时,顺序应是:Provider notify → ContentService/Transport → observer Handler → Loader content state → worker query → task identity → UI deliver → Cursor close。任何一步省略, 都可能把“已通知”误写成“已刷新”。
16. 适用边界
ContentObserver 适合接收内容变化信号;Loader/CursorLoader 适合把旧式查询、监听、取消和 结果交付组合起来。它们不提供 exactly-once 通知、自动线程安全、进程死亡恢复或现代生命周期 管理。Android 17 中这些类已经 deprecated,继续阅读它们的价值在于理解 Handler、Binder、 取消和结果所有权如何协作。
排查“数据变了但界面没刷新”时,应依次检查 Transport 是否仍注册、observer 是否投递到正确 Handler、Loader 是否 started、内容变化是否被记录、旧 task 是否仍 cancelling、Cursor 是否 被关闭,以及 deliverResult 是否因 reset/abandoned 被丢弃。
