本篇文章主要介紹Flutter 渲染框架及其渲染過程android
Flutter是谷歌的移動UI框架,在此以前也有相似ReactNative、Weex等跨端方案,Flutter在必定程度上借鑑了ReactNative的思想,採用三棵樹 其中element tree diff管理,來觸發renderTree的刷新,而且不一樣於android這種命令式視圖開發,採用了聲明式,下面將一一介紹。git
在Android視圖開發中是命令式的,view大多數都是在xml聲明,開發者而後經過id找出view,數據更新時,仍須要開發者關注須要變化的view,再調用方法好比 setText之類的使其發生改變;
可是在Flutter中視圖的開發是聲明式的,開發者須要維護好一套數據集合以及綁定好widgetTree,這樣後面數據變化時候widget會根據數據來渲染,開發者就再也不關注每一個組件,關心核心數據便可。github
Flutter的渲染框架分爲Framework和Engine兩層,應用是基於Framework層開發,其中web
該跨平臺應用框架沒有使用webview或者平臺自帶的組件,使用自身的高性能渲染引擎Skia 自繪,組件之間能夠任意組合
算法
flutter中經過各類各樣的widget組合使用,視圖樹中包含了如下三種樹 Widget、Element、RenderObject,對應關係以下編程
一般 咱們建立widget樹,而後調用runApp(rootWidget),將rootWidget傳給rootElement,做爲rootElement的子節點,生成Element樹,由Element樹生成Render樹
數組
Flutter界面開發是一種響應式的編程,當數據發生變化時通知到可變動的節點(statefullWidget或者rootwidget),可是每次數據變動,都會觸發widgetTree的重繪,因爲widget只是持有一些渲染的配置信息而已,不是真正觸發渲染的對象,很是輕量級,flutter團隊對widget的建立、銷燬作了優化,不用擔憂整個widget樹從新建立帶來的性能問題。RenderObject纔是真正渲染時使用,涉及到layout、paint等複雜操做,是一個真正渲染的view,兩者被Element Tree持有,ElementTree經過Diff 算法來將不斷變化的widget轉變爲相對穩定的RenderObject。
當咱們不斷改變widget時,BuilderOwner收到widgetTree會與以前的widgetTree做對比,在ElementTree上只更新變化的部分,當Elment變化以後 與之對應的RenderObject也就更新了,以下圖所示
緩存
在介紹Elment Tree的Diff規則以前,先介紹下,這三者以前的關係,以前也大體提到 Elment Tree持有了Element同時持有Widget和RenderObject(BuilderOwner),咱們先從代碼入手app
能夠看出 Widget抽象類有3個關鍵能力框架
從上面類圖也能夠看出,**Element和RenderObject都是由Widget建立出來,**也並非每個Widget都有與之對應的RenderObject
在Android中ViewTree
-PhoneWindow
- DecorView
- TitleView
- ContentView
複製代碼
而在Flutter中則比較簡單,只有底層的root widget
- RenderObjectToWidgetAdapter<RenderBox>
- MyApp (自定義)
- MyMaterialApp (自定義)
複製代碼
其中RenderObjectToWidgetAdapter 也是一個renderObjectWidget,經過註釋能夠發現它是runApp啓動時「A bridge from a [RenderObject] to an [Element] tree.」
runApp代碼
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
複製代碼
WidgetsFlutterBinding 初始化了一系列的Binding,這些Binding持有了咱們上面說的一些owner,好比BuildOwner,PipelineOwner,因此隨着WidgetsFlutterBinding的初始化,其餘的Binding也被初始化了,
GestureBinding | 提供了 window.onPointerDataPacket 回調,綁定 Framework 手勢子系統,是 Framework 事件模型與底層事件的綁定入口 |
---|---|
ServicesBinding | 提供了 window.onPlatformMessage 回調, 用於綁定平臺消息通道(message channel),主要處理原生和 Flutter 通訊 |
SchedulerBinding | 提供了 window.onBeginFrame 和 window.onDrawFrame 回調,監聽刷新事件,綁定 Framework 繪製調度子系統 |
PaintingBinding | 綁定繪製庫,主要用於處理圖片緩存 |
SemanticsBinding | 語義化層與 Flutter engine 的橋樑,主要是輔助功能的底層支持 |
RendererBinding | 提供了 window.onMetricsChanged 、window.onTextScaleFactorChanged 等回調。它是渲染樹與 Flutter engine 的橋樑 |
WidgetsBinding | 提供了 window.onLocaleChanged、onBuildScheduled 等回調。它是 Flutter widget 層與 engine 的橋樑 |
繼續跟進下attachRootWidget(app)
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}
複製代碼
內部建立了 RenderObjectToWidgetAdapter 並將咱們傳入的app 自定義widget作了child,接着執行attachToRenderTree這個方法,建立了第一個Element和RenderObject
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
if (element == null) {
owner.lockState(() {
element = createElement(); //建立rootElement
element.assignOwner(owner); //綁定BuildOwner
});
owner.buildScope(element, () { //子widget的初始化從這裏開始
element.mount(null, null); // 初始化子Widget前,先執行rootElement的mount方法
});
} else {
...
}
return element;
}
複製代碼
咱們解釋一下上面的圖片,Root的建立比較簡單:
attachRootWidget(app)
方法建立了Root[Widget](也就是 RenderObjectToWidgetAdapter)attachToRenderTree
方法建立了 Root[Element]mount
方法將本身掛載到父Element上,由於本身就是root了,因此沒有父Element,掛空了createRenderObject
,建立了 Root[RenderObject]它的child,也就是咱們傳入的app是怎麼掛載父控件上的呢?
owner.buildScope
,開始執行子Tree的建立以及掛載,敲黑板!!!這中間的流程和WidgetTree的刷新流程是如出一轍的,詳細流程咱們後面講!createElement
方法建立出Child[Element]mount
方法,將本身掛載到Root[Element]上,造成一棵樹widget.createRenderObject
,建立Child[RenderObject]attachRenderObject
,完成和Root[RenderObject]的連接就這樣,WidgetTree、ElementTree、RenderObject建立完成,並有各自的連接關係。
這裏有兩個操做須要注意下,
mount
abstract class Element: void mount(Element parent, dynamic newSlot) {
_parent = parent; //持有父Element的引用
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;//當前節點的深度
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner; //每一個Element的buildOwner,都來自父類的BuildOwner
...
}
複製代碼
咱們先看一下Element的掛載,就是讓_parent
持有父Element的引用,由於RootElement 是沒有父Element的,因此參數傳了null:element.mount(null, null);
還有兩個值得注意的地方:
RenderObjectElement
abstract class RenderObjectElement: @override void attachRenderObject(dynamic newSlot) {
...
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
...
}
複製代碼
RenderObject與父RenderObject的掛載稍微複雜了點。經過代碼咱們能夠看到須要先查詢一下本身的AncestorRenderObject
,這是爲何呢?
還記得以前咱們講過,每個Widget都有一個對應的Element,但Element不必定會有對應的RenderObject。因此你的父Element並不一有RenderObject,這個時候就須要向上查找。
RenderObjectElement _findAncestorRenderObjectElement() {
Element ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor;
}
複製代碼
經過代碼咱們也能夠看到,find方法在向上遍歷Element,直到找到RenderObjectElement,RenderObjectElement確定是有對應的RenderObject了,這個時候在進行RenderObject子父間的掛載。
當須要更新UI的時候,Framework通知Engine,Engine會等到下個Vsync信號到達的時候,會通知Framework,而後Framework會進行animations, build,layout,compositing,paint,最後生成layer提交給Engine。Engine會把layer進行組合,生成紋理,最後經過Open Gl接口提交數據給GPU, GPU通過處理後在顯示器上面顯示。整個流程以下圖:
在Flutter開發應用的時候,當須要更新的UI的時候,須要調用一下setState方法,而後就能夠實現了UI的更新,咱們接下來分析一下該方法作哪些事情。
void setState(VoidCallback fn) {
...
_element.markNeedsBuild(); //經過相應的element來實現更新,關於element,widget,renderOjbect這裏不展開討論
}
複製代碼
繼續追蹤
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
複製代碼
widget對應的element將自身標記爲dirty
狀態,並調用owner.scheduleBuildFor(this);
通知buildOwner進行處理
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled(); //這是一個callback,調用的方法是下面的_handleBuildScheduled
}
_dirtyElements.add(element); //把當前element添加到_dirtyElements數組裏面,後面從新build會遍歷這個數組
element._inDirtyList = true;
}
複製代碼
後續MyStatefulWidget的build方法必定會被執行,執行後,會建立新的子Widget出來,原來的子Widget便被拋棄掉了,原來的子Widget確定是沒救了,但他們的Element大機率仍是有救的,此時 buildOwner會將全部dirty的Element添加到_dirtyElements當中
通過Framework一連串的調用後,最終調用scheduleFrame來通知Engine須要更新UI,Engine就會在下個vSync到達的時候經過調用_drawFrame來通知Framework,而後Framework就會經過BuildOwner進行Build和PipelineOwner進行Layout,Paint,最後把生成Layer,組合成Scene提交給Engine。
底層引擎最終回到Dart層,並執行buildOwner的buildScope方法,首先從Engine回調Framework的入口開始。
void _drawFrame() { //Engine回調Framework入口
_invoke(window.onDrawFrame, window._onDrawFrameZone);
}
//初始化的時候把onDrawFrame設置爲_handleDrawFrame
void initInstances() {
super.initInstances();
_instance = this;
ui.window.onBeginFrame = _handleBeginFrame;
ui.window.onDrawFrame = _handleDrawFrame;
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
}
void _handleDrawFrame() {
if (_ignoreNextEngineDrawFrame) {
_ignoreNextEngineDrawFrame = false;
return;
}
handleDrawFrame();
}
void handleDrawFrame() {
_schedulerPhase = SchedulerPhase.persistentCallbacks;//記錄當前更新UI的狀態
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
}
}
void initInstances() {
....
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
void drawFrame() {
...
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement); //先從新build widget
super.drawFrame();
buildOwner.finalizeTree();
}
複製代碼
核心方法 buildScope
void buildScope(Element context, [VoidCallback callback]){
...
}
複製代碼
須要傳入一個Element的參數,這個方法經過字面意思應該理解就是對這個Element如下範圍rebuild
void buildScope(Element context, [VoidCallback callback]) {
...
try {
...
_dirtyElements.sort(Element._sort); //1.排序
...
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild(); //2.遍歷rebuild
} catch (e, stack) {
}
index += 1;
}
} finally {
for (Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear(); //3.清空
...
}
}
複製代碼
這裏對上面方法作下解釋
因爲父Widget的build方法必然會觸發子Widget的build,若是先build了子Widget,後面再build父Widget時,子Widget又要被build一次。因此這樣排序以後,能夠避免子Widget的重複build。
值得一提的是,遍歷執行的過程當中,也有可能會有新的element被加入到_dirtyElements集合中,此時會根據dirtyElements集合的長度判斷是否有新的元素進來了,若是有,就從新排序。
element的rebuild方法最終會調用
performRebuild()
,而performRebuild()
不一樣的Element有不一樣的實現
所以setState()過程主要工做是記錄全部的髒元素,添加到BuildOwner對象的_dirtyElements成員變量,而後調用scheduleFrame來註冊Vsync回調。 當下一次vsync信號的到來時會執行handleBeginFrame()和handleDrawFrame()來更新UI。
在上面的第二步會遍歷執行element的build方法
_dirtyElements[index].rebuild(); //2.遍歷rebuild
element的rebuild方法最終會調用performRebuild()
,而performRebuild()
不一樣的Element有不一樣的實現,如下面兩個爲例
void performRebuild() {
Widget built;
try {
built = build();
}
...
try {
_child = updateChild(_child, built, slot);
}
...
}
複製代碼
執行element的build();
,以StatefulElement的build方法爲例:Widget build() => state.build(this);
。 就是執行了咱們複寫的StatefulWidget的state的build方法,此時建立出來的固然就是這個StatefulWidget的子Widget了
下面看下核心方法 Element updateChild(Element child, Widget newWidget, dynamic newSlot)
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
//1
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
if (child != null) {
//2
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
//3
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
return child;
}
deactivateChild(child);
}
//4
return inflateWidget(newWidget, newSlot);
}
複製代碼
參數child 是上一次Element掛載的child Element, newWidget 是剛剛build出來的。updateChild有四種可能的狀況
Widget.canUpdate
的邏輯是判斷key值和運行時類型是否相等。若是知足條件的話,就更新,並返回。inflateWidget()
建立新的Element這裏再看下inflateWidget()
方法:
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
複製代碼
首先會嘗試經過GlobalKey去查找可複用的Element,複用失敗就調用Widget的方法建立新的Element,而後調用mount方法,將本身掛載到父Element上去,mount以前咱們也講過,會在這個方法裏建立新的RenderObject。
@override
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
複製代碼
與ComponentElement的不一樣之處在於,沒有去build,而是調用了updateRenderObject
方法更新RenderObject。到這裏咱們基本就明白了Element是如何在中間應對Widget的多變,保障RenderObject的相對不變了