前言

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

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  2. TensorFlow的学习

    1.先判断python的版本(因为有些python版本自带pip,可以参考我写的对pip的认识的博客文章),选择是否安装pip,然后安装更新tensorflow如:sudo pip install - ...

  3. Android自定义组件系列【13】——Android自定义对话框如此简单

    在我们的日常项目中很多地方会用到对话框,但是Android系统为我们提供的对话框样子和我们精心设计的界面很不协调,在这种情况下我们想很自由的定义对话框,或者有的时候我们的对话框是一个图片,没有标题和按 ...

  4. Model、ModelMap、ModelAndView的作用及区别

    Model.ModelMap.ModelAndView的作用及区别 对于MVC框架,控制器controller执行业务逻辑 用于产生模型数据Model 视图view用来渲染模型数据 Model和Mod ...

  5. js---15,模拟数组的ecah方法

    原型的另外一个作用就是扩展对象中的属性和方法的 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http:// ...

  6. RingtoneManager-获得系统当前的铃声

    我们直接看代码 bt1 = (Button) findViewById(R.id.bt1); bt2 = (Button) findViewById(R.id.bt2); bt3 = (Button) ...

  7. python运算符优先级表

    运算符 描述 lambda Lambda表达式 or 布尔“或” and 布尔“与” not x 布尔“非” in,not in 成员测试 is,is not 同一性测试 <,<=,> ...

  8. 使用Multiplayer Networking做一个简单的多人游戏例子-1/2

    原文地址: http://blog.csdn.net/cocos2der/article/details/51006463 本文主要讲述了如何使用Multiplayer Networking开发多人游 ...

  9. 【hdu 6181】Two Paths

    [链接]http://acm.hdu.edu.cn/showproblem.php?pid=6181 [题意] 让你求从1到n的次短路 [题解] 模板题; 因为点可以重复走; 则一定会有次短路. di ...

  10. Android 小米盒子游戏手柄按键捕获 - 能获取到的 home 键依旧是个痛

    Android 小米盒子游戏手柄按键捕获 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 ...