Loader,AsyncTaskLoader,CursorLoader与LoaderManager

本文深入剖析了Android中的Loader机制,包括Loader、AsyncTaskLoader、CursorLoader与LoaderManager等核心组件的工作原理,以及它们如何协调工作来实现高效的数据加载。
 

Loader,AsyncTaskLoader,CursorLoader与LoaderManager

  575人阅读  评论(0)  收藏  举报
  分类:

目录(?)[+]

概述

        参考

        在实际项目,一般很少直接在主线程中进行数据库操作。为解决该问题可以使用AsyncQueryHandler,该类中有一系列的startXXX方法,可以在子线程对数据库进行CRUD操作。但如果我们加载的数据不在数据库中时,AsyncQueryHandler就显得无能为力了。此时可以使用Loader。

        Loader设计用于从数据源加载某类数据(如对象)。数据源可以是磁盘、数据库、ContentProvider、网络或者另一个进程。loader可以在不阻塞主线程的情况下获取并发送数据给接收者。Android提供了Loader,AsyncTaskLoader与CursorLoader三种内置类型。

        作为基类,Loader本身没有多大用处,它定义了供LoaderManager与loader通讯的api

        AsyncTaskLoader是一个抽象的Loader。它使用AsyncTask将数据加载任务转移到其他线程上。我们用的大部分都是它的子类。

        CursorLoader,它是AsyncTaskLoader的子类,它借助ContentResolver从ContentProvider中加载数据。

        以上内容摘自王明发译的《Android编程权威指南》

Loader

        整个Loader类中没有进行任何关于加载数据的处理逻辑,它只是定义了Loader的三个状态——start,abandon,reset——以及两个监听器(OnLoadCompleteListener,OnLoadCanceledListener)和一个ContentObserver。

        ContentObserver在Loader中只是定义,在CursorLoader中才使用。CursorLoader#loadInBackground中有:

[java]  view plain  copy
  1. cursor.registerContentObserver(mObserver);  

        因此,到cursor发生变化时,会执行ForceLoadContentObserver#onChange(),它又会调用onContentChanged()方法:

[java]  view plain  copy
  1. public void onContentChanged() {  
  2.        if (mStarted) {//内容发生变化, 并且loader已经start过,所以直接强制重新加载一次  
  3.            forceLoad();  
  4.        } else {  
  5.            // loader已经停止了, 所以不需要立即加载新数据。<span style="font-family: Arial, Helvetica, sans-serif;">但记录下数据变化的标识,在重新启动时去刷新.</span>  
  6.            mContentChanged = true;  
  7.        }  
  8.    }  
        这里就可以看出mContentChanged的作用:记录loader停止后数据是否发生了变化,为了在再启动时能去刷新数据。
        对于commitContentChanged(),注释上有一句话:Call this when you have completely processed a load without it being cancelled。这就是说:当你完全处理了一个没有被取消的loader时调用该方法。因此,mProcessingChange表示是否正在处理changed
[java]  view plain  copy
  1. public void commitContentChanged() {  
  2.     mProcessingChange = false;  
  3. }  
        同样takeContentChanged的作用也很好说了:获取在停止时是否有内容变化。
[java]  view plain  copy
  1. public boolean takeContentChanged() {  
  2.     boolean res = mContentChanged;  
  3.     mContentChanged = false;  
  4.     mProcessingChange |= res;//如果有变化,那就表示正在处理变化。  
  5.     return res;  
  6. }  

        Loader为三种状态提供了一系列的方法:如获取方法isStarted(),isAbandoned(),isReset()。修改方法如下:

[java]  view plain  copy
  1. public final void startLoading() {//禁止私自调用  
  2.     //修改三个状态,略  
  3.     onStartLoading();  
  4. }  
  5. protected void onStartLoading() {  
  6. }  
  7. public boolean cancelLoad() {  
  8.     return onCancelLoad();  
  9. }  
  10. protected boolean onCancelLoad() {  
  11.     return false;  
  12. }  
  13. /** 
  14.  * 强制重新加载数据。无论是否已经加载过数据 
  15.  * 必须在主线程中调用。 
  16.  */  
  17. public void forceLoad() {  
  18.     onForceLoad();  
  19. }  
  20. protected void onForceLoad() {  
  21. }  
  22. /** 
  23.  * 由LoaderManager调用,禁止自己调用。 
  24.  * 停止向客户端反应最新的数据变化,但依旧能监听到。 
  25.  */  
  26. public void stopLoading() {  
  27.     mStarted = false;  
  28.     onStopLoading();  
  29. }  
  30. /** 
  31.  * 自己处理stop loading的逻辑的地方 
  32.  */  
  33. protected void onStopLoading() {  
  34. }  
  35. /** 
  36.  * 由LoaderManager调用,禁止私自调用。 
  37.  * 保留现有数据,但不会通知新数据变化 
  38.  */  
  39. public void abandon() {  
  40.     mAbandoned = true;  
  41.     onAbandon();  
  42. }  
  43. protected void onAbandon() {  
  44. }  
  45.   
  46. /** 
  47.  * 同上面stop一样 
  48.  */  
  49. public void reset() {  
  50.     onReset();  
  51.     //修改状态,略  
  52. }  
  53. protected void onReset() {  
  54. }  
        方法很多,只有forceLoad与cancelLoad能调用,其余的都是LoaderManager调用的。
        从上面可以看出,Loader只是定义了一些操作自身的函数,方便LoaderManager使用。它并没有真正加载数据,一般加载数据都是通过继承AsyncTaskLoader实现的

LoaderManager

        LoaderManager主要用来管理loader。可以在activity/fragment中调用getLoaderManager()获取LoaderManager的实例。LoaderManager只是一个接口,它的真正实现是其内部类<LoaderManagerImpl。

initLoader()

        通过该方法可以完成loader的初始化。LoaderManager会根据loader的状态自动调用第三个参数(以下称回调)中的方法。这也是回调的作用:根据loader的状态,执行不同的操作。

        第一个参数是指定当前loader的id,这是该loader的唯一标识。当id相同时,loader就相同。

        第二个参数是方便在初始化loader时传递参数。它最终会传递到回调中onCreateLoader()中成为第二个参数。

        initLoader()的部分代码如下:

[java]  view plain  copy
  1.     public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {  
  2.          ……  
  3.         LoaderInfo info = mLoaders.get(id);//mLoaders是SparseArray对象,所以key值可以为int类型的  
  4.          ……  
  5.         if (info == null) {  
  6.             // Loader doesn't already exist; create.  
  7.             info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);  
  8.         } else {  
  9.             info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;  
  10.         }  
  11.         if (info.mHaveData && mStarted) {  
  12.             // If the loader has already generated its data, report it now.  
  13.             info.callOnLoadFinished(info.mLoader, info.mData);  
  14.         }  
  15.         return (Loader<D>)info.mLoader;  
  16.     }  
        其中mLoaders存储的是当前所有激活的loader实例(These are the currently active loaders)。

        最后一个判断表明:如果当前的loader有数据,并且已经启动,就会直接调用LoadInfo.callOnLoadFinished(),而在该方法中会调用到LoaderCallbacks.onLoadFinished()

        当info为null时调用createAndInstallLoader(),同时将initLoader()中的所有参数传递进去。createAndInstallLoader()主要代码如下:

[java]  view plain  copy
  1. LoaderInfo info = createLoader(id, args, callback);  
  2. installLoader(info);  
        再看一下createLoader()的代码
[java]  view plain  copy
  1. private LoaderInfo createLoader(int id, Bundle args,  
  2.         LoaderManager.LoaderCallbacks<Object> callback) {  
  3.     LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);//分别为LoaderInfo中的mId,mArgs,mCallbacks赋值  
  4.     Loader<Object> loader = callback.onCreateLoader(id, args);//调用onCreateLoader生成Loader实例  
  5.     info.mLoader = (Loader<Object>)loader;//将生成的实例赋值到LoaderInfo#mLoader  
  6.     return info;  
  7. }  
        首先创建LoaderInfo,并调用回调中的onCreateLoader创建Loader。同时为info中的mLoader赋值。

        在installLoader()中,会调用mLoaders.put(info.mId, info),这里的info.mId就是initLoader()中的第一个参数。这样就将新建的loader存储到了mLoaders中。

        同时installLoader()还会调用info.start()。这是一个很重要的方法 ,因为它会调用Loader.startLoading()。代码为:

[java]  view plain  copy
  1. void start() {  
  2.     ……  
  3.     mStarted = true;  
  4.     ……  
  5.     if (mLoader == null && mCallbacks != null) {//再一次判断,防止mLoader为null  
  6.         mLoader = mCallbacks.onCreateLoader(mId, mArgs);  
  7.     }  
  8.     if (mLoader != null) {  
  9.                        ……  
  10.         if (!mListenerRegistered) {  
  11.                           mLoader.registerListener(mId, this);  
  12.                           mLoader.registerOnLoadCanceledListener(this);  
  13.                           mListenerRegistered = true;  
  14.                        }  
  15.         mLoader.startLoading();  
  16.     }  
  17. }  
        当mLoader不为null时,会调用mLoader.startLoading,同时为mLoader设置监听器。再结合createLoader()来看,mLoader的值就是LoaderCallbacks.onCreateLoader()的返回值。

        从这里开始已经由LoaderManager进入到了Loader中。

restartLoader

        当使用initLoader()时,如果指定的id的loader已经存在,系统会重用该loader;否则系统会创建一个新的。但是如果想丢弃掉旧的loader,应该使用restartLoader()。

AsyncTaskLoader

        异步执行加载操作。
        从上面的LoaderManager#initLoader()中可以看出,最后会执行的是onStartLoading。但AsyncTaskLoader并没有重写这个方法,而是重写了onForceLoad。由Loader的分析发现,onForceLoad()的调用是由forceLoad()引起的。因此,需要重写Loader#onStartLoading并调用forceLoad()。这一点可以从CursorLoader中得到佐证。如下:
[java]  view plain  copy
  1. protected void onStartLoading() {//CursorLoader.java  
  2.     if (mCursor != null) {  
  3.         deliverResult(mCursor);  
  4.     }  
  5.     if (takeContentChanged() || mCursor == null) {  
  6.         forceLoad();//调用forceLoad(),并在loadInBackground()进行查询操作。  
  7.     }  
  8. }  

onForceLoad

[java]  view plain  copy
  1. @Override  
  2. protected void onForceLoad() {//调用forceLoad会执行到该方法  
  3.     super.onForceLoad();//方法空实现  
  4.     cancelLoad();  
  5.     mTask = new LoadTask();  
  6.     executePendingTask();  
  7. }  
        LoadTask为AsyncTask的子类。最后一个方法如下:
[java]  view plain  copy
  1. void executePendingTask() {  
  2.     if (mCancellingTask == null && mTask != null) {  
  3.         ……  
  4.         mTask.executeOnExecutor(mExecutor, (Void[]) null);//启动AsyncTask进行异步执行  
  5.     }  
  6. }  

        在mTask的doInBackground中会调用onLoadInBackground(),它又调用loadInBackground(),这才是异步执行的地方。

        mTask为AsyncTask,故加载完毕后会走onPostExecute中,又会调用dispatchOnLoadComplete(),其作用主要是对加载结果进行分发。如下:

[java]  view plain  copy
  1. void dispatchOnLoadComplete(LoadTask task, D data) {  
  2.         if (mTask != task) {  
  3.             //Load complete of old task, trying to cancel.mTask只有在onForceLoad()中被赋值,所以这里的操作就是旧有的loader加载完数据  
  4.             dispatchOnCancelled(task, data);  
  5.         } else {  
  6.             if (isAbandoned()) {  
  7.                 // loader被废弃,所以给个回调,让子类有机会处理data的回收。参考CursorLoader.  
  8.                 onCanceled(data);  
  9.             } else {//通知数据变化,以及分发数据  
  10.                 commitContentChanged();//将Loader中的mProcessingChange设置为false  
  11.                 mLastLoadCompleteTime = SystemClock.uptimeMillis();  
  12.                 mTask = null;//关闭当前的loader  
  13.                 deliverResult(data);//很简单,只是调用Loader中关联的OnLoadCompleteListener回调  
  14.             }  
  15.         }  
  16.     }  
        在LoaderInfo#start()中,为Loader设置的OnLoadCompleteListener是LoaderInfo自身,因此上面的deliverResult会执行到LoaderInfo#onLoadComplete().有如下代码
[java]  view plain  copy
  1. if (mData != data || !mHaveData) {  
  2.                 mData = data;  
  3.                 mHaveData = true;  
  4.                 if (mStarted) {  
  5.                     callOnLoadFinished(loader, data);//这里最终会调用到Callback中的onLoadFinished  
  6.                 }  
  7.             }  

        至此,Loader的加载过程结束。这中间唯一需要自己操作的就是继承AsyncTaskLoader时,必须重写onStartLoad()并在其中调用forceLoad(),不然整个加载过程不完整。

总结

        LoaderManager通过id区分它所管理的loader。当两个loader的id相同时,便会被认为是同一个loader。

        如果当前id的loader已经存在,那么不会创建新的loader,只是更新initLoader()中的回调。

        如果当前id的loader已经存在,并且已经有数据,那么不会再次查询数据,而是直接将数据传递到回调中的onLoadFinished()中。

示例

        一个完整的对AsyncTaskLoader的实现,有些东西基本上是固定的,而且与CursorLoader类似。例子来源于google的TODO-MVP-Loaders。如下:

[java]  view plain  copy
  1. public class TasksLoader extends AsyncTaskLoader<List<Task>>  
  2.         implements TasksRepository.TasksRepositoryObserver{  
  3.     private TasksRepository mRepository;  
  4.     public TasksLoader(Context context, @NonNull TasksRepository repository) {  
  5.         super(context);  
  6.         checkNotNull(repository);//如果repository为null,此句话抛异常  
  7.         mRepository = repository;//为加载数据的数据源。根据实际逻辑换。  
  8.     }  
  9.   
  10.     @Override  
  11.     public List<Task> loadInBackground() {//异步加载数据  
  12.         return mRepository.getTasks();  
  13.     }  
  14.   
  15.     @Override  
  16.     public void deliverResult(List<Task> data) {  
  17.         if (isReset()) {//重置过,加载的数据就不需要。这里还需要对数据进行回收处理。CursorLoader中这里会关闭cursor  
  18.             return;  
  19.         }  
  20.   
  21.         if (isStarted()) {//start了,将新加载的数据传递给loader。最终会走到LoaderCallbacks#onLoadFinished  
  22.             super.deliverResult(data);  
  23.         }  
  24.         //这里一般需要对旧数据进行一些回收处理。  
  25.     }  
  26.   
  27.     @Override  
  28.     protected void onStartLoading() {  
  29.         if (mRepository.cachedTasksAvailable()) {//有缓存,就先把缓存给返回  
  30.             deliverResult(mRepository.getCachedTasks());  
  31.         }  
  32.         mRepository.addContentObserver(this);  
  33.         //takeContentChanged()的判断是一定有的  
  34.         if (takeContentChanged() || !mRepository.cachedTasksAvailable()) {  
  35.             //该loader停止后有数据变化,或者别的什么原因必须进行更新(本例中是缓存不可用),就强制更新数据。  
  36.             forceLoad();//它会调用到loadInBackground()。  
  37.         }  
  38.     }  
  39.   
  40.     @Override  
  41.     protected void onStopLoading() {//CursorLoader也这么调,没考虑为啥。  
  42.         cancelLoad();  
  43.     }  
  44.   
  45.     @Override  
  46.     protected void onReset() {  
  47.         onStopLoading();//  
  48.         //进行一些清尾工作  
  49.         mRepository.removeContentObserver(this);  
  50.     }  
  51.     //TasksRepository.TasksRepositoryObserver中的回调,跟Loader无关  
  52.     @Override  
  53.     public void onTasksChanged() {  
  54.         if (isStarted()) {  
  55.             forceLoad();  
  56.         }  
  57.     }  
  58. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值