筆記: Loader 加載器

參考 Loader
源碼分析
自定義Loader

設計目的

為了在ActivityFragment中更加方便地異步加載數據.

注意: 實際上Loader類并不提供異步功能, 真正提供異步加載功能的是它的直接子類AsyncTaskLoader.

AsyncTaskLoader是一個抽象類, 并不能直接使用, 官方僅提供了一個查詢數據庫或者ContentProvider的類, 就是CursorLoader類, 它直接繼承了AsyncTaskLoader.

因此, 實際使用中, 如果你想實現異步加載數據, 一般是繼承AsyncTaskLoader, 然后在AsyncTaskLoader#loadInBackground()中查詢數據.

特點

1. 在ActivityFragment中使用, 綁定生命周期

ActivityFragment中都有getLoaderManager()方法返回一個LoaderManager, 需要注意的是, Activity自己單獨持有一個LoaderManager實例, 而每一個Fragment都會持有一個實例. 而這些實例會保存在一個ArrayMap<String, LoaderManager>中.

綁定生命周期的是LoaderManager的實現類LoaderManagerImpl, 它有一系列對應生命周期的方法, 例如在Activity#onStart()會間接調用LoaderManagerImpl#doStart(), 在這些方法中還會調用LoaderInfo的對應方法, 然后LoaderInfo根據當前的狀態來調用Loader的方法或者回調接口.

2. 異步加載數據

上面已經說過, 異步加載數據是AsyncTaskLoader的特性, 如果你直接繼承Loader是不能異步加載數據的.

3. 監控數據源

監控數據源是由LoaderManagerImpl$ForceLoadContentObserver實現的.

注意: 這個類沒有在LoaderAsyncTaskLoader中使用, 而是在CursorLoader中用到因此這個特點僅僅屬于CursorLoader.

這里個人覺得是一個奇怪的設計, ForceLoadContentObserver繼承的是android.database.ContentObserver, 而Loader明顯不是僅僅針對數據庫的, 或者這個類放在CursorLoader中更加合適.

4. 重建加載器時避免重新查詢已經加載的數據

因為Loader的實例是單獨存放在LoaderManager中了, 當Activity或者Fragment在因系統設置改變而重創建的時候LoaderManager不會被回收, 因此Loader也能夠被重復利用.

注意, 如果是Activity被回收的情況, 那么LoaderManager也會被回收.

LoaderManager

作用是在Activity/FragmentLoader之間建立聯系, 跟隨Activity/Fragment的生命周期會有相關方法被調用, 因此Loader具有綁定聲明周期的特點.

獲取實例

直接通過getLoaderManager()獲取實例, ActivityFragment獲取的過程稍有不同, 但是最后都會得到一個LoaderManagerImpl實例.

LoaderManagerImplLoaderManager的唯一子類, ActivityFragment中持有的都是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()時被調用, 或者當LoaderonStart()之后初始化, 那么初始化時也會被被調用.

關鍵工作:

  1. 如果Loader還未創建, 那么會調用LoaderCallbacks#onCreateLoader()創建實例
  2. Loader注冊接口, 讓Loader在加載數據完成或者取消加載時可以通知LoaderInfo
  3. 調用了Loader#startLoading(), 通知Loader開始加載數據

stop()

LoaderManagerImpl#doStop()中被調用, 應該會被Activity#onStop間接調用.

關鍵工作:

  1. 重置了mStarted標志位
  2. 取消在start()中給Loader注冊的接口
  3. 調用了Loader#stopLoading, 通知Loader停止加載數據

remain(), reportStart(), finishRetain()

根據生命周期處理Loader狀態和回調接口, 暫略過.

destroy()

主要工作:

  1. 如果Loader已經傳遞過數據, 那么調用LoaderCallbacks#onLoaderReset()來通知client數據失效
  2. 取消在start()中給Loader注冊的接口
  3. 調用了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實現異步加載的.

AsyncTaskAsyncTaskLoader會根據加載數據的情況調用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

CursorLoaderAsyncTaskLoader的直接子類, 期望返回一個Cursor實例.

因為AsyncTaskLoader僅負責處理后臺線程和回調LoaderInfo注冊的接口, 所以CursorLoader實現了onReset(), onStartLoading()onStopLoading()來啟動/停止/重置加載數據.

另外還實現了AsyncTaskLoaderloadInBackground, cancelLoadInBackgroundonCanceled().

loadInBackground()

在后臺線程中, 主要工作是通過ContentResolver來獲取Cursor實例.

因此, CursorLoader是通過ContentResolver#query來獲取Cursor實例的.

注意: query()可以通過一個CancellationSignal實例來取消操作.

cancelLoadInBackground()

因為在loadInBackground()中啟動了一個query()操作, 所以在這里需要取消這個query()操作, CursorLoader是通過CancellationSignal實例來實現的.

onCanceled(Cursor)

簡單的調用Cursor#close關閉.

總結

  1. 主要還是關注LoaderManagerLoader和生命周期的關系
  2. LoaderInfo是直接管理Loader的類, 負責根據狀態調用Loader的方法.
  3. 如果希望實現異步加載則繼承AsyncTaskLoader而不是直接繼承Loader. 注意AsyncTaskLoader不會啟動加載數據.
  4. 對于CursorLoader, 這是一個專門用作獲取Cursor的類, 用來配合ContentProvider使用, 在我們自定義異步加載Loader的時候可以參考這個類的實現方式.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容