Android中CursorLoader的使用、原理及注意事项

前言

最近在项目中涉及到了即时聊天,因此不可避免地要用到实时刷新的功能,因为在以前的项目中见到别人使用CursorLoader+CursorAdapter+ContentProvider的机制来实现实时刷新,于是没有多研究就直接照搬了这个机制,直到后来出现了发送消息后不能更新到界面上的问题,查了很久也查不出原因,于是就想从这个机制本身出发,看看有可能是在哪个环节出了问题。

使用

1.让Activity或Fragment实现LoaderManager.LoaderCallbacks< D >接口

    由于我们的数据存储在数据库中,因此这里的泛型应该替换为Cursor 
    这个接口中有三个方法:

// 这个方法在初始化Loader时回调,我们要在这个方法中实例化CursorLoader
public Loader<D> onCreateLoader(int id, Bundle args);
// 加载数据完成后回调到这个方法,我们一般在这里调用CursorAdapter的changeCursor或swapCursor进行界面刷新的操作
public void onLoadFinished(Loader<D> loader, D data);
// 这个方法是在重启Loader时调用,一般可以不管
public void onLoaderReset(Loader<D> loader);
2.创建对应的ContentProvider
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        synchronized (DBLOCK) {
            SQLiteDatabase db = WeChatDBManager.getInstance(getContext()).getDatabase();
            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
            Cursor cursor = null;
            switch (sUriMatcher.match(uri)) {
                case CODE_CHAT_HISTORY:
                    queryBuilder.setDistinct(false);
                    queryBuilder.setTables(uri.getQuery());
                    cursor = queryBuilder.query(db, 
                            projection, 
                            selection, 
                            selectionArgs,
                            null, 
                            null, 
                            sortOrder 
                    );
                    break;
            }
            // 对查询到的结果集对应的Uri设置观察者
            if (cursor != null)
                cursor.setNotificationUri(getContext().getContentResolver(), uri);
            return cursor;
        }
}

@Override
    public Uri insert(Uri uri, ContentValues values) {
        ...
        // 通知对应的Uri数据发生改变
        getContext().getContentResolver().notifyChange(uri, null);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        ...
        // 通知对应的Uri数据发生改变
        getContext().getContentResolver().notifyChange(uri, null);
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        ...
        // 通知对应的Uri数据发生改变
        getContext().getContentResolver().notifyChange(uri, null);
    }
3.调用getLoaderManager().initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks< D> callback)初始化

原理

1.initLoader
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
        if (mCreatingLoader) {
            throw new IllegalStateException("Called while creating a loader");
        }

        LoaderInfo info = mLoaders.get(id);

        if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);

        if (info == null) {
            // 创建Loader
            info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
            if (DEBUG) Log.v(TAG, "  Created new loader " + info);
        } else {
            if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
            info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
        }

        if (info.mHaveData && mStarted) {
            // Loader中已经有数据,这里最终会回调到onLoadFinished方法
            info.callOnLoadFinished(info.mLoader, info.mData);
        }

      return (Loader<D>)info.mLoader
}

这里主要关注createAndInstallLoader方法

  private LoaderInfo createAndInstallLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        try {
            mCreatingLoader = true;
            // 这里首先会创建LoaderInfo对象
            LoaderInfo info = createLoader(id, args, callback);
            // 然后启动LoaderInfo
            installLoader(info);
            return info;
        } finally {
            mCreatingLoader = false;
        }
    }

首先来看看createLoader

  private LoaderInfo createLoader(int id, Bundle args,
            LoaderManager.LoaderCallbacks<Object> callback) {
        LoaderInfo info = new LoaderInfo(id, args,  callback);
        // 这里回调到了我们要实现的onCreateLoader方法
        Loader<Object> loader = callback.onCreateLoader(id, args);
        info.mLoader = loader;
        return info;
    }

在onCreateLoader中我们创建了具体的Loader,即CursorLoader

  @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(...);
    }

接着执行到installLoader

void installLoader(LoaderInfo info) {
        // 把上一步创建的LoaderInfo对象存到列表中
        mLoaders.put(info.mId, info);
        if (mStarted) {
            // 启动Loader
            info.start();
        }
    }

    void start() {
            ...
            // start方法中我们只关注startLoading方法
            mLoader.startLoading();
            ...
        }

    public final void startLoading() {
        mStarted = true;
        mReset = false;
        mAbandoned = false;
        // onStartLoading是个空方法,我们要看CursorLoader中的具体实现
        onStartLoading();
    }

    @Override
    protected void onStartLoading() {
        // 更新数据
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        // 初始化时调用 主要看这里,这里又调到父类Loader中的forceLoad
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    public void forceLoad() {
        // 这里的onForceLoad又是一个空方法,调用的是子类AsyncTaskLoader中的onForceLoad
        onForceLoad();
    }

    @Override
    protected void onForceLoad() {
        super.onForceLoad();
        cancelLoad();
        // 这里执行了一个异步任务,接下来看看这个异步任务具体做了什么事
        mTask = new LoadTask();
        if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
        executePendingTask();
    }

接下来具体看一下这个异步任务,具体关注其中的doInBackground和onPostExecute

 @Override
    protected D doInBackground(Void... params) {
        if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
        try {
            // 这里执行了onLoadInBackground
            D data = AsyncTaskLoader.this.onLoadInBackground();
            if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
            return data;
        } catch (OperationCanceledException ex) {
            if (!isCancelled()) {
                throw ex;
            }
            if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
            return null;
        }
    }

    protected D onLoadInBackground() {
        // 这里调用到CursorLoader的loadInBackground
        return loadInBackground();
    }

    @Override
    public Cursor loadInBackground() {
        synchronized (this) {
            if (isLoadInBackgroundCanceled()) {
                throw new OperationCanceledException();
            }
            mCancellationSignal = new CancellationSignal();
        }
        try {
            // 这里调用ContentResolver进行查询,查询条件就是前面我们在onCreateLoader创建CursorLoader对象时
            // 传入的,这里最终会调用我们的ContentProvider,我们在ContentProvider的query中对Cursor对象设置了监听的Uri
            Cursor cursor = ContentResolverCompat.query(getContext().getContentResolver(),
                    mUri, mProjection, mSelection, mSelectionArgs, mSortOrder,
                    mCancellationSignal);
            if (cursor != null) {
                try {
                    // 这里给Cursor对象注册了一个内容观察者,而在上面我们设置了要监听的Uri,因此当数据变化时,首先会通知Cursor,然后Cursor再触发ForceLoadContentObserver中的onChange
                    cursor.getCount();
                    cursor.registerContentObserver(mObserver);
                } catch (RuntimeException ex) {
                    cursor.close();
                    throw ex;
                }
            }
            return cursor;
        } finally {
            synchronized (this) {
                mCancellationSignal = null;
            }
        }
    }

    public final class ForceLoadContentObserver extends ContentObserver {
        public ForceLoadContentObserver() {
            super(new Handler());
        }

        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) {
            onContentChanged();
        }
    }

    public void onContentChanged() {
        if (mStarted) {
            // 这里又回到了forceLoad方法,接下来就是重复一遍上面的流程
            forceLoad();
        } else {
            mContentChanged = true;
        }
    }

异步任务最后会执行onPostExecute

  @Override
    protected void onPostExecute(D data) {
        if (DEBUG) Log.v(TAG, this + " onPostExecute");
        try {
            AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
        } finally {
            mDone.countDown();
        }
    }

    void dispatchOnLoadComplete(LoadTask task, D data) {
        if (mTask != task) {
            if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
            dispatchOnCancelled(task, data);
        } else {
            if (isAbandoned()) {
                // This cursor has been abandoned; just cancel the new data.
                onCanceled(data);
            } else {
                commitContentChanged();
                mLastLoadCompleteTime = SystemClock.uptimeMillis();
                mTask = null;
                if (DEBUG) Log.v(TAG, "Delivering result");
                // 传递数据
                deliverResult(data);
            }
        }
    }

    public void deliverResult(D data) {
        if (mListener != null) {
            // 回调到LoaderManager中的onLoadComplete
            mListener.onLoadComplete(this, data);
        }
    }

    @Override
    public void onLoadComplete(Loader<Object> loader, Object data) {
        ...
        // 这里我们只关注callOnLoadFinished,这个方法中最终会回调到我们的onLoadFinished
        if (mData != data || !mHaveData) {
            mData = data;
            mHaveData = true;
            if (mStarted) {
                callOnLoadFinished(loader, data);
            }
        }
        ...
    }

    void callOnLoadFinished(Loader<Object> loader, Object data) {
            if (mCallbacks != null) {
                String lastBecause = null;
                if (mHost != null) {
                    lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
                    mHost.mFragmentManager.mNoTransactionsBecause = "onLoadFinished";
                }
                try {
                    if (DEBUG) Log.v(TAG, "  onLoadFinished in " + loader + ": "
                            + loader.dataToString(data));
                    // 回调到我们的onLoadFinished
                    mCallbacks.onLoadFinished(loader, data);
                } finally {
                    if (mHost != null) {
                        mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
                    }
                }
                mDeliveredData = true;
            }
        }

整个流程还是比较清晰的,再梳理一遍:

  1. 初始化并启动Loader,会在createLoader方法中回调我们的onCreateLoader,我们在这里生成一个CursorLoader对象,其中设置了要查询的条件
  2. 在CursorLoader的onForceLoad实现中有一个异步任务,这个异步任务的loadInBackground方法中根据我们设置的查询条件查询数据库,最终会调用到我们的ContentProvider的query方法进行查询
  3. 查询完成会得到一个Cursor对象,我们调用cursor.setNotificationUri(getContext().getContentResolver(), uri)为这个Cursor设置要监听的Uri,同时CursorLoader会为这个Cursor对象注册一个内容观察者ForceLoadContentObserver
  4. 异步任务执行完成后会回调我们的onLoadFinished方法,我们在onLoadFinished方法中调用CursorAdapter的changeCursor或swapCursor方法,最终就能让我们的界面自动刷新
  5. 当我们用ContentProvider增删改数据时,只需在最后调用getContext().getContentResolver().notifyChange(uri, null),就会通知到上面的Cursor对象(因为Uri相同),再由Cursor触发内容观察者的onChange方法,最终又会调用到onForceLoad,重复上述过程

遇到的问题

根据上面的流程我们可以知道,每次数据发生改变时,最后都会触发loadInBackground中的查询,但是这里的查询条件一直是我们在创建CursorLoader时设置的查询条件,而我的项目中涉及到了分页查询(应用场景就是类似手机qq查看历史聊天记录),发生的问题就是当新增的数据达到一定数量时,界面就不会更新了,即如果在查询条件中含有动态改变的limit条件(如分页查询时的页数),就会产生问题。

解决方法

我的解决方法是每次数据库变化之后都通过CursorLoader的一系列set方法更新查询条件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值