雲計算

AliFlutter圖片解決方案與優化

Flutter與Native混合開發將是接下來很長時間的主流開發方式。一套穩定、高效、與官方體系無縫融合的外接圖片緩存方案是必不可少的。在AliFlutter系列第三場直播中,由阿里巴巴新零售淘系技術部無線開發專家王乾元為大家介紹AliFlutter提供的適合混合應用的外接圖片庫方案。首先對Flutter官方原生方案進行了分析,並提出了AliFlutter方案的切入點以及具體優化手段。

演講嘉賓簡介:王乾元,花名神漠,13年加入阿里,先後負責過天貓、支付寶、手機淘寶App的iOS架構工作。目前在AliFlutter團隊負責基礎組件、iOS架構,以及引擎、工具鏈等方面的研究。
以下內容根據演講視頻以及PPT整理而成。
觀看回放http://mudu.tv/watch/5624777

本次分享主要圍繞以下三個方面:

            一、Flutter如何顯示、加載圖片  
            二、AliFlutter圖片解決方案優化  
            三、如何選擇最適圖片解決方案  

一、Flutter如何顯示、加載圖片

介紹Flutter如何加載、顯示圖片,以及在線程、緩存設計層面的特點。

線程

閒魚分享的文章《深入理解Flutter引擎線程模式》中,詳細講解了Flutter引擎線程模型的原理以及作用。

Platform Thread:IOS與安卓平臺層應用的主線程。進行Flutter Engine接口的調用,用戶手勢和輸入等也通過Platform Thread輸入給Flutter Engine。

UI Thread:Flutter的主線程,也稱為Dart線程。同時可運行C++代碼。

IO Thread:進行圖片上傳。圖片在IO Thread進行異步上傳生成GPU紋理.

GPU Thread:負責Flutter最終的GPU調用。
Worker Thread:Flutter中的fml會創建若干個併發工作線程。可進行圖片解碼等工作。

屏幕快照 2020-06-22 下午2.39.18.png

如上圖所示,圖片在Worker Thread完成解碼,在IO Thread進行異步上傳,在引擎啟動時創建的ShellIOManager會創建OpenGL Context。同時GPU Thread創建GPU Context。IO Context與GPU Context將存放在Share Group中共享紋理。Flutter中紋理對象是C++的對象,在Flutter底層不會對紋理對象進行任何緩存,而是通過Dart層的ui.Image對象通過引用計數進行管理。

圖片加載、顯示流程

下圖為Flutter從圖片加載到顯示的相關類關係圖,包括類所在文件。

圖片加載用到Flutter的Image Widget,一般是使用其“.network”接口加載網絡圖片。Image Widget進行顯示繪製時需要ImageState。ImageState有兩個功能,一是驅動Provider下載圖片,二是調用State管理底層Render object。Render object負責圖片的渲染上屏。

NetworkImage(Provider)在自身resolve方法中異步調用http下載圖片。resolve方法調用Provider獲取自己的ImageStream。ImageStream會添加到StreamCompleter作為Listener。StreamCompleter可以添加多個ImageStream作為Listeners。圖片下載完成後通過Dart層和C++層的接口函數instantiateImageCodec創建底層C++解碼器的C++對象。解碼器對象獲取圖片流後在底層進行異步解碼,並生成紋理。ImageState接收到事件後獲取紋理對象繪製圖片。上層獲取圖片紋理後會調用ImageState的SetState方法將紋理對象傳給底層Render object,排版完成後圖片就會繪製到屏幕。

底層紋理對象會被上層Dart對象引用,具體為以下幾個對象。StreamCompleter負責驅動底層解碼器獲取紋理對象。因此StreamCompleter會持有底層GPU紋理,並通過Listeners通知所有ImageState。因此ImageState也會持有紋理對象。ImageState將圖片傳給底層Render object,因此Render object也會持有紋理對象。當上層Image Widget被銷燬,Image Cache清空時,觸發底層紋理的釋放。

屏幕快照 2020-06-22 下午2.40.04.png

Flutter加載顯示圖片的流程包括了圖片的組件、下載、解碼、上傳、繪製等工作,看似複雜,但是其邏輯較為簡單。

二、AliFlutter圖片解決方案優化

問題

首先,利用Flutter製作淘寶商品詳情頁面,圖片多,內存、CPU等佔用非常高,性能要求高。Flutter圖片管理能力較弱,缺乏本地緩存能力,圖片的重複下載極易造成內存飆高,易發生OOM(OutOfMemory)情況。因此Flutter原生方案無法滿足需求,需要構建適合的AliFlutter方案。

第二,電商APP需要與Native圖片庫對接,共享緩存、CDN能力以及監控設施。

第三,在使用簡單的基礎上,AliFlutter需要基於Flutter的強大擴展能力,支持小程序、Canvas等多種場景。

第四,希望AliFlutter與官方Flutter體系儘可能兼容與融合。

屏幕快照 2020-06-22 下午2.40.40.png

AliFlutter圖片解決方案總體架構

下圖紅色標籤為AliFlutter方案的重點。在Dart層實現了新的Provider,在C++層實現了新的解碼器對象,並基於Flutter規範提供了不同平臺的ObjC、安卓的Java接口。

AliFlutter圖片解決方案追求以下三個特點。

一致性:與官方體系無縫融合。僅在官方基礎上添加代碼。
高性能:優化CPU、內存佔用,增強List回收能力。
易用性:適配簡單、使用簡單、易擴展。

屏幕快照 2020-06-22 下午2.41.30.png

AliFlutter:如下圖所示,高亮部分為AliFlutter改進部分。
Image Widget添加了新類型的Provider,ExternalAdapterImage。新Provider接收的參數是URL、圖片尺寸信息等。可將參數通過Adapter傳給Native圖片庫,進行圖片下載或從緩存中加載。Completer會創建新的解碼器對象,通過Adapter對接Native圖片庫,讓Native圖片庫提供圖片的原始Buffer,並進行解碼。即不依賴Flutter的圖片解碼能力,而是依賴平臺層例如IOS和安卓原生的圖片解碼能力,可支持更多圖片格式。將平臺層解碼後的bitmap返回給解碼器對象,通過位圖數據進行圖片紋理的上傳。AliFlutter解碼器底層的C++對象支持這兩種工作模式。

屏幕快照 2020-06-22 下午2.41.58.png

一次完整圖片加載過程時序圖:首先從Image Widget拿到圖片請求URL,調用到底層解碼器對象的getNextFrame方法會將請求異步上傳給對接的Native圖片庫。由Native圖片庫做請求,獲取平臺層的圖片對象或Buffer,將圖片對象返回給解碼器對象。解碼器對象在Worker Thread中進行圖片解碼。圖片解碼完成後在IO Thread進行圖片的GPU紋理上傳。上傳完成後在UI Thread將圖片返回給Dart。上述流程完成一次圖片加載,線程模型與Flutter原生保持一致。
圖片取消:AliFlutter方案相比Flutter原生方案新增了Cancel能力。Widget通過State將自己添加到Completer的Listeners中。因此Widget銷燬時會將自己從Listeners中移除。當Completer的Listeners全部清空時,表示這次圖片請求已經不再需要了,調用底層解碼器對象的cancel方法。如果圖片還未從Native圖片庫返回,可以取消下載;如果已經返回,還有解碼或上傳GPU過程,都可以及時取消操作。Cancel能力可以避免許多無用的CPU和內存的消耗,尤其是電商App中常見的快速滑動商品列表的場景。

屏幕快照 2020-06-22 下午2.42.21.png

性能優化

AliFlutter進行了以下層面的優化,除圖片取消外,還包括延遲加載、解碼併發控制、GIF逐幀上傳紋理、增強List回收能力等。

屏幕快照 2020-06-22 下午2.42.48.png

適配與使用:介紹AliFlutter圖片方案最終對接到平臺層Native圖片庫的接口。

Flutter的封裝是在IOS平臺公開了Objective-C接口,在安卓平臺提供了Java接口,因此AliFlutter遵循Flutter規範提供了OC接口與Java接口。

IOS平臺OC接口只需要實現一個回調。OC回調在對接圖片庫時接收的是URL以及一些參數,獲取圖片後向底層返回UIImage即可。使用時可以直接調用Dart的Image.externalAdapter方法加載一張圖片。在此可以指定placeholderProvider,可以是AssetImage或其他網絡圖片,以此可在主圖加載失敗時加載一張副圖。

屏幕快照 2020-06-22 下午2.43.22.png

增強List回收能力

優化前後對比:下圖左側所示為使用Flutter製作的淘寶商品詳情頁面,其中有多個Cell。其中一個Cell為寶貝詳情。寶貝詳情Cell最初的實現方式是解析一段HTML。商家有時會上傳多張高清大圖,若此時將連續的圖文詳情放在一個Cell中,用戶瀏覽詳情頁時會同時加載多張大圖。另外Flutter默認對所有Cell添加RepaintBoundary屬性,該屬性默認將Cell中所有內容繪製到一個紋理中,下次瀏覽時若Cell中內容不變,直接使用紋理繪製圖片會比較快速。因此易導致內存飆高問題。

如下圖所示,優化前內存容易暴增到600+MB甚至1G,幾乎100%會出現OOM問題。在業務代碼不進行修改的情況下,優化後的內存增長變得較為平緩。

屏幕快照 2020-06-22 下午2.43.46.png

Flutter List特點:Flutter List回收以Cell為單位。下圖所示紅色框部分為屏幕大小。默認情況下Flutter默認對所有Cell添加RepaintBoundary。當列表滾動時,若Cell 1繪製過,下次繪製時直接將及紋理上屏即可,無需繪製內部圖文元素。而Cell 2會佔用大量內存,首先其圖文多,同時RepaintBoundary形成的紋理也會佔用大量內存。

因此增強List回收能力首先需要解除對部分Cell的RepaintBoundary設置。

屏幕快照 2020-06-22 下午2.44.28.png

優化流程:假設一個Image Widget在一個Cell中,正常情況下當Cell出現,Image Widget也會被創建並且請求圖片。List回收能力的優化中試圖解除此約定,根據圖片是否在屏來判斷是否需要圖片紋理。若不需要,則釋放,若需要,進行請求。

Image Widget的寬、高已知情況下,其排版信息是有效的。SetState完成後觸發底層Render Object排版與繪製。在繪製圖片過程中添加一段邏輯判斷圖片是否在屏。若圖片不在屏,不作任何處理。若圖片在屏幕中,進行圖片請求獲取真實圖片後重復調用SetState,重新進行圖片排版和繪製,並判斷是否在屏。若圖片隨著列表滾動不在屏幕中,則回調通知上層解除紋理引用。
若Image Widget的寬、高未知,Flutter只能在獲取圖片後根據其真實尺寸進行排版。原本底層解碼器對象持有getNextFrame接口,該接口導致GPU紋理的生成。在優化後可以不依賴圖片紋理上傳完成再進行排版。在流程中添加了Request Size接口,Image Widget的寬、高未知時調用該接口可以預先通知底層C++解碼器獲取圖片尺寸。得到圖片尺寸後再從SetState開始流程,避免了無效的紋理上傳。

屏幕快照 2020-06-22 下午2.45.07.png

關鍵代碼:判斷圖片是否在屏是通過Dart層的Image Render Object。其paint方法中進行圖片是否在屏的判斷,根據其是否在屏向上層ImageState發送回調通知。
實現指定Cell不添加RepaintBoundary是通過建立虛類NoRepaintBoundaryHint。若List檢測到上層某個Cell繼承自NoRepaintBoundaryHint,則不給該Cell添加RepaintBoundary。因此可以在每次屏幕滾動時重新進行繪製,瞭解圖片的在屏、離屏信息。

屏幕快照 2020-06-22 下午2.45.30.png

圖片解碼時通過Image Codec接口實現只獲取圖片尺寸,不上傳紋理。圖片尺寸可以直接從圖片的頭部信息獲取,並不需要分配內存。

圖片排版時可以僅根據圖片的尺寸信息進行排版,無需獲取真實圖片。

總結起來,大Cell優化就是避免圖片紋理上傳,圖片真正在屏時,再獲取其紋理,當圖片離屏時,立刻清除其紋理。

屏幕快照 2020-06-22 下午2.46.21.png

優化效果:經過以上優化,List回收能力的增強取得了較好效果。當商品詳情頁面有幾十張大圖在同一列表的同一個Cell中出現,可以做到僅加載在屏圖片,若圖片離屏則釋放。

屏幕快照 2020-06-22 下午2.46.47.png

案例-優化前:十張圖片放在一個Cell中,內存突增突降。

屏幕快照 2020-06-22 下午2.47.17.png

案例-優化後:根據圖片是否在屏進行加載或釋放,內存增降均較為平緩。

屏幕快照 2020-06-22 下午2.47.36.png

後續改進

AliFlutter圖片解決方案還有以下方面可以改進。
功能改進:第一,圖片在屏、離屏判斷優化。第二,支持圖片庫返回圖片原始文件,精簡鏈路。第三,支持業務定製化緩存策略。

包優化大小:允許定製化裁剪Flutter中的若干圖片解碼庫,同時保證Flutter所有功能正常。

與官方探討:如何將AliFlutter優化融合到Flutter主幹。

三、如何選擇最適圖片解決方案

Flutter圖片解決方案誕生以來,開發者也進行了許多嘗試,創建圖片庫方案。難以定論哪些圖片方案更加優秀。
如下圖所示,開發者可以考慮圖片解決方案是否為純Flutter應用,網絡圖片場景多不多,圖片有無必要緩存等方面。根據自己的應用場景選擇最適合自己的圖片解決方案。

屏幕快照 2020-06-22 下午2.48.20.png

關注「淘系技術」微信公眾號,一個有溫度有內容的技術社區~
image.png

Leave a Reply

Your email address will not be published. Required fields are marked *