1.動(dòng)畫(huà)原理:在一段時(shí)間內(nèi)快速的多次改變UI外觀,由于人眼會(huì)產(chǎn)生視覺(jué)暫留所以最終看到的就是一個(gè)連續(xù)的動(dòng)畫(huà)。

成都創(chuàng)新互聯(lián)公司2013年成立,先為個(gè)舊等服務(wù)建站,個(gè)舊等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢(xún)服務(wù)。為個(gè)舊企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
UI的一次改變稱(chēng)為一個(gè)動(dòng)畫(huà)幀,對(duì)應(yīng)一次屏幕刷新。
FPS:幀率,每秒的動(dòng)畫(huà)幀數(shù)。
flutter動(dòng)畫(huà)分為兩類(lèi):
常見(jiàn)動(dòng)畫(huà)模式:
是一個(gè)抽象類(lèi),主要的功能是保存動(dòng)畫(huà)的值和狀態(tài)。常用的一個(gè)Animation類(lèi)是Animation double ,是一個(gè)在一段時(shí)間內(nèi)依次生成一個(gè)區(qū)間之間的值的類(lèi),可以是線(xiàn)性或者曲線(xiàn)或者其他。
可以生成除double之外的其他類(lèi)型值,如:Animation Color 或 Animation Size 。
是一個(gè)動(dòng)畫(huà)控制器,控制動(dòng)畫(huà)的播放狀態(tài),在屏幕刷新的每一幀,就會(huì)生成一個(gè)新的值。
包含動(dòng)畫(huà)的啟動(dòng)forward()、停止stop() 、反向播放 reverse()等方法,在給定的時(shí)間段內(nèi)線(xiàn)性的生成從0.0到1.0(默認(rèn)區(qū)間)的數(shù)字。
curve:描述動(dòng)畫(huà)的曲線(xiàn)過(guò)程。
curvedAnimation:指定動(dòng)畫(huà)的曲線(xiàn)。
常用Curve:
繼承自Animatable T ,表示的就是一個(gè) Animation 對(duì)象的取值范圍,只需要設(shè)置開(kāi)始和結(jié)束的邊界值(值也支持泛型)。 它唯一的工作就是定義輸入范圍到輸出范圍的映射。
例如,Tween可能會(huì)生成從紅到藍(lán)之間的色值,或者從0到255。
Tween.animate:返回一個(gè)Animation。
映射過(guò)程:
1). Tween.animation通過(guò)傳入 aniamtionController 獲得一個(gè)_AnimatedEvaluation 類(lèi)型的 animation 對(duì)象(基類(lèi)為 Animation), 并且將 aniamtionController 和 Tween 對(duì)象傳入了 _AnimatedEvaluation 對(duì)象。
2). animation.value方法即是調(diào)用 _evaluatable.evaluate(parent)方法, 而 _evaluatable 和 parent 分別為 Tween 對(duì)象和 AnimationController 對(duì)象。
3). 這里的 animation 其實(shí)就是前面的 AnimationController 對(duì)象, transform 方法里面的 animation.value則就是 AnimationController 線(xiàn)性生成的 0.0~1.0 直接的值。 在 lerp 方法里面我們可以看到這個(gè) 0.0~1.0 的值被映射到了 begin 和 end 范圍內(nèi)了。
接收一個(gè)TickerProvider類(lèi)型的對(duì)象,它的主要職責(zé)是創(chuàng)建Ticker。
防止屏幕外動(dòng)畫(huà)消耗資源。
[圖片上傳失敗...(image-115b94-1636441483468)]
過(guò)程:
回調(diào):
不使用addListener()和setState()來(lái)給widget添加動(dòng)畫(huà)。
使用AnimatedWidget,將widget分離出來(lái),創(chuàng)建一個(gè)可重用動(dòng)畫(huà)的widget,AnimatedWidget中會(huì)自動(dòng)調(diào)用addListener()和setState()
AnimatedModalBarrier、DecoratedBoxTransition、FadeTransition、PositionedTransition、RelativePositionedTransition、RotationTransition、ScaleTransition、SizeTransition、SlideTransition
如何渲染過(guò)渡,把渲染過(guò)程也抽象出來(lái):
AnimatedBuilder的示例包括: BottomSheet、 PopupMenu、ProgressIndicator、RefreshIndicator、Scaffold、SnackBar、TabBar。
MaterialPageRoute:平臺(tái)風(fēng)格一致的路由切換動(dòng)畫(huà)
CupertinoPageRoute:左右切換風(fēng)格
自定義:PageRouteBuilder
1.要?jiǎng)?chuàng)建交織動(dòng)畫(huà),需要使用多個(gè)動(dòng)畫(huà)對(duì)象(Animation)。
2.一個(gè)AnimationController控制所有的動(dòng)畫(huà)對(duì)象。
3.給每一個(gè)動(dòng)畫(huà)對(duì)象指定時(shí)間間隔(Interval)
可以同時(shí)對(duì)其新、舊子元素添加顯示、隱藏動(dòng)畫(huà).
當(dāng)AnimatedSwitcher的child發(fā)生變化時(shí)(類(lèi)型或Key不同),舊child會(huì)執(zhí)行隱藏動(dòng)畫(huà),新child會(huì)執(zhí)行執(zhí)行顯示動(dòng)畫(huà)。
希望大家支持一下,感謝
RenderObjectWidget 是 Widget 例如 SizeBox , Column 等
RenderObjectElement 是這類(lèi) Widget 生成的 Element 類(lèi)型,
例如 SizeBox 對(duì)應(yīng) SingleChildRenderObjectElement (單子節(jié)點(diǎn)的 Element )
RenderObject 才是真正負(fù)責(zé)繪制的對(duì)象,其中包含了 paint , layout 等方法~
Text 組件為例:
class Text extends StatelessWidget
Text build返回的是
class RichText extends MultiChildRenderObjectWidget
MultiChildRenderObjectWidget - MultiChildRenderObjectElement
RenderObjectElement 會(huì)重寫(xiě) mount
遍歷 _parent (就是上個(gè)節(jié)點(diǎn)的 element ,這個(gè) _parent 在每次的 mount 方法設(shè)置),一直遍歷知道找到最近的一個(gè) RenderObjectElement
在回顧下 inflateWidget
SingleChildRenderObjectElement
MultiChildRenderObjectElement
inflateWidget 會(huì)觸發(fā) RenderObjectWidget 的 createElement 創(chuàng)建 RenderObjectElement ,
再調(diào)用 RenderObjectElement 的 mount 方法
mount 方法調(diào)用 RenderObjectWidget 的 createRenderObject 方法創(chuàng)建 RenderObject
然后找到最近的一個(gè)父 RenderObjectElement 調(diào)用 insertRenderObjectChild 插入 RenderObject
(注意:這里不是 RenderObjectElement 的 child ,而是 RenderObjectElement 關(guān)聯(lián)的 RenderObject 的 child , element 樹(shù)里面都是 element , RenderObject 樹(shù)里面都是 RenderObject )
MultiChildRenderObjectElement 多子節(jié)點(diǎn)掛載邏輯
多節(jié)點(diǎn)添加子節(jié)點(diǎn)邏輯
每個(gè)子節(jié)點(diǎn)的 parentData 的 after 用來(lái)指向前一個(gè)子節(jié)點(diǎn) previous 指向下一個(gè)子節(jié)點(diǎn)
生命周期是別人封裝好的一套方法接口,然后提供回調(diào)方法給我們調(diào)用,生命周期本質(zhì)是回調(diào)方法;
1.監(jiān)聽(tīng)widget的事件
2.初始化數(shù)據(jù)
創(chuàng)建數(shù)據(jù)
發(fā)送網(wǎng)絡(luò)請(qǐng)求
3.內(nèi)存管理
銷(xiāo)毀數(shù)據(jù),銷(xiāo)毀監(jiān)聽(tīng)者
銷(xiāo)毀timer等
//1.StatelessWidget構(gòu)造函數(shù)調(diào)用...1
//2.StatelessWidget的buil方法調(diào)用...2
//1.StatefulWidget的構(gòu)造函數(shù)...1
//2.State的構(gòu)造函數(shù)...2
//3.State的initState的方法...3
//4.State的build的方法...4
//5.State的dispose的方法...5
setState(() {})
可被
StatefulElement _element = context;
_element.markNeedsBuild();
替代
參考:
APP 啟動(dòng)頁(yè)在國(guó)內(nèi)是最常見(jiàn)也是必備的場(chǎng)景,其中啟動(dòng)頁(yè)在 iOS 上算是強(qiáng)制性的要求,其實(shí)配置啟動(dòng)頁(yè)挺簡(jiǎn)單,因?yàn)樵?Flutter 里現(xiàn)在只需要:
一般只要配置無(wú)誤并且圖片尺寸匹配,基本上就不會(huì)有什么問(wèn)題, 那既然這樣,還有什么需要適配的呢?
事實(shí)上大部分時(shí)候 iOS 是不會(huì)有什么問(wèn)題, 因?yàn)? LaunchScreen.storyboard 的流程本就是 iOS 官方用來(lái)做應(yīng)用啟動(dòng)的過(guò)渡;而對(duì)于 Andorid 而言,直到 12 之前 windowBackground 這種其實(shí)只能算“民間”野路子 ,所以對(duì)于 Andorid 來(lái)說(shuō),這其中就涉及到一個(gè)點(diǎn):
所以下面主要介紹 Flutter 在 Android 上為了這個(gè)啟動(dòng)圖做了哪些騷操作~
在已經(jīng)忘記版本的“遠(yuǎn)古時(shí)期” , FlutterActivity 還在 io.flutter.app.FlutterActivity 路徑下的時(shí)候,那時(shí)啟動(dòng)頁(yè)的邏輯相對(duì)簡(jiǎn)單,主要是通過(guò) App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 來(lái)進(jìn)行判斷。
在 FlutterActivity 內(nèi)部 FlutterView 被創(chuàng)建的時(shí)候,會(huì)通過(guò)讀取 meta-data 來(lái)判斷是否需要使用 createLaunchView 邏輯 :
是不是很簡(jiǎn)單,那就會(huì)有人疑問(wèn)為什么要這樣做?我直接配置 Activity 的 android:windowBackground 不就完成了嗎?
這就是上面提到的時(shí)間差問(wèn)題, 因?yàn)閱?dòng)頁(yè)到 Flutter 渲染完第一幀畫(huà)面中間,會(huì)出現(xiàn)概率出現(xiàn)黑屏的情況,所以才需要這個(gè)行為來(lái)實(shí)現(xiàn)過(guò)渡 。
經(jīng)歷了“遠(yuǎn)古時(shí)代”之后, FlutterActivity 來(lái)到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本發(fā)布之前,F(xiàn)lutter 又針對(duì)這個(gè)啟動(dòng)過(guò)程做了不少調(diào)整和優(yōu)化,其中主要就是 SplashScreen 。
自從開(kāi)始進(jìn)入 embedding 階段后, FlutterActivity 主要用于實(shí)現(xiàn)了一個(gè)叫 Host 的 interface ,其中和我們有關(guān)系的就是 provideSplashScreen 。
默認(rèn)情況下它會(huì)從 AndroidManifest 文件里是否配置了 SplashScreenDrawable 來(lái)進(jìn)行判斷 。
默認(rèn)情況下當(dāng) AndroidManifest 文件里配置了 SplashScreenDrawable ,那么這個(gè) Drawable 就會(huì)在 FlutterActivity 創(chuàng)建 FlutterView 時(shí)被構(gòu)建成 DrawableSplashScreen 。
DrawableSplashScreen 其實(shí)就是一個(gè)實(shí)現(xiàn)了 io.flutter.embedding.android.SplashScreen 接口的類(lèi),它的作用就是:
之后 FlutterActivity 內(nèi)會(huì)創(chuàng)建出 FlutterSplashView ,它是個(gè) FrameLayout。
FlutterSplashView 將 FlutterView 和 ImageView 添加到一起, 然后通過(guò) transitionToFlutter 的方法來(lái)執(zhí)行動(dòng)畫(huà),最后動(dòng)畫(huà)結(jié)束時(shí)通過(guò) onTransitionComplete 移除 splashScreenView 。
所以整體邏輯就是:
當(dāng)然這里也是分狀態(tài):
當(dāng)然這個(gè)階段的 FlutterActivity 也可以通過(guò) override provideSplashScreen 方法來(lái)自定義 SplashScreen 。
看到?jīng)]有,做了這么多其實(shí)也就是為了彌補(bǔ)啟動(dòng)頁(yè)和 Flutter 渲染之間, 另外還有一個(gè)優(yōu)化,叫 NormalTheme 。
通過(guò)該配置 NormalTheme ,在 Activity 啟動(dòng)時(shí),就會(huì)首先執(zhí)行 switchLaunchThemeForNormalTheme(); 方法將主題從 LaunchTheme 切換到 NormalTheme 。
大概配置完就是如下樣子, 前面分析那么多其實(shí)就是為了告訴你,如果出現(xiàn)問(wèn)題了,你可以從哪個(gè)地方去找到對(duì)應(yīng)的點(diǎn) 。
講了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被棄用了,驚不喜驚喜,意不意外,開(kāi)不開(kāi)心 ?
通過(guò)源碼你會(huì)發(fā)現(xiàn),當(dāng)你設(shè)置了 splashScreen 的時(shí)候,會(huì)看到一個(gè) log 警告:
為什么會(huì)棄用?
其實(shí)這個(gè)提議是在 這個(gè) issue 上,然后通過(guò) 這個(gè) pr 完成調(diào)整。
大概意思就是: 原本的設(shè)計(jì)搞復(fù)雜了,用 OnPreDrawListener 更精準(zhǔn),而且不需要為了后面 Andorid12 的啟動(dòng)支持做其他兼容,只需要給 FlutterActivity 等類(lèi)增加接口開(kāi)關(guān)即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 來(lái)實(shí)現(xiàn)延遲直到加載出 Flutter 的第一幀。
為什么說(shuō)默認(rèn)情況? 因?yàn)檫@個(gè)行為在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才會(huì)被調(diào)用,而 RenderMode 又和 BackgroundMode 有關(guān)心 。
所以在 2.5 版本后, FlutterActivity 內(nèi)部創(chuàng)建完 FlutterView 后就會(huì)執(zhí)行一個(gè) delayFirstAndroidViewDraw 的操作。
這里主要注意一個(gè)參數(shù): isFlutterUiDisplayed 。
當(dāng) Flutter 被完成展示的時(shí)候, isFlutterUiDisplayed 就會(huì)被設(shè)置為 true。
所以當(dāng) Flutter 沒(méi)有執(zhí)行完成之前, FlutterView 的 onPreDraw 就會(huì)一直返回 false ,這也是 Flutter 2.5 開(kāi)始之后適配啟動(dòng)頁(yè)的新調(diào)整。
看了這么多,大概可以看到其實(shí)開(kāi)源項(xiàng)目的推進(jìn)并不是一帆風(fēng)順的,沒(méi)有什么是一開(kāi)始就是最優(yōu)解,而是經(jīng)過(guò)多方嘗試和交流,才有了現(xiàn)在的版本,事實(shí)上開(kāi)源項(xiàng)目里,類(lèi)似這樣的經(jīng)歷數(shù)不勝數(shù):
文/陳爐軍
整理/LiveVideoStack
大家好,我是阿里巴巴閑魚(yú)事業(yè)部的陳爐軍,本次分享的主題是Flutter浪潮下的音視頻研發(fā)探索,主要內(nèi)容是針對(duì)閑魚(yú)APP在當(dāng)下流行的跨平臺(tái)框架Flutter的大規(guī)模實(shí)踐,介紹其在音視頻領(lǐng)域碰到的一些困難以及解決方案。
分享內(nèi)容主要分為四個(gè)方面,首先會(huì)對(duì)Flutter有一個(gè)簡(jiǎn)單介紹以及選擇Flutter作為跨平臺(tái)框架的原因,其次會(huì)介紹Flutter中與音視頻關(guān)系非常大的外接紋理概念,以及對(duì)它做出的一些優(yōu)化。之后會(huì)對(duì)閑魚(yú)在音視頻實(shí)踐過(guò)程中碰到的一些Flutter問(wèn)題提出了一些解決方案——TPM音視頻框架。最后是閑魚(yú)Flutter多媒體開(kāi)源組件的介紹。
Flutter
Flutter是一個(gè)跨平臺(tái)框架,以往的做法是將音頻、視頻和網(wǎng)絡(luò)這些模塊都下沉到C++層或者ARM層,在其上封裝成一個(gè)音視頻的SDK,供UI層的PC、iOS和Android調(diào)用。
而Flutter做為一個(gè)UI層的跨平臺(tái)框架,顧名思義就是在UI層也實(shí)現(xiàn)了一個(gè)跨平臺(tái)開(kāi)發(fā)。可以預(yù)想的是未Flutter發(fā)展的好的話(huà),會(huì)逐漸變?yōu)橐粋€(gè)從底層到UI層的一個(gè)全鏈路的跨平臺(tái)開(kāi)發(fā),技術(shù)人員分別負(fù)責(zé)SDK和UI層的開(kāi)發(fā)。
在Flutter之前已經(jīng)有很多跨平臺(tái)UI解決方案,那為什么選擇Flutter呢?
我們主要考慮性能和跨平臺(tái)的能力。
以往的跨平臺(tái)方案比如Weex,ReactNative,Cordova等等因?yàn)榧軜?gòu)的原因無(wú)法滿(mǎn)足性能要求,尤其是在音視頻這種性能要求幾乎苛刻的場(chǎng)景。
而諸如Xamarin等,雖然性能可以和原生App一致,但是大部分邏輯還是需要分平臺(tái)實(shí)現(xiàn)。
我們可以看一下,為什么Flutter可以實(shí)現(xiàn)高性能:
原生的native組件渲染以IOS為例,蘋(píng)果的UIKit通過(guò)調(diào)用平臺(tái)自己的繪制框架QuaztCore來(lái)實(shí)現(xiàn)UI的繪制,圖形繪制也是調(diào)用底層的API,比如OpenGL、Metal等。
而Flutter也是和原生API邏輯一致,也是通過(guò)調(diào)用底層的繪制框架層SKIA實(shí)現(xiàn)UI層。這樣相當(dāng)于Flutter他自己實(shí)現(xiàn)了一套UI框架,提供了一種性能超越原生API的跨平臺(tái)可能性。
但是我們說(shuō)一個(gè)框架最終性能怎樣,其實(shí)取決于設(shè)計(jì)者和開(kāi)發(fā)者。至于現(xiàn)在到底是一個(gè)什么狀況:
在閑魚(yú)的實(shí)踐中,我們發(fā)現(xiàn)在正常的開(kāi)發(fā)沒(méi)有特意的去優(yōu)化UI代碼的情況下,在一些低端機(jī)上,F(xiàn)lutter界面的流暢性是比Native界面要好的。
雖然現(xiàn)在閑魚(yú)某些場(chǎng)景下會(huì)有卡頓閃退等情況,但是這是一個(gè)新事物發(fā)展過(guò)程中的必然問(wèn)題,我們相信未來(lái)性能肯定不會(huì)成為限制Flutter發(fā)展的瓶頸的。
在閑魚(yú)實(shí)踐Flutter的過(guò)程中,混合棧和音視頻是其中比較難解決的兩個(gè)問(wèn)題,混合棧是指一個(gè)APP在Flutter過(guò)程中不可能一口氣將所有業(yè)務(wù)全部重寫(xiě)為Flutter,所以這是一個(gè)逐步迭代的過(guò)程,這期間原生native界面與Flutter界面共存的狀態(tài)就稱(chēng)之為混合棧。閑魚(yú)在混合棧上也有一些比較好的輸出,例如FlutterBoost。
外接紋理
在講音視頻之前需要簡(jiǎn)要介紹一下外接紋理的概念,我們將它稱(chēng)之為是Flutter和Frame之間的橋梁。
Flutter渲染一幀屏幕數(shù)據(jù)首先要做的是,GPU發(fā)出的VC信號(hào)在Flutter的UI線(xiàn)程,通過(guò)AOT編譯的機(jī)器碼結(jié)合當(dāng)前Dart Runtime,生成Layer Tree UI樹(shù),Layer Tree上每一個(gè)葉子節(jié)點(diǎn)都代表了當(dāng)前屏幕上所需要渲染的每一個(gè)元素,包含了這些元素渲染所需要的內(nèi)容。將Layer Tree拋給GPU線(xiàn)程,在GPU線(xiàn)程內(nèi)調(diào)用Skia去完成整個(gè)UI的渲染過(guò)程。Layer Tree中有PictureLayer和TextureLayer兩個(gè)比較重要的節(jié)點(diǎn)。PictureLayer主要負(fù)責(zé)屏幕圖片的渲染,F(xiàn)lutter內(nèi)部實(shí)現(xiàn)了一套圖片解碼邏輯,在IO線(xiàn)程將圖片讀取或者從網(wǎng)絡(luò)上拉取之后,通過(guò)解碼能夠在IO線(xiàn)程上加載出紋理,交給GPU線(xiàn)程將圖片渲染到屏幕上。但是由于音視頻場(chǎng)景下系統(tǒng)API太過(guò)繁多,業(yè)務(wù)場(chǎng)景過(guò)于復(fù)雜。Flutter沒(méi)有一套邏輯去實(shí)現(xiàn)跨平臺(tái)的音視頻組件,所以說(shuō)Flutter提出了一種讓第三方開(kāi)發(fā)者來(lái)實(shí)現(xiàn)音視頻組件的方式,而這些音視頻組件的視頻渲染出口,就是TextureLayer。
在整個(gè)Layer Tree渲染的過(guò)程中,TextureLayer的數(shù)據(jù)紋理需要由外部第三方開(kāi)發(fā)者來(lái)指定,可以把視頻數(shù)據(jù)和播放器數(shù)據(jù)送到TextureLayer里,由Flutter將這些數(shù)據(jù)渲染出來(lái)。
TextureLayer渲染過(guò)程:首先判斷Layer是否已經(jīng)初始化,如果沒(méi)有就創(chuàng)建一個(gè)Texture,然后將Texture Attach到一個(gè)SufaceTexture上。
這個(gè)SufaceTexture是音視頻的native代碼可以獲取到的對(duì)象,通過(guò)這個(gè)對(duì)象創(chuàng)建的Suface,我們可以將視頻數(shù)據(jù)、攝像頭數(shù)據(jù)解碼放到Suface中,然后Flutter端通過(guò)監(jiān)聽(tīng)SufaceTexture的數(shù)據(jù)更新就可以順利把剛才創(chuàng)建的數(shù)據(jù)更新到它的紋理中,然后再將紋理交給SKIA渲染到屏幕上。
然而我們?nèi)绻枰肍lutter實(shí)現(xiàn)美顏,濾鏡,人臉貼圖等等功能,就需要將視頻數(shù)據(jù)讀取出來(lái),更新到紋理中,再將GPU紋理經(jīng)過(guò)美顏濾鏡處理后生成一個(gè)處理后的紋理。按Flutter提供的現(xiàn)有能力,必須先將紋理中的數(shù)據(jù)從GPU讀出到CPU中,生成Bitmap后再寫(xiě)入Surface中,這樣在Flutter中才能順利的更新到視頻數(shù)據(jù),這樣做對(duì)系統(tǒng)性能的消耗很大。
通過(guò)對(duì)Flutter渲染過(guò)程分析,我們知道Flutter底層需要渲染的數(shù)據(jù)就是GPU紋理,而我們經(jīng)過(guò)美顏濾鏡處理完成以后的結(jié)果也是GPU紋理,如果可以將它直接交給Flutter渲染,那就可以避免GPU-CPU-GPU這樣的無(wú)用循環(huán)。這樣的方法是可行的,但是需要一個(gè)條件,就是OpenGL上下文共享。
OpenGL
在說(shuō)上下文之前,得提到一個(gè)和上線(xiàn)文息息相關(guān)的概念:線(xiàn)程。
Flutter引擎啟動(dòng)后會(huì)啟動(dòng)四個(gè)線(xiàn)程:
第一個(gè)線(xiàn)程是UI線(xiàn)程,這是Flutter自己定義的UI線(xiàn)程,主要負(fù)責(zé)GPU發(fā)出的VSync信號(hào)時(shí)候用當(dāng)前Dart編譯的機(jī)器碼和當(dāng)前運(yùn)行環(huán)境創(chuàng)建出Layer Tree。
還有就是IO線(xiàn)程和GPU線(xiàn)程。和大部分OpenGL處理解決方案中一樣,F(xiàn)lutter也采取一個(gè)線(xiàn)程責(zé)資源加載,一部分負(fù)責(zé)資源渲染這種思路。
兩個(gè)線(xiàn)程之間紋理共享有兩種方式。一種是EGLImage(IOS是 CVOpenGLESTextureCache)。一種是OpenGL Share Context。Flutter通過(guò)Share Context來(lái)實(shí)現(xiàn)紋理共享,將IO線(xiàn)程的Context和GPU線(xiàn)程的Context進(jìn)行Share,放到同一個(gè)Share Group下面,這樣兩個(gè)線(xiàn)程下資源是互相可見(jiàn)可以共享的。
Platform線(xiàn)程是主線(xiàn)程,F(xiàn)lutter中有一個(gè)很奇怪的設(shè)定,GPU線(xiàn)程和主線(xiàn)程共用一個(gè)Context。并且在主線(xiàn)程也有很多OpenGL 操作。
這樣的設(shè)計(jì)會(huì)給音視頻開(kāi)發(fā)帶來(lái)很多問(wèn)題,后面會(huì)詳細(xì)說(shuō)。
音視頻端美顏處理完成的OpenGL紋理能夠讓Flutter直接使用的條件就是Flutter的上下文需要和平臺(tái)音視頻相關(guān)的OpenGL上下文處在一個(gè)Share Group下面。
由于Flutter主線(xiàn)程的Context就是GPU的Context,所以在音視頻端主線(xiàn)程中有一些OpenGL操作的話(huà),很有可能使Flutter整個(gè)OpenGL被破壞掉。所以需要將所有的OpenGL操作都限制在子線(xiàn)程中。
通過(guò)上述這兩個(gè)條件的處理,我們就可以在沒(méi)有增加GPU消耗的前提下實(shí)現(xiàn)美顏和濾鏡等等功能。
TPM
在經(jīng)過(guò)demo驗(yàn)證之后,我們將這個(gè)方案應(yīng)用到閑魚(yú)音視頻組件中,但改造過(guò)程中發(fā)現(xiàn)了一些問(wèn)題。
上圖是攝像頭采集數(shù)據(jù)轉(zhuǎn)換為紋理的一段代碼,其中有兩個(gè)操作:首先是切進(jìn)程,將后面的OpenGL操作都切到cameraQueue中。然后是設(shè)置一次上下文。然后這種限制條件或者說(shuō)是潛規(guī)則往往在開(kāi)發(fā)過(guò)程中容易被忽略的。而這個(gè)條件一旦忽略后果就是出現(xiàn)一些莫名其妙的詭異問(wèn)題極難排查。因此我們就希望能抽象出一套框架,由框架本身實(shí)現(xiàn)線(xiàn)程的切換、上下文和模塊生命周期等的管理,開(kāi)發(fā)者接入框架以后只需要安心實(shí)現(xiàn)自己的算法,而不需要關(guān)心這些潛規(guī)則還有其他一些重復(fù)的邏輯操作。
在引入Flutter之前閑魚(yú)的音視頻架構(gòu)與大部分音視頻邏輯一樣采用分層架構(gòu):
1:底層是一些獨(dú)立模塊
2:SDK層是對(duì)底層模塊的封裝
3:最上層是UI層。
引入Flutter之后,通過(guò)分析各個(gè)模塊的使用場(chǎng)景,我們可以得出一個(gè)假設(shè)或者說(shuō)是抽象:音視頻應(yīng)用在終端上可以歸納為視頻幀解碼之后視頻數(shù)據(jù)幀在各個(gè)模塊之間流動(dòng)的過(guò)程,基于這種假設(shè)去做Flutter音視頻框架的抽象。
咸魚(yú)Flutter多媒體開(kāi)源組件
整個(gè)Flutter音視頻框架抽象分為管線(xiàn)和數(shù)據(jù)的抽象、模塊的抽象、線(xiàn)程統(tǒng)一管理和上下文同一管理四部分。
管線(xiàn),其實(shí)就是視頻幀流動(dòng)的管道。數(shù)據(jù),音視頻中涉及到的數(shù)據(jù)包括紋理、Bit Map以及時(shí)間戳等。結(jié)合現(xiàn)有的應(yīng)用場(chǎng)景我們定義了管線(xiàn)流通數(shù)據(jù)以Texture為主數(shù)據(jù),同時(shí)可以選擇性的添加Bit Map等作為輔助數(shù)據(jù)。這樣的數(shù)據(jù)定義方式,避免重復(fù)的創(chuàng)建和銷(xiāo)毀紋理帶來(lái)的性能開(kāi)銷(xiāo)以及多線(xiàn)程訪問(wèn)紋理帶來(lái)的一些問(wèn)題。也滿(mǎn)足一些特殊模塊對(duì)特殊數(shù)據(jù)的需求。同時(shí)也設(shè)計(jì)了紋理池來(lái)管理管線(xiàn)中的紋理數(shù)據(jù)。
模塊:如果把管線(xiàn)和數(shù)據(jù)比喻成血管和血液,那框架音視頻的場(chǎng)景就可以比喻成器官,我們根據(jù)模塊所在管線(xiàn)的位置抽象出采集、處理和輸出三個(gè)基類(lèi)。這三個(gè)基類(lèi)里實(shí)現(xiàn)了剛才說(shuō)的線(xiàn)程切換,上下文切換,格式轉(zhuǎn)換等等共同邏輯,各個(gè)功能模塊通過(guò)集成自這些基類(lèi),可以避免很多重復(fù)勞動(dòng)。
線(xiàn)程:每一個(gè)模塊初始化的時(shí)候,初始化函數(shù)就會(huì)去線(xiàn)程管理的模塊去獲取自己的線(xiàn)程,線(xiàn)程管理模塊可以決定給初始化函數(shù)分配新的線(xiàn)程或者已經(jīng)分配過(guò)其他模塊的線(xiàn)程。
這樣有三個(gè)好處:
一是可以根據(jù)需要去決定一個(gè)線(xiàn)程可以?huà)燧d多少模塊,做到線(xiàn)程間的負(fù)載均衡。第二,多線(xiàn)程并發(fā)式能夠保證模塊內(nèi)的OpenGL操作是在當(dāng)前線(xiàn)程內(nèi)而不會(huì)跑到主線(xiàn)程去,徹底避免Flutter的OpenGL 環(huán)境被破壞。第三,多線(xiàn)程并行可以充分利用CPU多核架構(gòu),提升處理速度。
從Flutter端修改Flutter引擎將Context取出后,根據(jù)Context創(chuàng)建上下文的統(tǒng)一管理模塊,每一個(gè)模塊在初始化的時(shí)候會(huì)獲取它的線(xiàn)程,獲取之后會(huì)調(diào)用上下文管理模塊獲取自己的上下文。這樣可以保證每一個(gè)模塊的上下文都是與Flutter的上下文進(jìn)行Share的,每個(gè)模塊之間資源都是共享可見(jiàn)的,F(xiàn)lutter和音視頻native之間也是互相共享可見(jiàn)的。
基于上述框架如果要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的場(chǎng)景,比如畫(huà)面實(shí)時(shí)預(yù)覽和濾鏡處理功能,
1:需要選擇功能模塊,功能模塊包括攝像頭模塊、濾鏡處理模塊和Flutter畫(huà)面渲染模塊,
2:需要配置模塊參數(shù),比如采集分辨率、濾鏡參數(shù)和前后攝像頭設(shè)置等,
3:在創(chuàng)建視頻管線(xiàn)后使用已配置的參數(shù)創(chuàng)建模塊
4:最后管線(xiàn)搭載模塊,開(kāi)啟管線(xiàn)就可以實(shí)現(xiàn)這樣簡(jiǎn)單的功能。
上圖為整個(gè)功能實(shí)現(xiàn)的代碼和結(jié)構(gòu)圖。
結(jié)合上述音視頻框架,閑魚(yú)實(shí)現(xiàn)了Flutter多媒體開(kāi)源組件。
組要包含四個(gè)基本組件分別是:
1:視頻圖像拍攝組件
2:播放器組件
3:視頻圖像編輯組件
4:相冊(cè)選擇組件
現(xiàn)在這些組件正在走內(nèi)部開(kāi)源流程。預(yù)計(jì)9月份,相冊(cè)和播放器會(huì)實(shí)現(xiàn)開(kāi)源。
后續(xù)展望和規(guī)劃
1:實(shí)現(xiàn)開(kāi)頭所說(shuō)的從底層SDK到UI的全鏈路的跨端開(kāi)發(fā)。目前底層框架層和模塊層都是各個(gè)平臺(tái)各自實(shí)現(xiàn),反而是Flutter的UI端進(jìn)行了跨平臺(tái)的統(tǒng)一,所以后續(xù)會(huì)將底層也按照音視頻常用做法把邏輯下沉到C++層,盡可能的實(shí)現(xiàn)全鏈路跨平臺(tái)。
2:第二部分內(nèi)容為開(kāi)源共建,閑魚(yú)開(kāi)源的內(nèi)容不僅包括拍攝、編輯組件,還包括了很多底層模塊,希望有開(kāi)發(fā)者在基于Flutter開(kāi)發(fā)音視頻應(yīng)用時(shí)可以充分利用閑魚(yú)開(kāi)源出的音視頻模塊能力,搭建APP框架,開(kāi)發(fā)者只要去負(fù)責(zé)實(shí)現(xiàn)特殊需求模塊就可以,盡可能的減少重復(fù)勞動(dòng)。
頁(yè)面中的各界面元素(Widget)以樹(shù)的形式組織,即控件樹(shù)。Flutter通過(guò)控件樹(shù)中的每個(gè)控件創(chuàng)建不同類(lèi)型的渲染對(duì)象,組成渲染對(duì)象樹(shù)。而渲染對(duì)象樹(shù)在Flutter的展示過(guò)程分為三個(gè)階段:布局、繪制、合成和渲染。
(一)布局
Flutter采用深度優(yōu)先機(jī)制遍歷渲染對(duì)象樹(shù),決定渲染對(duì)象樹(shù)中各渲染對(duì)象在屏幕上的位置和尺寸。在布局過(guò)程中,渲染對(duì)象樹(shù)中的每個(gè)渲染對(duì)象都會(huì)接收父對(duì)象的布局約束參數(shù),決定自己的大小,然后父對(duì)象按照控件邏輯決定各個(gè)子對(duì)象的位置,完成布局過(guò)程。
為了防止因子節(jié)點(diǎn)發(fā)生變化而導(dǎo)致整個(gè)控件樹(shù)重新布局,F(xiàn)lutter加入了一個(gè)機(jī)制——布局邊界(Relayout Boundary),可以在某些節(jié)點(diǎn)自動(dòng)或手動(dòng)地設(shè)置布局邊界,當(dāng)邊界內(nèi)的任何對(duì)象發(fā)生重新布局時(shí),不會(huì)影響邊界外的對(duì)象,反之亦然。
二)繪制
布局完成后,渲染對(duì)象樹(shù)中的每個(gè)節(jié)點(diǎn)都有了明確的尺寸和位置。Flutter會(huì)把所有的渲染對(duì)象繪制到不同的圖層上。與布局過(guò)程一樣,繪制過(guò)程也是深度優(yōu)先遍歷,而且總是先繪制自身,再繪制子節(jié)點(diǎn)。
以下圖為例:節(jié)點(diǎn)1在繪制完自身后,會(huì)再繪制節(jié)點(diǎn)2,然后繪制它的子節(jié)點(diǎn)3、4和5,最后繪制節(jié)點(diǎn)6。
可以看到,由于一些其他原因(比如,視圖手動(dòng)合并)導(dǎo)致2的子節(jié)點(diǎn)5與它的兄弟節(jié)點(diǎn)6處于了同一層,這樣會(huì)導(dǎo)致當(dāng)節(jié)點(diǎn)2需要重繪的時(shí)候,與其無(wú)關(guān)的節(jié)點(diǎn)6也會(huì)被重繪,帶來(lái)性能損耗。
為了解決這一問(wèn)題,F(xiàn)lutter提出了與布局邊界對(duì)應(yīng)的機(jī)制——重繪邊界(Repaint Boundary)。在重繪邊界內(nèi),F(xiàn)lutter會(huì)強(qiáng)制切換新的圖層,這樣就可以避免邊界內(nèi)外的互相影響,避免無(wú)關(guān)內(nèi)容置于同一圖層引起不必要的重繪。
重繪邊界的一個(gè)典型場(chǎng)景是Scrollview。ScrollView滾動(dòng)的時(shí)候需要刷新視圖內(nèi)容,從而觸發(fā)內(nèi)容重繪。而當(dāng)滾動(dòng)內(nèi)容重繪時(shí),一般情況下其他內(nèi)容是不需要重繪的,這時(shí)候重繪邊界就派上用場(chǎng)了。
(三)合成和渲染
終端設(shè)備的頁(yè)面越來(lái)越復(fù)雜,因此Flutter的渲染樹(shù)層級(jí)通常很多,直接交付給渲染引擎進(jìn)行多圖層渲染,可能會(huì)出現(xiàn)大量渲染內(nèi)容的重復(fù)繪制,所以還需要先進(jìn)行一次圖層合成,即將所有的圖層根據(jù)大小、層級(jí)、透明度等規(guī)則計(jì)算出最終的顯示效果,將相同的圖層歸類(lèi)合并,簡(jiǎn)化渲染樹(shù),提高渲染效率。
合并完成后,F(xiàn)lutter會(huì)將幾何圖層數(shù)據(jù)交由Skia引擎加工成二維圖像數(shù)據(jù),最終交由GPU進(jìn)行渲染,完成界面的展示。
四、總結(jié)
咱們從各種業(yè)界主流跨端方案與Flutter的對(duì)比開(kāi)始,到Flutter的簡(jiǎn)要介紹以及Flutter的運(yùn)行機(jī)制,并以界面渲染過(guò)程為例,從布局、繪制、合成和渲染三個(gè)階段講述了Flutter的實(shí)現(xiàn)原理。相信大家對(duì)Flutter已經(jīng)有一個(gè)整體認(rèn)知,趕快一起上手操作起來(lái)吧!
本文標(biāo)題:flutter渲染時(shí)機(jī)的簡(jiǎn)單介紹
本文網(wǎng)址:http://chinadenli.net/article41/dseoohd.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、軟件開(kāi)發(fā)、品牌網(wǎng)站設(shè)計(jì)、外貿(mào)建站、、品牌網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)