設計目的
為了在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的時候可以參考這個類的實現方式.