本文轉(zhuǎn)自Mr.Simple的博客,如侵刪
前言
在Android開發(fā)中,ImageLoader應(yīng)該算得上是最重要的開源庫之一,由于項目原因(不能使用開源庫),前段時間自己也是需要實現(xiàn)一個簡單的ImageLoader囚霸,因此誕生了這個庫,我們暫且叫它為SimpleImageLoader激才。就目前而言拓型,你上網(wǎng)查ImageLoader資料的時候,基本上能夠找到很簡單的實現(xiàn)瘸恼,基本上一個類就把所有的工作給做了劣挫,這就顯得很不專業(yè)了嘛,很多時候我們不只是需要實現(xiàn)功能钞脂,而是希望能夠在實現(xiàn)功能的同時在設(shè)計層面有所提升揣云。
SimpleImageLoader分享出來的主要目的并不是說替代那些著名開源庫,而是提供一個簡單的冰啃、又有一定參考價值的ImageLoader實現(xiàn)讓一些需要幫助的人學(xué)習(xí)邓夕,在深入了解實現(xiàn)的同時學(xué)到知識,也能夠體會到在設(shè)計一個開源庫時應(yīng)該要做哪些考慮阎毅、做哪些取舍焚刚、有什么模式,當(dāng)然在了解了ImageLoader的實現(xiàn)之后再去使用專業(yè)的開源庫也會更加的得心應(yīng)手扇调,出現(xiàn)問題的時候自己也能夠不太費力地去究其原因矿咕。在提升自己的同時也能夠了解一些開源庫的設(shè)計基本原則,這也是我的博客中一直主張的觀點狼钮。
當(dāng)然限于本人水平有限碳柱,有bug在所難免,但是我們這里是以學(xué)習(xí)為目的熬芜,了解ImageLoader的設(shè)計與實現(xiàn)才是我們最重要的目的莲镣,一些細(xì)節(jié)不必在意,可以在你深入的學(xué)習(xí)過程中修改你認(rèn)為不合理或者錯誤的地方涎拉。如果你有好的實現(xiàn)方法或者有好的建議瑞侮,也都請指教一二;如果你認(rèn)為我寫的東西爛得不值一提鼓拧,那你就深深地埋藏在心里吧半火。
基本架構(gòu)
一般來說,ImageLoader的實現(xiàn)都是基于線程池季俩,在第一版的我也是使用線程池來加載圖片钮糖,但是后面的版本卻換成了跟SimpleNet類似的架構(gòu)模式,原因是覺得線程池剛啟動的時候會稍微慢一些酌住,我感覺不太爽就換了線程模型店归。當(dāng)然我也會把線程池的版本在另外的分支上給出,這樣給一些需要的朋友參考赂韵。
SimpleImageLoader的基本結(jié)構(gòu)如圖1所示娱节。
看到這幅圖看過SimpleNet網(wǎng)絡(luò)框架的朋友應(yīng)該是會熟悉一些,基本結(jié)構(gòu)與SimpleNet很相似祭示,其實主要也是我們比較喜歡把架構(gòu)圖畫成這種分層樣式肄满,感覺比較好理解,
SimpleImageLoader類是用戶的入口质涛,用戶在通過配置類初始化SimpleImageLoader之后就可以向SimpleImageLoader提交加載圖片的請求了稠歉。SimpleImageLoader內(nèi)部維護(hù)了一個請求隊列,用戶提交的加載圖片的請求會在內(nèi)部被封裝成BitmapRequest對象汇陆,然后將這些對象放到請求隊列中怒炸。在創(chuàng)建隊列時會創(chuàng)建用戶指定數(shù)量(默認(rèn)為CPU個數(shù) + 1)的線程來加載圖片,這些線程在內(nèi)部命名為RequestDispatcher毡代,它們在run函數(shù)中不斷地獲取隊列中的加載請求阅羹,然后交給對應(yīng)的Loader加載圖片勺疼。
為了方便用戶的擴(kuò)展,我們引入了Loader這個抽象捏鱼,因為在SimpleImageLoader中只支持兩種圖片uri的加載执庐,即網(wǎng)絡(luò)圖片uri和本地文件的uri。網(wǎng)絡(luò)圖片一般以"http:// "或者"https:// "開頭导梆,而本地圖片的uri格式卻是"file://"開頭轨淌,SimpleImageLoader內(nèi)部通過圖片uri的格式的不同使用不同的Loader來加載圖片,這樣后續(xù)用戶就可以注冊Loader來實現(xiàn)其他格式的加載看尼,例如"drawable:// + 圖片名"來加載res/drawable中的圖片等递鹉。這樣保證了SimpleImageLoader可加載圖片uri格式的可擴(kuò)展性。Loader會通過LoaderManager來進(jìn)行管理藏斩,如果需要注冊自己的Loader實現(xiàn)躏结,則調(diào)用LoaderManager的register函數(shù)即可。如果你傳遞進(jìn)去的圖片uri是無效灾茁,例如格式錯誤窜觉,那么LoaderManager會返回一個默認(rèn)的Loader,這個默認(rèn)的Loader名為NullLoader北专,它其實什么也不做禀挫,只是為了防止在外部進(jìn)行判空而已,這種模式成為Null Object設(shè)計模式拓颓。當(dāng)然语婴,在加載圖片之前我們會從緩存中讀取,如果有緩存我們則不加載驶睦。
Loader加載完圖片之后會先更新UI砰左,即將圖片顯示到對應(yīng)的ImageView上,在構(gòu)造BitmapRequest時內(nèi)部已經(jīng)將圖片的uri設(shè)置為ImageView的tag了场航。圖片加載完成后判斷ImageView的tag和uri是否相等缠导,如果相等則將圖片顯示到ImageView上,否則不更新ImageView溉痢。這一步很重要僻造,很多朋友在使用ImageLoader時出現(xiàn)問題基本上就是由于沒有設(shè)置ImageView的tag。
加載圖片的先后順序是由加載策略決定的孩饼,策略相關(guān)的內(nèi)容沒有在架構(gòu)圖中給出髓削。加載策略決定了請求在隊列中的排序,在將請求添加到隊列中時會給每個請求設(shè)置一個序號镀娶,隊列將根據(jù)這個序號對請求進(jìn)行排序立膛。這樣我們就可以知道哪個請求是先添加進(jìn)來的,也可以很方便的實現(xiàn)自己的策略類來定制自己的加載策略梯码,比如最后加載到隊列中的請求最先加載宝泵。比如我們在ListView滾動時好啰,最后添加到隊列中的圖片請求應(yīng)該是我們最急需顯示的,我們它們就在手機(jī)的當(dāng)前屏幕鲁猩,而前面的請求對應(yīng)的ImageView已經(jīng)被復(fù)用坎怪,即使它們加載完成它們也不會被顯示罢坝,因為ImageView的tag已經(jīng)變化了廓握。因此,策略的靈活性依然很重要嘁酿。
在加載完圖片并且更新UI之后隙券,我們會將圖片緩存起來。內(nèi)置的緩存類型有四種闹司,無緩存娱仔、內(nèi)存緩存、sd卡緩存游桩、內(nèi)存和sd卡的雙緩存牲迫,這四種緩存都實現(xiàn)了Cache接口,如果你這四種緩存類型還不能滿足你的需求借卧,那么你可以實現(xiàn)Cache接口盹憎,然后實現(xiàn)自己的緩存邏輯,然后在配置ImageLoader時設(shè)置需要的緩存類型(具體配置后續(xù)說明)铐刘,如果不配置則默認(rèn)使用的是內(nèi)存緩存陪每。這里我們又看到了一個面向接口編程的例子,即SimpleImageLoader只依賴于Cache接口的抽象镰吵,而不是說依賴于某個具體的緩存類檩禾,這樣用戶就可以很方面的實現(xiàn)自己的緩存邏輯,并且將緩存實現(xiàn)注入到sdk中疤祭。當(dāng)然盼产,上述的Loader、加載策略實現(xiàn)也是基于同樣的理論基礎(chǔ)勺馆,就是說過很多遍的“面向接口編程”戏售。
恩,是時候捋一捋這個執(zhí)行流程了谓传。
用戶調(diào)用displayImage請求加載圖片蜈项,SimpleImageLoader將這個加載圖片請求封裝成一個Request,然后加入到隊列中续挟。幾個色瞇瞇的調(diào)度子線程不斷地從隊列中獲取請求紧卒,然后根據(jù)uri的格式獲取到對應(yīng)的Loader來加載圖片。在加載圖片之前首先會查看緩存中是否含有目標(biāo)圖片(具體細(xì)節(jié)在后續(xù)的博客再細(xì)說)诗祸,如果有緩存則使用緩存跑芳,否則加載目標(biāo)圖片轴总。獲取到圖片之后,我們會將圖片投遞給ImageView進(jìn)行更新博个,如果該ImageView的tag與圖片的uri是一樣的怀樟,那么則更新ImageView,否則不處理盆佣。
使用ImageView的tag與圖片的uri進(jìn)行對比是為了防止圖片錯位顯示的問題往堡,這在ImageLoader中是很重要的一步。如果目標(biāo)圖片沒有緩存共耍,第一次從uri中加載后會加入緩存中虑灰,當(dāng)然從sdcard中加載的圖片我們只會緩存到內(nèi)存中,而不會再緩存一份到sd卡的另一個目錄中痹兜。這樣穆咐,整個加載過程也就完成了。
SimpleImageLoader工程結(jié)構(gòu)圖
效果圖
上述效果中有四張圖片是顯示不出來的字旭,因為在圖片uri列表中有三張是我手機(jī)中的圖片对湃,模擬器中沒有,因此顯示加載失敗遗淳。還有一張是無效的uri拍柒,也是加載失敗。
GitHub