天天看點

AliFlutter圖檔解決方案與優化一、Flutter如何顯示、加載圖檔二、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會建立若幹個并發工作線程。可進行圖檔解碼等工作。

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

如上圖所示,圖檔在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清空時,觸發底層紋理的釋放。

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

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

二、AliFlutter圖檔解決方案優化

問題

首先,利用Flutter制作淘寶商品詳情頁面,圖檔多,記憶體、CPU等占用非常高,性能要求高。Flutter圖檔管理能力較弱,缺乏本地緩存能力,圖檔的重複下載下傳極易造成記憶體飙高,易發生OOM(OutOfMemory)情況。是以Flutter原生方案無法滿足需求,需要建構适合的AliFlutter方案。

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

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

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

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

AliFlutter圖檔解決方案總體架構

下圖紅色标簽為AliFlutter方案的重點。在Dart層實作了新的Provider,在C++層實作了新的解碼器對象,并基于Flutter規範提供了不同平台的ObjC、安卓的Java接口。

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

一緻性:與官方體系無縫融合。僅在官方基礎上添加代碼。

高性能:優化CPU、記憶體占用,增強List回收能力。

易用性:适配簡單、使用簡單、易擴充。

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

AliFlutter:如下圖所示,高亮部分為AliFlutter改進部分。

Image Widget添加了新類型的Provider,ExternalAdapterImage。新Provider接收的參數是URL、圖檔尺寸資訊等。可将參數通過Adapter傳給Native圖檔庫,進行圖檔下載下傳或從緩存中加載。Completer會建立新的解碼器對象,通過Adapter對接Native圖檔庫,讓Native圖檔庫提供圖檔的原始Buffer,并進行解碼。即不依賴Flutter的圖檔解碼能力,而是依賴平台層例如IOS和安卓原生的圖檔解碼能力,可支援更多圖檔格式。将平台層解碼後的bitmap傳回給解碼器對象,通過位圖資料進行圖檔紋理的上傳。AliFlutter解碼器底層的C++對象支援這兩種工作模式。

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

一次完整圖檔加載過程時序圖:首先從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中常見的快速滑動商品清單的場景。

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

性能優化

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

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

适配與使用:介紹AliFlutter圖檔方案最終對接到平台層Native圖檔庫的接口。

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

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

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

增強List回收能力

優化前後對比:下圖左側所示為使用Flutter制作的淘寶商品詳情頁面,其中有多個Cell。其中一個Cell為寶貝詳情。寶貝詳情Cell最初的實作方式是解析一段HTML。商家有時會上傳多張高清大圖,若此時将連續的圖文詳情放在一個Cell中,使用者浏覽詳情頁時會同時加載多張大圖。另外Flutter預設對所有Cell添加RepaintBoundary屬性,該屬性預設将Cell中所有内容繪制到一個紋理中,下次浏覽時若Cell中内容不變,直接使用紋理繪制圖檔會比較快速。是以易導緻記憶體飙高問題。

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

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

Flutter List特點:Flutter List回收以Cell為機關。下圖所示紅色框部分為螢幕大小。預設情況下Flutter預設對所有Cell添加RepaintBoundary。當清單滾動時,若Cell 1繪制過,下次繪制時直接将及紋理上屏即可,無需繪制内部圖文元素。而Cell 2會占用大量記憶體,首先其圖文多,同時RepaintBoundary形成的紋理也會占用大量記憶體。

是以增強List回收能力首先需要解除對部分Cell的RepaintBoundary設定。

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

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

Image Widget的寬、高已知情況下,其排版資訊是有效的。SetState完成後觸發底層Render Object排版與繪制。在繪制圖檔過程中添加一段邏輯判斷圖檔是否在屏。若圖檔不在屏,不作任何處理。若圖檔在螢幕中,進行圖檔請求擷取真實圖檔後重複調用SetState,重新進行圖檔排版和繪制,并判斷是否在屏。若圖檔随着清單滾動不在螢幕中,則回調通知上層解除紋理引用。

若Image Widget的寬、高未知,Flutter隻能在擷取圖檔後根據其真實尺寸進行排版。原本底層解碼器對象持有getNextFrame接口,該接口導緻GPU紋理的生成。在優化後可以不依賴圖檔紋理上傳完成再進行排版。在流程中添加了Request Size接口,Image Widget的寬、高未知時調用該接口可以預先通知底層C++解碼器擷取圖檔尺寸。得到圖檔尺寸後再從SetState開始流程,避免了無效的紋理上傳。

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

關鍵代碼:判斷圖檔是否在屏是通過Dart層的Image Render Object。其paint方法中進行圖檔是否在屏的判斷,根據其是否在屏向上層ImageState發送回調通知。

實作指定Cell不添加RepaintBoundary是通過建立虛類NoRepaintBoundaryHint。若List檢測到上層某個Cell繼承自NoRepaintBoundaryHint,則不給該Cell添加RepaintBoundary。是以可以在每次螢幕滾動時重新進行繪制,了解圖檔的在屏、離屏資訊。

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

圖檔解碼時通過Image Codec接口實作隻擷取圖檔尺寸,不上傳紋理。圖檔尺寸可以直接從圖檔的頭部資訊擷取,并不需要配置設定記憶體。

圖檔排版時可以僅根據圖檔的尺寸資訊進行排版,無需擷取真實圖檔。

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

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

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

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

案例-優化前:十張圖檔放在一個Cell中,記憶體突增突降。

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

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

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

後續改進

AliFlutter圖檔解決方案還有以下方面可以改進。

功能改進:第一,圖檔在屏、離屏判斷優化。第二,支援圖檔庫傳回圖檔原始檔案,精簡鍊路。第三,支援業務定制化緩存政策。

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

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

三、如何選擇最适圖檔解決方案

Flutter圖檔解決方案誕生以來,開發者也進行了許多嘗試,建立圖檔庫方案。難以定論哪些圖檔方案更加優秀。

如下圖所示,開發者可以考慮圖檔解決方案是否為純Flutter應用,網絡圖檔場景多不多,圖檔有無必要緩存等方面。根據自己的應用場景選擇最适合自己的圖檔解決方案。

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

關注「淘系技術」微信公衆号,一個有溫度有内容的技術社群~

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