Skip to content

ContentObserver Loader 回调

从 ContentObserver 的 Binder Transport 到 Handler 回调,再追踪 CursorLoader 的查询、变更触发、取消和结果回收。

基于android-17.0.0_r1
AndroidContentObserverCursorLoaderHandler源码阅读

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 的关系。

java
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 信息。

java
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 决定。

java
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。

java
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() 执行位置失败/生命周期边界
HandlerHandler 绑定 Looperpost 失败时旧实现不抛 RejectedExecutionException
ExecutorExecutor 决定由 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 内部匹配树,但要保留跨进程边界。

java
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 方法契约要求从进程主线程调用。

java
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 线程交付路径。

java
/* 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 的旧式组合。

java
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 时处理。

java
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()。

java
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()。

java
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。

java
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,避免立即启动查询。

java
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.java
  • frameworks/base/core/java/android/content/ContentResolver.java
  • frameworks/base/core/java/android/content/Loader.java
  • frameworks/base/core/java/android/content/AsyncTaskLoader.java
  • frameworks/base/core/java/android/content/CursorLoader.java
bash
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 被丢弃。