前言

最近在项目中涉及到了即时聊天,因此不可避免地要用到实时刷新的功能,因为在以前的项目中见到别人使用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方法更新查询条件

Android中CursorLoader的使用、原理及注意事项的更多相关文章

  1. Android中的LruCache的原理和使用

    Android中的LruCache的原理和使用 LruCache,虽然很多文章都把LRU翻译成"最近最少使用"缓存策略,但Android中的LruCache真的如此吗? 答案是No ...

  2. Android中图像变换Matrix的原理、代码验证和应用(一)

    第一部分 Matrix的数学原理 在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类.Android中的Matrix是一个3 x 3的矩阵,其内容如下: Matri ...

  3. Android 中View的工作原理

    Android中的View在Android的知识体系中扮演着重要的角色.简单来说,View就是Android在视觉的体现.我们所展现的页面就是Android提供的GUI库中控件的组合.但是当要求不能满 ...

  4. Android中微信抢红包插件原理解析和开发实现

    一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  5. Android中图像变换Matrix的原理、代码验证和应用(三)

    第三部分 应用 在这一部分,我们会将前面两部分所了解到的内容和Android手势结合起来,利用各种不同的手势对图像进行平移.缩放和旋转,前面两项都是在实践中经常需要用到的功能,后一项据说苹果也是最近才 ...

  6. Android中SensorManager.getRotationMatrix函数原理解释

    SensorManager是Android中的一个类,其有一个函数getRotationMatrix,可以计算出旋转矩阵,进而通过getOrientation求得设备的方向(航向角.俯仰角.横滚角). ...

  7. Android中典型的ROOT原理(5)

    ROOT的作用 Customization 用户的个人定制,如删除一些预安装,定制开机动画等. 特权操作 所有需要特权操作的基本都是要通过ROOT,这也是ROOT的初衷. ROOT的第一步:寻找漏洞并 ...

  8. Android中线程间通信原理分析:Looper,MessageQueue,Handler

    自问自答的两个问题 在我们去讨论Handler,Looper,MessageQueue的关系之前,我们需要先问两个问题: 1.这一套东西搞出来是为了解决什么问题呢? 2.如果让我们来解决这个问题该怎么 ...

  9. Android中图像变换Matrix的原理、代码验证和应用(二)

    第二部分 代码验证 在第一部分中讲到的各种图像变换的验证代码如下,一共列出了10种情况.如果要验证其中的某一种情况,只需将相应的代码反注释即可.试验中用到的图片: 其尺寸为162 x 251. 每种变 ...

随机推荐

  1. Reuse Is About People and Education, Not Just Architecture

     Reuse Is About People and Education, Not Just Architecture Jeremy Meyer you MigHT AdopT THE AppRoA ...

  2. Split Shape by Plane in OpenCASCADE

    Split Shape by Plane in OpenCASCADE eryar@163.com Abstract. Sometimes you want to split a shape by p ...

  3. UICollectionView——整体总结

    前言 这几天有时间看了下UICollectionView的东西,才发觉它真的非常强大,很有必要好好学习学习.以前虽然用过几次,但没有系统的整理总结过.这两天我为UICollectionView做一个比 ...

  4. android 移植ffmpeg后so库的使用

    今天折腾了一天,可算是有所收获,成功的用jni调用了libffmpeg中的一个方法-----avcodec_version(),至于avcodec_version()是干什么用的我不大清楚,应该是获取 ...

  5. celery work logging 问题

    celery 的日志里只输出日志 不输入标准打印

  6. LightOJ 1300 Odd Personality

    Odd Personality Time Limit: 2000ms Memory Limit: 32768KB This problem will be judged on LightOJ. Ori ...

  7. Font-Awesome最新版完整使用教程

    何为Font-Awesome Font Awesome gives you scalable vector icons that can instantly be customized - size, ...

  8. mysql判断一个字符串是否包含某子串 【转】

    文章出处:mysql判断一个字符串是否包含某子串 使用locate(substr,str)函数,如果包含,返回>0的数,否则返回0 例子:判断site表中的url是否包含'http://'子串, ...

  9. ORA-01031: 权限不足

    1.错误描写叙述 ORA-01031: 权限不足 2.错误原因 SQL> create user yhd identified by scott account unlock; create u ...

  10. Codeforces #258 Div.2 E Devu and Flowers

    大致题意: 从n个盒子里面取出s多花.每一个盒子里面的花都同样,而且每一个盒子里面花的多数为f[i],求取法总数. 解题思路: 我们知道假设n个盒子里面花的数量无限,那么取法总数为:C(s+n-1, ...