前言
最近在项目中涉及到了即时聊天,因此不可避免地要用到实时刷新的功能,因为在以前的项目中见到别人使用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; } }
整个流程还是比较清晰的,再梳理一遍:
- 初始化并启动Loader,会在createLoader方法中回调我们的onCreateLoader,我们在这里生成一个CursorLoader对象,其中设置了要查询的条件
- 在CursorLoader的onForceLoad实现中有一个异步任务,这个异步任务的loadInBackground方法中根据我们设置的查询条件查询数据库,最终会调用到我们的ContentProvider的query方法进行查询
- 查询完成会得到一个Cursor对象,我们调用cursor.setNotificationUri(getContext().getContentResolver(), uri)为这个Cursor设置要监听的Uri,同时CursorLoader会为这个Cursor对象注册一个内容观察者ForceLoadContentObserver
- 异步任务执行完成后会回调我们的onLoadFinished方法,我们在onLoadFinished方法中调用CursorAdapter的changeCursor或swapCursor方法,最终就能让我们的界面自动刷新
- 当我们用ContentProvider增删改数据时,只需在最后调用getContext().getContentResolver().notifyChange(uri, null),就会通知到上面的Cursor对象(因为Uri相同),再由Cursor触发内容观察者的onChange方法,最终又会调用到onForceLoad,重复上述过程
遇到的问题
根据上面的流程我们可以知道,每次数据发生改变时,最后都会触发loadInBackground中的查询,但是这里的查询条件一直是我们在创建CursorLoader时设置的查询条件,而我的项目中涉及到了分页查询(应用场景就是类似手机qq查看历史聊天记录),发生的问题就是当新增的数据达到一定数量时,界面就不会更新了,即如果在查询条件中含有动态改变的limit条件(如分页查询时的页数),就会产生问题。
解决方法
我的解决方法是每次数据库变化之后都通过CursorLoader的一系列set方法更新查询条件