Android 深入理解Loader機(jī)制 讓APP輕裝上陣

Android開發(fā)者都經(jīng)歷過APP UI開發(fā)不當(dāng) 會(huì)造成overDraw,導(dǎo)致APP UI渲染過慢袖迎,但是很多人卻沒聽過overLoad,overLoad一般是由于開發(fā)者在主線程操作耗時(shí)操作腺晾,導(dǎo)致程序變慢 甚至出現(xiàn)的anr的現(xiàn)象燕锥,那么android早已為這種現(xiàn)象提供完美的解決方案,就是今天給大家說的Loader機(jī)制悯蝉。

一 Loader
Android的裝載器(loader)是從Android 3.0新引入的API , 主要完成單線程耗時(shí)數(shù)據(jù)異步裝載功能归形,并在數(shù)據(jù)有更新自動(dòng)通知UI刷新的作用。業(yè)內(nèi)也叫加載器鼻由,裝載機(jī)暇榴。
Loader用途
Loader一般用在Activity和fragment異步加載數(shù)據(jù),無需重新啟動(dòng)一個(gè)線程來執(zhí)行數(shù)據(jù)加載蕉世,異步加載可以用asyncTask, 但是loader自帶數(shù)據(jù)結(jié)果監(jiān)聽機(jī)制蔼紧,可以方便優(yōu)雅的進(jìn)行UI更新。

作用和優(yōu)點(diǎn):

提供異步加載數(shù)據(jù)功能狠轻;
對(duì)數(shù)據(jù)源變化進(jìn)行監(jiān)聽奸例,實(shí)時(shí)更新數(shù)據(jù);
在Activity配置發(fā)生變化(如橫豎屏切換)時(shí)不避免數(shù)據(jù)重復(fù)加載向楼;
適用于任何Activity和Fragment查吊;

加載耗時(shí)數(shù)據(jù)常用方式
android開發(fā)者都知道不能再UI線程里去執(zhí)行耗時(shí)操作,甚至在4.0里已經(jīng)無法在主線程里去訪問網(wǎng)絡(luò)湖蜕,那么一般加載耗時(shí)操作有以下辦法逻卖。

1 2B加載


2b.jpg

2 普通加載

普通.jpg

3 文藝加載

使用Loader.jpg

為何說1和2是不可取呢,我們從Loader源碼看起昭抒,文章結(jié)束會(huì)知道答案评也。

二 Loader實(shí)現(xiàn)

Loader源碼在android.content下面虚茶,可見它的份量有多重,loader機(jī)制包括LoaderManager仇参,Loader嘹叫,LoaderCallbacks三部分,
LoaderManager 來管理我們的laoder實(shí)例诈乒,獲取罩扇,初始化,重啟一個(gè)loader,
Loader 來執(zhí)行我們的異步操作怕磨,有開始喂饥,完成,后臺(tái)加載中等接口實(shí)現(xiàn)
LoaderCallbacks 來執(zhí)行我們的loader回調(diào)肠鲫,主要是綁定分發(fā)Loader员帮,完成加載,重置數(shù)據(jù)等导饲。
流程如下圖:


圖片1.png

1 LoaderManager
LoaderManager是抽象類捞高,負(fù)責(zé)管理一組Loader,主要定義執(zhí)行Loader的一些抽象方法,類結(jié)構(gòu)如下圖:

LoadManager.jpg

從上圖看以看出渣锦,Ta里面主要初始化loader,獲取 重啟,銷毀一個(gè)loader硝岗,也包含一個(gè)內(nèi)部成員變量LoaderCallback回調(diào),主要方便我們?cè)谏蠈訉懟卣{(diào)實(shí)現(xiàn)累操作袋毙,但真正是由他的實(shí)現(xiàn)類LoaderManagerImpl去完成操作的,
LoaderManagerImpl 記錄著一組LoaderInfo信息型檀,(ps:Activity也同等擁有activityInfo一樣,只不過記錄是Activityrecord來完成听盖,其實(shí)谷歌很多源碼都是相同的)胀溺,并且持有LoaderManager.LoaderCallbacks, mLoader等成員皆看,負(fù)責(zé)對(duì)Loader和LoaderCallbacks的對(duì)應(yīng)回調(diào)仓坞,內(nèi)部基于觀察者模式實(shí)現(xiàn),源碼不在解讀;

2 Loader
Loader是具體來操作任務(wù)的類悬蔽,負(fù)責(zé)去調(diào)用不同渠道的數(shù)據(jù)接口扯躺,比如數(shù)據(jù)庫(kù),contentProvider, 文件等蝎困。

loader.jpg

從大致的UML圖我可以了解loader持有一個(gè)內(nèi)部觀察者录语,和一些注冊(cè)注銷觀者的內(nèi)部方法,并且已經(jīng)暴露出來的加載操作的狀態(tài)步驟的方法禾乘,包括加載中澎埠,取消加載,強(qiáng)制加載始藕,內(nèi)容發(fā)生改變等蒲稳,
在平常的開發(fā)中氮趋,谷歌為我們提供了laoder的子類,AsyncTaskLoader江耀,CursorLoader等子類剩胁, 源碼不在介紹,現(xiàn)在說下他們的不同點(diǎn)祥国。CursorLoader也是AsyncTaskLoader的子類昵观,主要負(fù)責(zé)數(shù)據(jù)庫(kù)查詢的異步加載,AsyncTaskLoader可用來所有異步加載舌稀。

2.1 AsyncTaskLoader
AsyncTaskLoader繼承了Loader, 除了擁有l(wèi)oader的功能啊犬,還有executePendingTask(), dispatchOnCancelled()壁查,onLoadInBackground()等方法觉至,最神奇的是他擁有AsyncTask的實(shí)例,并且實(shí)現(xiàn)Runnable,這是他能進(jìn)行異步的原因所在睡腿。對(duì)AsyncTask不熟悉的請(qǐng)自我補(bǔ)腦语御,看如下代碼,

AsyncTaskLoader.jpg

筆者看了源碼嫉到,AsyncTaskLoader擁有AsyncTask沃暗,在自身實(shí)例化后開啟一個(gè)線程,自我進(jìn)行executePendingTask()何恶,此方法里其實(shí)就在執(zhí)行asyncTask的mTask.executeOnExecutor(mExecutor,(Void[])null);?來實(shí)現(xiàn)AsyncTaskLoader的自我監(jiān)聽機(jī)制,當(dāng)然自身輪詢和通信是離不開Handler的 因?yàn)檎麄€(gè)android的通訊就是建立在Handler(底層binder)基礎(chǔ)上嚼黔,這里不再分析细层。

2.2 CursorLoader
CursorLoader是AsyncTaskLoader的子類,內(nèi)部持有ForceLoadContentObserver變量唬涧,觀察者來實(shí)現(xiàn)對(duì)數(shù)據(jù)源的數(shù)據(jù)更新疫赎,執(zhí)行加載數(shù)據(jù)操作,最重要的當(dāng)然是離不開查詢操作碎节,內(nèi)部主要代碼:

CursorLoader

三 怎么使用loader

1 啟動(dòng)一個(gè)Loader
Activity初始化在oncreate()初始化捧搞,一個(gè)Activity或Fragment中LoaderManager管理一個(gè)或多個(gè)Loader實(shí)例,每個(gè)Activity或Fragment只有一個(gè)LoaderManager狮荔,我們可以在Activity的onCreate()或Fragment的onActivityCreated()里初始化一個(gè)Loader胎撇。例如:
getLoaderManager().initLoader(0, null, new DataLoaderCallback());

可以看見上面的initLoader()方法有三個(gè)參數(shù):

第一個(gè)參數(shù)代表當(dāng)前Loader的ID ,用來區(qū)分哪個(gè)loader;

第二個(gè)參數(shù)代表提供給Loader構(gòu)造函數(shù)的參數(shù)殖氏,Bundle對(duì)象類型 晚树,可選;

第三個(gè)參數(shù)代表LoaderManager.LoaderCallbacks的回調(diào)實(shí)現(xiàn) 需要我自我實(shí)現(xiàn)雅采。

上面initLoader()方法的調(diào)用一個(gè)Loader被初始化和激活的狀態(tài)爵憎,該方法的調(diào)運(yùn)有如下兩種結(jié)果:

如果代表該Loader的ID已經(jīng)存在慨亲,則后面創(chuàng)建的Loader將直接復(fù)用已經(jīng)存在的;

如果代表該Loader的ID不存在宝鼓,initLoader()會(huì)觸發(fā)LoaderManager.LoaderCallbacks回調(diào)的onCreateLoader()方法創(chuàng)建一個(gè)Loader刑棵;

可以看見通過initLoader()方法可以將LoaderManager.LoaderCallbacks實(shí)例與Loader進(jìn)行關(guān)聯(lián),且當(dāng)Loader的狀態(tài)變化時(shí)就被回調(diào)愚铡。所以說蛉签,如果調(diào)用者正處于其開始狀態(tài)并且被請(qǐng)求的Loader已經(jīng)存在,且已產(chǎn)生了數(shù)據(jù)茂附,那么系統(tǒng)會(huì)立即調(diào)用onLoadFinished()(在initLoader()調(diào)用期間)正蛙,所以你必須考慮到這種情況的發(fā)生。

當(dāng)然了营曼,intiLoader()會(huì)返回一個(gè)創(chuàng)建的Loader乒验,但是你不用獲取它的引用,因?yàn)長(zhǎng)oadeManager會(huì)自動(dòng)管理該Loader的生命周期蒂阱,你只用在它回調(diào)提供的生命周期方法中做自己數(shù)據(jù)邏輯的處理即可锻全。

2 實(shí)現(xiàn)LoaderManager.Callbacks回調(diào)

LoaderManager.LoaderCallbacks是LoaderManager的回調(diào)交互接口。LoaderManager.LoaderCallbacks包含以下三個(gè)方法:

onCreateLoader()
實(shí)例化并返回一個(gè)新創(chuàng)建給指定ID的Loader對(duì)象录煤;第一啟動(dòng)時(shí)調(diào)用

onLoadFinished()
load完成之后回調(diào)此方法鳄厌;每次都調(diào)用

onLoaderReset()
當(dāng)創(chuàng)建好的Loader被reset時(shí)調(diào)用此方法,會(huì)清空已綁定數(shù)據(jù)妈踊,此時(shí)CreatLoader會(huì)重新執(zhí)行

3 Loader使用實(shí)例

1》 初始化loader

getLoaderManager().initLoader(0, null, new DataLoaderCallback());

2》實(shí)現(xiàn)callback接口了嚎,注冊(cè)自我監(jiān)聽回調(diào)

callback

當(dāng)然你也可以用來綁定谷歌提供的CursorLoader ,在Loader創(chuàng)建的時(shí)候被調(diào)用,這里使用一個(gè)ContentProvider獲取數(shù)據(jù)廊营,所以使用CursorLoader返回?cái)?shù)據(jù)

CursorLoader

3》 繼承Loader歪泳,構(gòu)造自我的數(shù)據(jù)綁定,和數(shù)據(jù)適配

Loader

在這里我們模擬了構(gòu)造一組數(shù)據(jù)露筒,當(dāng)然你也可以在loadInBackgruond去讀文件呐伞,訪問網(wǎng)絡(luò),查詢數(shù)據(jù)庫(kù)

4 拓展
1》 用來自動(dòng)刷新ContentPorvider

在我們使用CurSorLoader時(shí)大家都會(huì)考慮一種情況的處理—–當(dāng)數(shù)據(jù)庫(kù)發(fā)生變化時(shí)如何自動(dòng)刷新當(dāng)前UI慎式,數(shù)據(jù)庫(kù)在數(shù)據(jù)改變時(shí)通過ContentPorvider和ContentResolver發(fā)出通知伶氢,接著ContentProvider通知Cursor的觀察者數(shù)據(jù)發(fā)生了變化,然后Cursor通知CursorLoader的觀察者數(shù)據(jù)發(fā)生了變化瘪吏,CursorLoader又通過ContentProvider加載新數(shù)據(jù)癣防,完成后調(diào)用CursorAdapter的changeCursor()用新數(shù)據(jù)替換舊數(shù)據(jù)顯示。

這個(gè)過程具體的實(shí)現(xiàn)步驟如下:

對(duì)獲取的Cursor數(shù)據(jù)設(shè)置需要監(jiān)聽的URI(即肪虎,在ContentProvider的query()方法或者Loader的loadingBackground()方法中調(diào)用Cursor的setNotificationUri()方法)劣砍;

在ContentProvider的insert()、update()扇救、delete()等方法中調(diào)用ContentResolver的notifyChange()方法刑枝;

通過上面兩步我們就能實(shí)現(xiàn)CurSorLoader的自動(dòng)數(shù)據(jù)刷新功能了香嗓;可以發(fā)現(xiàn),所謂的CurSorLoader自動(dòng)刷新也是對(duì)文章開頭說的觀察者模式装畅,所以不再過多說明靠娱。
2》不使用ContentPorvider的自動(dòng)刷新


自動(dòng)數(shù)據(jù)刷

四Loaders相關(guān)源碼流程

通過上面我們的源碼分析和分析前那副圖可以總結(jié)如下結(jié)論:

一次完整的數(shù)據(jù)加載流程為Activity調(diào)用LoaderManager的doStart()方法,LoaderManager調(diào)用Loader的startLoading()方法掠兄,然后Loader調(diào)運(yùn)AsyncTaskLoader的doingBackground()方法進(jìn)行耗時(shí)數(shù)據(jù)加載像云,緊接著AsyncTaskLoader回調(diào)LoaderManager的complete數(shù)據(jù)加載完成方法,接著又LoaderManager回調(diào)我們?cè)贏ctivity中實(shí)現(xiàn)的callback中的onLoadFinish()方法蚂夕。

Acivity和Fragment的生命周期主動(dòng)管理了LoaderManager迅诬,每個(gè)Activity用一個(gè)ArrayMap的mAllLoaderManager來保存當(dāng)前Activity及其附屬Frament的唯一LoaderManager;在Activity配置發(fā)生變化時(shí)婿牍,Activity在destory前會(huì)保存mAllLoaderManager侈贷,當(dāng)Activity再重新創(chuàng)建時(shí),會(huì)在Activity的onAttcach()等脂、onCreate()俏蛮、performStart()方法中恢復(fù)mAllLoaderManager。

LoaderManager給Activity提供了管理自己的一些方法上遥;同時(shí)主動(dòng)管理了對(duì)應(yīng)的Loader搏屑,它把每一個(gè)Loader封裝為L(zhǎng)oadInfo對(duì)象,同時(shí)它負(fù)責(zé)主動(dòng)調(diào)運(yùn)管理Loader的startLoading()粉楚、stopLoading()辣恋、,forceLoad()等方法。

由于整個(gè)Activity和Fragment主動(dòng)管理了Loader模软,所以關(guān)于Loader的釋放(譬如Cursor要要主動(dòng)關(guān)閉游標(biāo)的等抑党,文件流要置空等)不需要我們?nèi)藶樘幚恚琇oader會(huì)幫我們很好的處理的撵摆;同時(shí)特別注意,對(duì)于CursorLoader害晦,當(dāng)我們數(shù)據(jù)源發(fā)生變化時(shí)Loader框架會(huì)通過ContentObserver調(diào)用onContentChanged的forceLoad方法重新請(qǐng)求數(shù)據(jù)進(jìn)行回調(diào)刷新特铝。

五 總結(jié)
通過前面基礎(chǔ)實(shí)例、源碼分析壹瘟、拓展你會(huì)發(fā)現(xiàn)Loader很強(qiáng)大鲫剿,例如在普通展現(xiàn)某個(gè)android手機(jī)有多少應(yīng)用程序,加載已安裝app時(shí)候稻轨,其實(shí)loader就能排上用場(chǎng)灵莲。
詳細(xì)見谷歌對(duì)Loader介紹:
https://developer.android.com/reference/android/content/AsyncTaskLoader.html

PS:順便說下AsyncTaskLoader與AsyncTask的區(qū)別殴俱,看完源碼我們?cè)倩剡^頭來總結(jié)性的說說他們二者區(qū)別政冻,如下:

兩者區(qū)別

最主要是加載數(shù)據(jù)枚抵,使用loader我們無須關(guān)注數(shù)據(jù)何時(shí)改變了,也無需關(guān)注activity的生命周期明场,做到數(shù)據(jù)不被重復(fù)多次加載情況汽摹,activty銷毀數(shù)據(jù)自動(dòng)釋放的作用,做到一次加載多次使用的效果苦锨,我們可以依據(jù)需求逼泣,拿著loader變活靈通,這里的博大精深還需要你自己體會(huì)舟舒。
案例:https://github.com/NeglectedByBoss/Loader
更多文章請(qǐng)猛戳

第一時(shí)間獲取大佬技術(shù)文章和行業(yè)動(dòng)態(tài)請(qǐng)關(guān)注微信公眾號(hào)拉庶!

開發(fā)者技術(shù)前線
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市秃励,隨后出現(xiàn)的幾起案子氏仗,更是在濱河造成了極大的恐慌,老刑警劉巖莺治,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廓鞠,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡谣旁,警方通過查閱死者的電腦和手機(jī)床佳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榄审,“玉大人砌们,你說我怎么就攤上這事「榻” “怎么了浪感?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)饼问。 經(jīng)常有香客問我影兽,道長(zhǎng),這世上最難降的妖魔是什么莱革? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任峻堰,我火速辦了婚禮,結(jié)果婚禮上盅视,老公的妹妹穿的比我還像新娘捐名。我一直安慰自己,他們只是感情好闹击,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布镶蹋。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贺归。 梳的紋絲不亂的頭發(fā)上淆两,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音牧氮,去河邊找鬼琼腔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛踱葛,可吹牛的內(nèi)容都是我干的丹莲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼尸诽,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼甥材!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起性含,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤洲赵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后商蕴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叠萍,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年绪商,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了苛谷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡格郁,死狀恐怖腹殿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情例书,我是刑警寧澤锣尉,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站决采,受9級(jí)特大地震影響自沧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜树瞭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一暂幼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧移迫,春花似錦、人聲如沸管行。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捐顷。三九已至荡陷,卻和暖如春雨效,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背废赞。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工徽龟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唉地。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓据悔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親耘沼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子极颓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,302評(píng)論 25 707
  • 1 背景## 在Android中任何耗時(shí)的操作都不能放在UI主線程中,所以耗時(shí)的操作都需要使用異步實(shí)現(xiàn)群嗤。同樣的菠隆,在...
    我是昵稱閱讀 1,221評(píng)論 0 3
  • 你一直低頭 盯著書本 那么柔雅 那么恬靜 不知道 我像你盯著書本那樣 一直 靜靜地盯著你 我撓撓頭 實(shí)在猜不透 哪...
    蝸牛在路上遇到了黃鸝閱讀 265評(píng)論 0 0
  • 每次換電腦都得重新安裝所有的環(huán)境和軟件,為方便日后再次安裝狂秘,特此記錄骇径。 1、安裝 Homebrew 直接打開終端并...
    SwiftAlan閱讀 1,423評(píng)論 0 0