當前位置:
首頁 > 最新 > Flutter圖片緩存 Image.network源碼分析

Flutter圖片緩存 Image.network源碼分析

來這裡找志同道合的小夥伴!

作 者 簡 介

郭海生

Android高級工程師,6年以上開發經驗,有豐富的代碼重構和架構設計經驗,負責京東商城我的京東的開發工作,熱衷於學習和研究新技術。

隨著手機設備硬體水平的飛速發展,用戶對於圖片的顯示要求也越來越高,稍微處理不好就會容易造成內存溢出等問題。所以我們在使用Image的時候,建立一個圖片緩存機制已經是一個常態。Android目前提供了很豐富的圖片框架,像ImageLoader、Glide、Fresco等。對於Flutter而言,為了探其緩存機制或者定製自己的緩存框架,特從其Image入手進行突破。

>>>>

Image 的用法

Image是Flutter里提供的顯示圖片的控制項,類似Android里ImageView,不過其用法有點類似Glide等圖片框架。

我們先看Image的用法。Flutter對Image控制項提供了多種構造函數:

我們只分析Image.network源碼,分析理解完這個之後,其他的也是一樣的思路。

我們先從Image.network的用法入手:顯示一個網路圖片很簡單,直接通過Image.network攜帶一個url參數即可。

範例:

>>>>

Image結構UML類圖


可以看到Image的框架結構還是有點兒複雜的,在你只調用一行代碼的情況下,其實Flutter為你做了很多工作。

初步梳理下每個類概念

StatefulWidget就是有狀態的Widget,是展示在頁面上的元素。

Image繼承於StatefulWidget,是來顯示和載入圖片。

State控制著StatefulWidget狀態改變的生命周期,當Widget被創建、Widget配置信息改變或者Widget被銷毀等等,State的一系列方法會被調用。

_ImageState繼承於State,處理State生命周期變化以及生成Widget。

ImageProvider提供載入圖片的入口,不同的圖片資源載入方式不一樣,只要重寫其load方法即可。同樣,緩存圖片的key值也有其生成。

NetWorkImage負責下載網路圖片的,將下載完成的圖片轉化成ui.Codec對象交給ImageStreamCompleter去處理解析。

ImageStreamCompleter就是逐幀解析圖片的。

ImageStream是處理Image Resource的,ImageState通過ImageStream與ImageStreamCompleter建立聯繫。ImageStream里也存儲著圖片載入完畢的監聽回調。

MultiFrameImageStreamCompleter就是多幀圖片解析器。

先把Image的框架結構了解一下,有助於下面我們更加清晰地分析代碼。

>>>>

源碼分析

我們看下Image.network都做了什麼:

我們看到Image是一個StatefulWidget對象,可以直接放到Container或者Column等容器里,其屬性解釋如下:

width:widget的寬度

height:widget的高度

color:與colorBlendMode配合使用,將此顏色用BlendMode方式混合圖片

colorBlendMode:混合模式演算法

fit:與android:scaletype一樣,控制圖片如何resized/moved來匹對Widget的size

alignment:widget對齊方式

repeat:如何繪製未被圖像覆蓋的部分

centerSlice:支持9patch,拉伸的中間的區域

matchTextDirection:繪製圖片的方向:是否從左到右

gaplessPlayback:圖片變化的時候是否展示老圖片或者什麼都不展示

headers:http請求頭

image:一個ImageProvide對象,在調用的時候已經實例化,這個類主要承擔了從網路載入圖片的功能。它是載入圖片的最重要的方法,不同的圖片載入方式(assert文件載入、網路載入等等)也就是重寫ImageProvider載入圖片的方法(load())。

Image是一個StatefulWidget對象,所以我們看它的State對象:

我們對_ImageState的兩個屬性對象解釋一下:

ImageStream是處理Image Resource的,ImageStream里存儲著圖片載入完畢的監聽回調,ImageStreamCompleter也是其成員,這樣ImageStream將圖片的解析流程交給了ImageStreamCompleter去處理。

ImageInfo包含了Image的數據源信息:width和height以及ui.Image。 將ImageInfo里的ui.Image設置給RawImage就可以展示了。RawImage就是我們真正渲染的對象,是顯示ui.Image的一個控制項,接下來我們會看到。

我們知道State的生命周期,首先State的initState執行,然後didChangeDependencies會執行,我們看到ImageState里沒有重寫父類的initState,那我們看其didChangeDependencies():

>>>>

_resolveImage方法解析

我們看到首先調用了resolveImage(),我們看下resolveImage方法:

這個方法是處理圖片的入口。widget.image這個就是上面的創建的NetworkImage對象,是個ImageProvider對象,調用它的resolve並且傳進去默認的ImageConfiguration。 我們看下resolve方法,發現NetworkImage沒有,果不其然,我們在其父類ImageProvider找到了:

我們看到這個方法創建了ImageStream並返回,調用obtainKey返回一個攜帶NetworkImage的future,以後會作為緩存的key使用,並且調用ImageStream的setCompleter的方法:

這個是Flutter默認提供的內存緩存api的入口方法,這個方法會先通過key獲取之前的ImageStreamCompleter對象,這個key就是NetworkImage對象,當然我們也可以重寫obtainKey方法自定義key,如果存在則直接返回,如果不存在則執行load方法載入ImageStreamCompleter對象,並將其放到首位(最少最近使用演算法)。

也就是說ImageProvider已經實現了內存緩存:默認緩存圖片的最大個數是1000,默認緩存圖片的最大空間是10MiB。 第一次載入圖片肯定是沒有緩存的,所以我們看下loader方法,我們看到ImageProvider是空方法,我們去看NetWorkImage,按照我們的預期確實在這裡:

這個方法為我們創建了一個MultiFrameImageStreamCompleter對象,根據名字我們也能知道它繼承於ImageStreamCompleter。還記得ImageStreamCompleter是做什麼的嗎,就是輔助ImageStream管理解析Image的。

參數解析

_loadAsync()是請求網路載入圖片的方法

scale是縮放係數

informationCollector是信息收集對象的,提供錯誤或者其他日誌用

MultiFrameImageStreamCompleter是多幀的圖片處理載入器,我們知道Flutter的Image支持載入gif,通過MultiFrameImageStreamCompleter可以對gif文件進行解析:

我們看到MultiFrameImageStreamCompleter拿到loadAsync返回的codec數據對象,通過handleCodecReady來處理數據,然後會調用_decodeNextFrameAndSchedule方法:

通過codec.getNextFrame()去拿下一幀,對於靜態的圖片frameCount是1,直接用ImageInfo組裝image,交給emitFrame方法,這個方法里會調用setImage,如下:

setImage方法就是設置當前的ImageInfo並檢查監聽器列表,通知監聽器圖片已經載入完畢可以刷新UI了。

對於動圖來說就是就是交給SchedulerBinding逐幀的去調用setImage,通知UI刷新,代碼就不貼了,有興趣的可以自行查看下。 至此resolveImage調用流程我們算是講完了,接下來我們看listenToStream。

>>>>

_listenToStream方法解析

我們繼續分析didChangeDependencies方法,這個方法里會判斷TickerMode.of(context)的值,這個值默認是true,和AnimationConrol有關,後續可以深入研究。然後調用_listenToStream()。 我們看下這個方法:

這個就是添加圖片載入完畢的回調器。還記得嗎,當圖片載入並解析完畢的時候,MultiFrameImageStreamCompleter的setImage方法會調用這裡傳過去的回調方法。我們看下這裡回調方法里做了什麼:

很顯然就是拿到上層傳過來ImageInfo,調用setState更新UI 我們看下build方法:

就是用imageInfo和widget的信息來封裝RawImage,RawImage是RenderObjectWidget對象,是應用程序真正渲染的對象,將咱們的圖片顯示到界面上。

>>>>

總結

梳理下流程:

從入口開始,Image是繼承於StatefulWidget,它為咱們實現好了State:_ImageState,並且提供了一個已經實例化的NetWorkImage對象,它是繼承於ImageProvider對象的。

ImageState創建完之後,ImageState通過調用resolveImage(),resolveImage()又會調用ImageProvider的resolve()方法返回一個ImageStream對象。_ImageState也註冊了監聽器給ImageStream,當圖片下載完畢後會執行回調方法。

然後在ImageProvider的resolve()方法里不僅創建了ImageStream還設置了ImageStream的setComplete方法去設置ImageStreamCompleter,在這裡去判斷是否有緩存,沒有緩存就調用load方法去創建ImageStreamCompleter並且添加監聽器為了執行載入完圖片之後的緩存工作。ImageStreamCompleter是為了解析已經載入完成的Image的。

NetWorkImage實現了ImageProvider的load方法,是真正下載圖片的地方,創建了MultiFrameImageStreamCompleter對象,並且調用_loadAsync去下載圖片。當圖片下載完成後就調用UI的回調方法,通知UI刷新。

>>>>

最後

至此,對Image.network的源碼分析到這裡也結束了,你也可以返回去看下Image的結構圖了。怎麼樣,分析完之後是不是對Flutter載入網路圖片的流程已經很了解了,也找到了Flutter緩存的突破口,Flutter自身已經提供了內存緩存(雖然不太完美),接下來你就可以添加你的硬碟緩存或者定製你的圖片框架了。

京東技術∣關注技術的公眾號

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 京東技術 的精彩文章:

都已經禁止調用私有API了,你還不重視Android P怎麼行
分散式資源管理與作業調度

TAG:京東技術 |