在作圖片、視頻相關功能的時候,相冊是一個繞不開的話題,由於你們基本都有從相冊獲取圖片或者視頻的需求。最直接的方式是調用系統相冊接口,基本功能是知足的,一些高級功能就不行了,例如自定義UI、多選圖片等。java
咱們調研了官方的image_picker,它也是調用系統的相冊接口來處理的,可定製程度不高,不能知足咱們的要求。因此咱們選擇本身來開發Flutter相冊組件。緩存
咱們的組件須要有以下的功能:架構
API使用簡單,功能豐富靈活,具備較高的訂製性。業務方能夠選擇徹底接入組件,也能夠選擇在組件上面進行UI定製。app
Flutter作UI展示層,具體的數據由各Native平臺提供。這種模式,自然從工程上把UI代碼和數據代碼進行了隔離。咱們在開發一個native組件的時候經常會使用MVC架構。Flutter組件的開發的思路也基本相似。總體架構以下:函數
能夠看出,在Flutter側是一個典型的MVC架構,這裏Widget就是View,View和Model綁定,在Model改變的時候View會從新build反映出Model的變化。View的事件會觸發Controller去Native獲取數據而後更新Model。Native和Flutter經過Method Channel進行通訊,兩層之間沒有強依賴關係,只須要按約定的協議進行通訊便可。性能
Native側的組成部分,UIAdapter主要是負責機型的適配、劉海屏、全面屏之類的識別。Permission負責媒體讀寫權限的申請處理。Cache主要負責緩存GPU紋理,在大圖預覽的時候提升響應速度。Decoder負責解析Bitmap,OpenGL負責Bitmap轉紋理。測試
須要說明的是:咱們的這一套實現依賴於flutter外接紋理。在整個相冊組件看到的大多數圖片都是一個GPU紋理,這樣給java堆內存的佔用相對於之前的相冊實現有大幅的下降。在低端機上面若是使用原生的系統相冊,因爲內存的緣由,app有被系統殺掉的風險。現象就是,從系統相冊返回,app從新啓動了。使用Flutter相冊組件,在低端機上面體驗會有所改觀。ui
1. 分頁加載spa
相冊列表須要加載大量圖片,Flutter的GridView組件有好幾個構造函數,比較容易犯的錯誤是使用了第一個函數,這須要在一開始就提供大量的widget。應該選擇第二個構造函數,GridView在滑動的時候會回調IndexedWidgetBuilder來獲取widget,至關於一種懶加載。線程
GridView.builder({ ... List<Widget> children = const <Widget>[], ... })
GridView.builder({ ... @required IndexedWidgetBuilder itemBuilder, int itemCount, ... })
滑動過程當中,圖片滑事後,也就是不可見的時候要進行資源的回收,咱們這裏這裏對應的就是紋理的刪除。不斷的滑動GridView,內存在上升後會處於穩定,不會一直增加。若是快速的來回滑動紋理會反覆的建立和刪除,這樣會有內存的抖動,體驗不是很好。
因而,咱們維護了一個圖片的狀態機,狀態有None,Loading,Loaded,Wait_Dispose,Disposed。開始加載的時候,狀態從None進入Loading,這個時候用戶看到的是空白或者是佔位圖,當數據回調回來會把狀態設置爲Loaded的這時候會從新build widget樹來顯示圖片icon,當用戶滑走的時候狀態進入 Wait_Dispose,這時候並不會立刻Dispose,若是用戶又滑回來則會從Wait_Dispose進入Loaded狀態,不會繼續Dispose。若是用戶沒有往回滑則會從Wait_Dispose進入Disposed狀態。當進入Disposed狀態後,再須要顯示該圖片的時候就須要從新走加載流程了。
2. 相冊大圖展現:
當點擊GridView的某張圖片的時候會進行這張圖片的大圖展現,方便用戶查看的更清楚。咱們知道相機拍攝的圖片分辨率都是很高的,若是徹底加載,內存會有很大的開銷,因此咱們在Decode Bitmap的時候進行了縮放,最高只到1080p。大圖展現能夠歸納爲三個步驟。
在步驟1中,Android原生的Bitmap Decode經驗一樣適用,先Decode出Bitmap的寬高,而後根據要展現的大小計算出縮放倍數, 而後Decode出須要的Bitmap。
Android相冊的圖片大可能是有旋轉角度的,若是不處理直接顯示,會出現照片旋轉90度的問題,因此須要對Bitmap進行旋轉,採用Matrix旋轉一張1080p的圖片在個人測試機器上面大概須要200ms,若是使用OpenGL的紋理座標進行旋轉,大於只須要10ms左右,因此採用OpenGl進行紋理的旋轉是一個較好的選擇。
在進行大圖預覽的時候會進入一個水平滑動的PageView,Flutter的PageView通常來講是不會去主動加載相鄰的page的。舉個例子,在顯示index是5的page的時候index爲4,6的page也不會提早建立的。這裏有一個取巧的辦法,對於PageController的viewportFraction參數咱們能夠設置成爲0.9999。對於前面這個例子,就是在顯示index是5的page的時候,index爲4,6的page也須要顯示0.0001。這樣index爲4,6的page顯示不到1個像素,基本上看不出來:
PageController(viewportFraction=0.9999)
還有另一種辦法,就是在Native側作預加載。例如:在加載第5張圖片的時候,相鄰的4,6的圖片紋理提早進行加載,當滑動到4,6的時候直接使用緩存的紋理。
紋理緩存後,一個直接的問題:何時釋放紋理?等到預覽頁面退出的時候釋放全部的紋理顯示不是很合適,若是用戶一直瀏覽內存則會無限增加。因此,咱們維護了一個5個紋理的LRU緩存,在滑動過程當中,最老的紋理會被釋放掉。在頁面退出的時候整個LRU的緩存會進行銷燬。
3. 關於內存
相冊圖片使用GPU紋理,會大幅減小Java堆內存的佔用,對整個app的性能有必定的提高。須要注意的是,GPU的內存是有限的須要在使用完畢後及時刪除,否則會有內存的泄漏的風險。另外,在Android平臺刪除紋理的時候須要保證在GPU線程進行,否則刪除是沒有效果的。
在華爲P8,Android5.0上面進行了對比測試,Flutter相冊和原native相冊總內存佔用基本一致,在GridView列表頁面,新增最大內存13M左右。它們的區別在於原native相冊使用的是Java堆內存,Flutter相冊使用的是Native內存。
相冊組件API簡單、易用,高度可定製。Flutter側井井有條,有UI訂製需求的能夠重寫Widget來達到目的。另外這是一個不依賴於系統相冊的相冊組件,自身是完備的,可以和現有的app保持UI、交互的一致性。同時爲後面支持更多和相冊相關的玩法打好基礎。
因爲咱們使用的是GPU紋理,能夠考慮支持顯示高清4K圖片,並且客戶端內存不會有太大的壓力。可是4k圖片的Bitmap轉紋理需消耗更多的時間,UI交互上面須要作些loading狀態的支持。
組件功能豐富,穩定後,進行開源,回饋給社區。
本文爲雲棲社區原創內容,未經容許不得轉載。