Android ImageLoader框架之基本架構

Mr.Simple的專欄發表於2015-04-05

前言

在Android開發中,ImageLoader應該算得上是最重要的開源庫之一,由於專案原因(不能使用開源庫),前段時間自己也是需要實現一個簡單的ImageLoader,因此誕生了這個庫,我們暫且叫它為SimpleImageLoader。就目前而言,你上網查ImageLoader資料的時候,基本上能夠找到很簡單的實現,基本上一個類就把所有的工作給做了,這就顯得很不專業了嘛,很多時候我們不只是需要實現功能,而是希望能夠在實現功能的同時在設計層面有所提升。

SimpleImageLoader分享出來的主要目的並不是說替代那些著名開源庫,而是提供一個簡單的、又有一定參考價值的ImageLoader實現讓一些需要幫助的人學習,在深入瞭解實現的同時學到知識,也能夠體會到在設計一個開源庫時應該要做哪些考慮、做哪些取捨、有什麼模式,當然在瞭解了ImageLoader的實現之後再去使用專業的開源庫也會更加的得心應手,出現問題的時候自己也能夠不太費力地去究其原因。在提升自己的同時也能夠了解一些開源庫的設計基本原則,這也是我的部落格中一直主張的觀點。

當然限於本人水平有限,有bug在所難免,但是我們這裡是以學習為目的,瞭解ImageLoader的設計與實現才是我們最重要的目的,一些細節不必在意,可以在你深入的學習過程中修改你認為不合理或者錯誤的地方。如果你有好的實現方法或者有好的建議,也都請指教一二;如果你認為我寫的東西爛得不值一提,那你就深深地埋藏在心裡吧。

基本架構

一般來說,ImageLoader的實現都是基於執行緒池,在第一版的我也是使用執行緒池來載入圖片,但是後面的版本卻換成了跟SimpleNet類似的架構模式,原因是覺得執行緒池剛啟動的時候會稍微慢一些,我感覺不太爽就換了執行緒模型。當然我也會把執行緒池的版本在另外的分支上給出,這樣給一些需要的朋友參考。
SimpleImageLoader的基本結構如圖1所示。

圖1

看到這幅圖看過SimpleNet網路框架的朋友應該是會熟悉一些,基本結構與SimpleNet很相似,其實主要也是我們比較喜歡把架構圖畫成這種分層樣式,感覺比較好理解,

SimpleImageLoader類是使用者的入口,使用者在通過配置類初始化SimpleImageLoader之後就可以向SimpleImageLoader提交載入圖片的請求了。SimpleImageLoader內部維護了一個請求佇列,使用者提交的載入圖片的請求會在內部被封裝成BitmapRequest物件,然後將這些物件放到請求佇列中。在建立佇列時會建立使用者指定數量(預設為CPU個數 + 1)的執行緒來載入圖片,這些執行緒在內部命名為RequestDispatcher,它們在run函式中不斷地獲取佇列中的載入請求,然後交給對應的Loader載入圖片。

為了方便使用者的擴充套件,我們引入了Loader這個抽象,因為在SimpleImageLoader中只支援兩種圖片uri的載入,即網路圖片uri和本地檔案的uri。網路圖片一般以”http://”或者”https://”開頭,而本地圖片的uri格式卻是”file://”開頭,SimpleImageLoader內部通過圖片uri的格式的不同使用不同的Loader來載入圖片,這樣後續使用者就可以註冊Loader來實現其他格式的載入,例如”drawable:// + 圖片名”來載入res/drawable中的圖片等。這樣保證了SimpleImageLoader可載入圖片uri格式的可擴充套件性。Loader會通過LoaderManager來進行管理,如果需要註冊自己的Loader實現,則呼叫LoaderManager的register函式即可。如果你傳遞進去的圖片uri是無效,例如格式錯誤,那麼LoaderManager會返回一個預設的Loader,這個預設的Loader名為NullLoader,它其實什麼也不做,只是為了防止在外部進行判空而已,這種模式成為Null Object設計模式。當然,在載入圖片之前我們會從快取中讀取,如果有快取我們則不載入。

Loader載入完圖片之後會先更新UI,即將圖片顯示到對應的ImageView上,在構造BitmapRequest時內部已經將圖片的uri設定為ImageView的tag了。圖片載入完成後判斷ImageView的tag和uri是否相等,如果相等則將圖片顯示到ImageView上,否則不更新ImageView。這一步很重要,很多朋友在使用ImageLoader時出現問題基本上就是由於沒有設定ImageView的tag。

載入圖片的先後順序是由載入策略決定的,策略相關的內容沒有在架構圖中給出。載入策略決定了請求在佇列中的排序,在將請求新增到佇列中時會給每個請求設定一個序號,佇列將根據這個序號對請求進行排序。這樣我們就可以知道哪個請求是先新增進來的,也可以很方便的實現自己的策略類來定製自己的載入策略,比如最後載入到佇列中的請求最先載入。比如我們在ListView滾動時,最後新增到佇列中的圖片請求應該是我們最急需顯示的,我們它們就在手機的當前螢幕,而前面的請求對應的ImageView已經被複用,即使它們載入完成它們也不會被顯示,因為ImageView的tag已經變化了。因此,策略的靈活性依然很重要。

在載入完圖片並且更新UI之後,我們會將圖片快取起來。內建的快取型別有四種,無快取、記憶體快取、sd卡快取、記憶體和sd卡的雙快取,這四種快取都實現了Cache介面,如果你這四種快取型別還不能滿足你的需求,那麼你可以實現Cache介面,然後實現自己的快取邏輯,然後在配置ImageLoader時設定需要的快取型別(具體配置後續說明),如果不配置則預設使用的是記憶體快取。這裡我們又看到了一個面向介面程式設計的例子,即SimpleImageLoader只依賴於Cache介面的抽象,而不是說依賴於某個具體的快取類,這樣使用者就可以很方面的實現自己的快取邏輯,並且將快取實現注入到sdk中。當然,上述的Loader、載入策略實現也是基於同樣的理論基礎,就是說過很多遍的“面向介面程式設計”。

恩,是時候捋一捋這個執行流程了。

使用者呼叫displayImage請求載入圖片,SimpleImageLoader將這個載入圖片請求封裝成一個Request,然後加入到佇列中。幾個色眯眯的排程子執行緒不斷地從佇列中獲取請求,然後根據uri的格式獲取到對應的Loader來載入圖片。在載入圖片之前首先會檢視快取中是否含有目標圖片(具體細節在後續的部落格再細說),如果有快取則使用快取,否則載入目標圖片。獲取到圖片之後,我們會將圖片投遞給ImageView進行更新,如果該ImageView的tag與圖片的uri是一樣的,那麼則更新ImageView,否則不處理。使用ImageView的tag與圖片的uri進行對比是為了防止圖片錯位顯示的問題,這在ImageLoader中是很重要的一步。如果目標圖片沒有快取,第一次從uri中載入後會加入快取中,當然從sdcard中載入的圖片我們只會快取到記憶體中,而不會再快取一份到sd卡的另一個目錄中。這樣,整個載入過程也就完成了。

SimpleImageLoader工程結構圖

           

效果圖

上述效果中有四張圖片是顯示不出來的,因為在圖片uri列表中有三張是我手機中的圖片,模擬器中沒有,因此顯示載入失敗。還有一張是無效的uri,也是載入失敗。

Github倉庫地址將在下一篇部落格中給出,敬請期待!!

相關文章