设计目的
为了在Activity
和Fragment
中更加方便地异步加载数据.
注意: 实际上
Loader
类并不提供异步功能, 真正提供异步加载功能的是它的直接子类AsyncTaskLoader
.
而AsyncTaskLoader
是一个抽象类, 并不能直接使用, 官方仅提供了一个查询数据库或者ContentProvider
的类, 就是CursorLoader
类, 它直接继承了AsyncTaskLoader
.
因此, 实际使用中, 如果你想实现异步加载数据, 一般是继承
AsyncTaskLoader
, 然后在AsyncTaskLoader#loadInBackground()
中查询数据.
特点
1. 在Activity
和Fragment
中使用, 绑定生命周期
在Activity
和Fragment
中都有getLoaderManager()
方法返回一个LoaderManager
, 需要注意的是, Activity
自己单独持有一个LoaderManager
实例, 而每一个Fragment
都会持有一个实例. 而这些实例会保存在一个ArrayMap<String, LoaderManager>
中.
绑定生命周期的是LoaderManager
的实现类LoaderManagerImpl
, 它有一系列对应生命周期的方法, 例如在Activity#onStart()
会间接调用LoaderManagerImpl#doStart()
, 在这些方法中还会调用LoaderInfo
的对应方法, 然后LoaderInfo
根据当前的状态来调用Loader
的方法或者回调接口.
2. 异步加载数据
上面已经说过, 异步加载数据是AsyncTaskLoader
的特性, 如果你直接继承Loader
是不能异步加载数据的.
3. 监控数据源
监控数据源是由LoaderManagerImpl$ForceLoadContentObserver
实现的.
注意: 这个类没有在
Loader
和AsyncTaskLoader
中使用, 而是在CursorLoader
中用到因此这个特点仅仅属于CursorLoader
.
这里个人觉得是一个奇怪的设计, ForceLoadContentObserver
继承的是android.database.ContentObserver
, 而Loader
明显不是仅仅针对数据库的, 或者这个类放在CursorLoader
中更加合适.
4. 重建加载器时避免重新查询已经加载的数据
因为Loader
的实例是单独存放在LoaderManager
中了, 当Activity
或者Fragment
在因系统设置改变而重创建的时候LoaderManager
不会被回收, 因此Loader
也能够被重复利用.
注意, 如果是
Activity
被回收的情况, 那么LoaderManager
也会被回收.
LoaderManager
作用是在
Activity
/Fragment
和Loader
之间建立联系, 跟随Activity
/Fragment
的生命周期会有相关方法被调用, 因此Loader
具有绑定声明周期的特点.
获取实例
直接通过getLoaderManager()
获取实例, Activity
和Fragment
获取的过程稍有不同, 但是最后都会得到一个LoaderManagerImpl
实例.
LoaderManagerImpl
是LoaderManager
的唯一子类,Activity
和Fragment
中持有的都是LoaderManagerImpl
引用而不是LoaderManager
,LoaderManager
更多的意义是提供给使用者的接口.
创建Loader
通过LoaderManager#initLoader()
申请创建一个Loader
, 内部会根据ID
判断加载器是否已经存在, 如果存在则会重复使用, 如果不存在就会触发调用LoaderManager.LoaderCallbacks#onCreateLoader()
方法创建加载器.
LoaderManager
不是直接引用Loader
的, 而是通过LoaderInfo
包装Loader
,LoaderInfo
同时负责保存LoaderManager.LoaderCallbacks
回调接口和当前Loader
的状态和数据.
LoaderInfo
负责根据当前状态调用LoaderManager.LoaderCallbacks
中合适的回调方法.
如果是在Activity#onStart()
之前创建的Loader
会等到该方法的时候统一启动, 即间接调用Loader#start()
. 如果是在之后创建, 则会马上启动.
创建的Loader
之后就会和Activity
/Fragment
的生命周期关联起来.
LoaderManager
的实际作用就是把当前的生命周期传递给LoaderInfo
,Loader
的状态判断逻辑和接口回调都是由LoaderInfo
决定.
LoaderInfo
负责保存Loader
状态和直接处理LoaderManager.LoaderCallbacks
.
start()
启动方法, 会在Activity#onStart()
时被调用, 或者当Loader
在onStart()
之后初始化, 那么初始化时也会被被调用.
关键工作:
- 如果
Loader
还未创建, 那么会调用LoaderCallbacks#onCreateLoader()
创建实例 - 给
Loader
注册接口, 让Loader
在加载数据完成或者取消加载时可以通知LoaderInfo
- 调用了
Loader#startLoading()
, 通知Loader
开始加载数据
stop()
在LoaderManagerImpl#doStop()
中被调用, 应该会被Activity#onStop
间接调用.
关键工作:
- 重置了
mStarted
标志位 - 取消在
start()
中给Loader
注册的接口 - 调用了
Loader#stopLoading
, 通知Loader
停止加载数据
remain(), reportStart(), finishRetain()
根据生命周期处理Loader
状态和回调接口, 暂略过.
destroy()
主要工作:
- 如果
Loader
已经传递过数据, 那么调用LoaderCallbacks#onLoaderReset()
来通知client数据失效 - 取消在
start()
中给Loader
注册的接口 - 调用了
Loader#reset
, 通知Loader
重置数据
onLoadCanceled
在start()
中给Loader
注册的OnLoadCanceledListener
接口方法.
Loader
加载过程中被取消时通过注册的接口方法通知LoaderInfo
.
对于
AsyncTaskLoader
来说就是在AsyncTask#onCancelled()
中调用该方法.
在LoaderInfo
中, Loader
被取消时, 如果mPendingLoader
不为null
的话, 会把LoaderInfo
实例移出集合, 然后destroy()
自己, 接着开始加载mPendingLoader
mPendingLoader
: 当外部调用LoaderManager#restartLoader()
的时候, 如果当前指定的Loader
仍在加载数据, 那么就会创建一个新的Loader
赋值给mPendingLoader
, 而不会再次启动这个Loader
.
onLoadComplete
在start()
中给Loader
注册的OnLoadCompleteListener
接口方法.
当Loader
加载完成的时候通过这个方法通知LoaderInfo
同上, 如果mPendingLoader
不为null
的话, 会把LoaderInfo
实例移出集合, 然后destroy()
自己, 接着开始加载mPendingLoader
.
另外如果Loader
从未加载数据或者加载了新数据, 那么最后会调用LoaderCallbacks.onLoadFinished()
, 把数据传递给client.
自定义Loader
LoaderInfo
直接操作的是Loader
, 会在生命周期的不同阶段调用Loader
中的对应方法, 例如startLoading()
, reset()
, stopLoading()
等等.
在这些方法里面都会有对应的protect void onXXX()
方法, 例如onStartLoading()
, Loader
的子类就是通过重写这些方法来进行特定的逻辑.
因此, 自定义
Loader
需要重写其中的onXXXX()
方法. 各个方法代表的含义建议查看源码注释.
另外, 在上面已经提到, LoaderInfo
会给Loader
注册两个接口, Loader
应该在完成加载或者取消加载数据时通过这两个接口通知LoaderInfo
.
因此, 自定义
Loader
还需要在完成加载时调用OnLoadCompleteListener#onLoadComplete
, 在取消加载时调用OnLoadCanceledListener#onLoadCanceled
.
AsyncTaskLoader
在实际使用中, 我们不太可能直接继承Loader
, 而是继承AsyncTaskLoader
. 因为它已经实现了异步加载, 同时也处理了上述提到的两点, 这可以简化自定义Loader
的代码.
现在对AsyncTaskLoader
稍作分析:
onForceLoad()
AsyncTaskLoader
重写了Loader#onForceLoad()
, 看注释可以知道:
onForceLoad()
会被forceLoad()
调用, 后者会在请求异步加载数据时调用, 和startLoading()
的区别在于, 无论有无旧数据,forceLoad()
都会加载数据. 这个方法会在主线程被调用
上面的分析我们可以知道onStartLoading()
才是数据加载的发起点, 所以在这里我们可以知道
AsyncTaskLoader
自身没有发起加载数据, 因为它没有重写onStartLoading()
方法. 因此子类需要重写onStartLoading()
来启动加载数据.
在onForceLoad
中, AsyncTaskLoader
会执行一个AsyncTask
, 因此
AsyncTaskLoader
是通过AsyncTask
实现异步加载的.
在AsyncTask
中AsyncTaskLoader
会根据加载数据的情况调用OnLoadCompleteListener#onLoadComplete
或者OnLoadCanceledListener#onLoadCanceled
, 因此通知LoaderInfo
的工作AsyncTaskLoader
已经帮我们处理好了.
对应AsyncTask
, AsyncTaskLoader
提供了loadInBackground()
和onCanceled()
方法来让子类可以在后台线程加载数据和在任务被取消时作处理.
onCancelLoad()
AsyncTaskLoader
还重写了Loader#onCancelLoad()
, 看方法名就知道在这里应该取消加载数据.
在这个方法里面, 主要就是处理AsyncTask
, 根据AsyncTask
所处的不同状态处理, 如果AsyncTask
正在运行, 那么AsyncTaskLoader#cancelLoadInBackground()
会被调用.
这是因为对于AsyncTaskLoader
, 启动加载数据的工作是交给子类的, 而子类应该在后台线程中加载数据, 也就是AsyncTaskLoader#loadInBackground()
中加载, 那意味着AsyncTaskLoader
并不知道自己启动的后台线程做了什么, 所以提供这个方法来让子类可以取消在后台进行的工作.
因此,
AsyncTaskLoader
的子类需要在loadInBackground()
中加载数据, 另外还需要在cancelLoadInBackground()
中进行对应的操作来取消加载数据.
注意在子类中区分onCanceled()
和cancelLoadInBackground()
.
onCanceled()
是AsyncTask
被取消时被调用的, 只是取消了某一个Task, 有可能是子类发起了另一个Task.
cancelLoadInBackground()
是系统想取消加载数据时用来取消loadInBackground()
中的操作的, 此时整个Loader
都会停止加载数据.
CursorLoader
CursorLoader
是AsyncTaskLoader
的直接子类, 期望返回一个Cursor
实例.
因为AsyncTaskLoader
仅负责处理后台线程和回调LoaderInfo
注册的接口, 所以CursorLoader
实现了onReset()
, onStartLoading()
和onStopLoading()
来启动/停止/重置加载数据.
另外还实现了AsyncTaskLoader
的loadInBackground
, cancelLoadInBackground
和onCanceled()
.
loadInBackground()
在后台线程中, 主要工作是通过ContentResolver
来获取Cursor
实例.
因此,
CursorLoader
是通过ContentResolver#query
来获取Cursor
实例的.
注意: query()
可以通过一个CancellationSignal
实例来取消操作.
cancelLoadInBackground()
因为在loadInBackground()
中启动了一个query()
操作, 所以在这里需要取消这个query()
操作, CursorLoader
是通过CancellationSignal
实例来实现的.
onCanceled(Cursor)
简单的调用Cursor#close
关闭.
总结
- 主要还是关注
LoaderManager
和Loader
和生命周期的关系 -
LoaderInfo
是直接管理Loader
的类, 负责根据状态调用Loader
的方法. - 如果希望实现异步加载则继承
AsyncTaskLoader
而不是直接继承Loader
. 注意AsyncTaskLoader
不会启动加载数据. - 对于
CursorLoader
, 这是一个专门用作获取Cursor
的类, 用来配合ContentProvider
使用, 在我们自定义异步加载Loader的时候可以参考这个类的实现方式.
网友评论