前言

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

    Model using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace ...

  2. 关于HashMap

    总是觉得对HashMap很熟悉,但最近连续被问到几个关于它的问题,才发现它其实并不简单.这里对关于它的一些问题做个总结,也希望能够大家一个参考. 都知道它是基于hash值,可以进行常量时间消化的存储结 ...

  3. js -- fileData 实现文件断点续传

    前端实现文件的断点续传 一.一些知识准备 断点续传,既然有断,那就应该有文件分割的过程,一段一段的传. 以前文件无法分割,但随着HTML5新特性的引入,类似普通字符串.数组的分割,我们可以可以使用sl ...

  4. CDQZ 0003:jubeeeeeat

    0003:jubeeeeeat 查看 提交 统计 提问 总时间限制:  1000ms 内存限制:  256000kB 描述 众所周知,LZF很喜欢打一个叫Jubeat的游戏.这是个音乐游戏,游戏界面是 ...

  5. Android java.lang.NoSuchFieldError: No static field xxx of type I in class Lcom/XX/R$id; or its superclasses

    项目开发快到尾声,突然发现之前一个模块莫名其妙的奔溃了,我的内心也是奔溃的.以前一直都是好好的,也没去动过它,为啥会出现这样的问题呢? 下面我会根据自己的理解来看待问题 android是怎么根据id查 ...

  6. Codefroces Educational Round 27 845G Shortest Path Problem?

    Shortest Path Problem? You are given an undirected graph with weighted edges. The length of some pat ...

  7. nginx学习十一 nginx启动流程

    今天用了一天的时间看nginx的启动流程,流程还是非常复杂.基本的函数调用有十几个之多.通过看源代码和上网查资料,弄懂了一些函数.有些函数还在学习中,有些函数还待日后学习,这里记录一下今天所学.加油! ...

  8. 阿里云 CentOS7.4 环境安装mysql5.7

    1. 删除默认安装的数据库,无所谓的请略过 据说CentOS7.x版本会默认安装mariadb数据库,我有点强迫症,故卸载之: rpm -qa|grep mariadb yum remove mari ...

  9. 如何创建Hiren的BootCD USB磁盘 -- 制作U盘启动盘

    如何创建Hiren的BootCD USB磁盘 原文 https://www.wintips.org/how-to-create-hirens-bootcd-usb-disk/  本文基本是谷歌翻译 H ...

  10. Funui-overlay 如何添加theme 的 overlay

    昨天更改theme主题的时候,发现所有仓库下的theme都是共用的.也就是说,如果你更改了52平台下的theme,那么你提交了代码以后,82下也会发生相应的更改.但是,昨天修改的theme属性,只在3 ...