2.Flutte3.0 遙遙領先系列|一文教你完全掌握flutter渲染核心(3顆樹)

目錄:

  1. Flutter渲染3顆樹和源碼分析 (重點)
    ComponentElement
    RenderObjectElement
  2. Flutter渲染3顆樹案例分析
  3. flutter的屏幕渲染原理Vsync機制
  4. Flutter渲染3部曲 核心渲染流程

1. Flutter渲染3顆樹和源碼分析

把視圖數據的組織和渲染抽象為三部分,即 Widget,Element 和 RenderObject。
那么,Widget到底是什么呢?

Widget是Flutter功能的抽象描述,是視圖的配置信息,同樣也是數據的映射,是Flutter開發框架中最基本的概念。前端框架中常見的名詞,比如視圖(View)、視圖控制器(View Controller)、活動(Activity)、應用(Application)、布局(Layout)等,在Flutter中都是Widget。

事實上,Flutter的核心設計思想便是“一切皆Widget”

1.1 Widget (描述 UI 渲染的配置信息)

Widget是控件實現的基本邏輯單位,里面存儲的是有關視圖渲染的配置信息,包括布局、渲染屬性、事件響應信息等。

Widget總結: 我們開發最直接的,

絕大多數情況下,我們只需要了解各種Widget特性及使用方法,而無需關心Element及RenderObject!

Widget源碼分析:

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });
    
  ///  * The discussions at [Key] and [GlobalKey].
  final Key? key;

  @protected
  @factory
  Element createElement();

  static bool canUpdate(Widget oldWidget, Widget newWidget) { // canupdate方法
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  @protected
  Widget build(BuildContext context);

那一般我們可以怎么獲取布局的大小和位置呢?
Widget大概可以分為三類組合類(紫色標識)、代理類(紅色標識)、繪制類(黃色標識)

1). 組合類: Container和Text就是組合類的Widget
2). 代理類: 狀態管理的,例如:InheritedWidget用于將一些狀態信息傳遞給子孫Widget。
3). 渲染類: 所有我們在屏幕上看到的UI最終幾乎都會通過繪制類的WidgetRenderObjectWidget實現。RenderObjectWidget中有個createRenderObject()方法生成實際渲染的RenderObject對象!

LeafRenderObjectWidget 用于只有渲染功能,無子節點 (如Switch、Radio等)
SingleChildRenderObjectWidget 只含有一個子節點 (如SizedBox、Padding等)
MultiChildRenderObjectWidget 含有多個子節點(如Column、Stack等)
RenderObjectWidget介紹
RenderObjectWidget是一個抽象類。我們通過源碼可以看到,這個類中同時擁有創建Element、RenderObject,以及更新RenderObject的方法
RenderObjectWidget本身并不負責這些對象的創建與更新。

blogwidget3種類型2jpg.jpg

1.2 Element (存放上下文,持有 Widget 和 RenderObject)

Element是Widget的一個實例化對象,它承載了視圖構建的上下文數據,是連接結構化的配置信息到完成最終渲染的橋梁。
Element同時持有Widget和RenderObject。而無論是Widget還是Element,其實都不負責最后的渲染,只負責發號施令,真正去干活兒的只有RenderObject。那你可能會問,既然都是發號施令,那為什么需要增加中間的這層Element樹呢?直接由Widget命令RenderObject去干活兒不好嗎?
答案是,可以,但這樣做會極大地增加渲染帶來的性能損耗。

總結: 中間層, 梳理差異, 提高渲染效率 (diff),

Widget 和 Element 之間是一對多的關系 。

問題:Element是如何創建的?

創建出來后會由framework調用mount方法;

問題: Element會重新創建么?

在 newWidget 與oldWidget的 runtimeType 和 key 相等時會選擇使用 newWidget 去更新已經存在的 Element 對象,不然就選擇重新創建新的 Element。
Element 持有 RenderObject 和 Widget! Element 是 Widget 和 RenderObject 的粘合劑
Element分為3種, ComponentElement和RenderObjectElement,ProxyElement, 前者負責組合子Element,后者負責渲染, 最后的是代理類
如圖:


blog_element.jpg

1). 組合類: ComponentElement主要的子類:
2). 渲染類: RenderObjectElement的主要子類:SingleChildRenderObjectElement、MultiChildRenderObjectElement、RenderObjectToWidgetElement。SingleChildRenderObjectElement
如圖: 組合類生成渲染類, 但是渲染類也可以生產出組合類!
而我們也知道 BuildContext 的實現其實是 Element, 所以本質上BuildContext就是當前的Element

 abstract class Element extends DiagnosticableTree implements BuildContext {
 
RenderObject? get renderObject {
    Element? current = this;
    while (current != null) {
      if (current._lifecycleState == _ElementLifecycle.defunct) {
        break;
      } else if (current is RenderObjectElement) {
        return current.renderObject;
      } else {
        Element? next;
        current.visitChildren((Element child) {
          assert(next == null);  // This verifies that there's only one child.
          next = child;
        });
        current = next;
      }
    }
    return null;
  }
    @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    assert(!_renderObject!.debugDisposed!);
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    assert(() {
      _debugUpdateRenderObjectOwner();
      return true;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    super.performRebuild(); // clears the "dirty" flag
  }
     
      @override
  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null) {
      _updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
    }
  }

1.2.1 組合類ComponentElement的核心流程和核心方法分析:
1.2.2 渲染類RenderObjectElement 的核心流程和核心方法分析:
1.3.1 重點方法Element.inflateWidget() 重點方法
1.3.2重點方法 Element.mount()
1.3.3 重點方法 RenderObjectElement.performRebuild()
1.3.4 RenderObjectElement.update()
1.3.5 RenderObjectElement.updateChildren()
整個創建, 更新的圖片如下:


blogelement重要的api3.jpg

————————————————

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

@override
void performRebuild() {
//更新renderObject
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
1.3 RenderObject (實際渲染樹中的對象)
問題: RenderObject是如何創建的?
@override
  void mount(Element parent, dynamic newSlot) { // 調用 mount方法! 
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);  // 通過widget,創建RenderObject
    assert(() {
      _debugUpdateRenderObjectOwner();
      returntrue;
    }());
    assert(_slot == newSlot);
    attachRenderObject(newSlot);
    _dirty = false;
  }

如果你去看類似于Text這種組合類的Widget,它也會執行mount方法,但是mount方法中并沒有調用createRenderObject這樣的方法。
RenderObject才是真正負責繪制的對象,其中包含了paint,layout等方法~
布局和繪制在RenderObject中完成,Skia: 負責合成和渲染!
更新和變化的流程: canupdate方法
如果Widget的配置數據發生了改變,那么持有該Widget的Element節點也會被標記為dirty。
在下一個周期的繪制時,Flutter就會觸發Element樹的更新,并使用最新的Widget數據更新自身以及關聯的RenderObject對象,
接下來便會進入Layout和Paint的流程。而真正的繪制和布局過程,則完全交由RenderObject完成:
布局和繪制完成后,接下來的事情就交給Skia了。在VSync信號同步時直接從渲染樹合成Bitmap,然后提交給GPU

  @protected
  @pragma('vm:prefer-inline')
  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    if (newWidget == null) {
      if (child != null) {
        deactivateChild(child);
      }
      return null;
    }

    final Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
      assert(() {
        final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
        return true;
      }());
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
        if (isTimelineTracked) {
          Map<String, String>? debugTimelineArguments;
          assert(() {
            if (kDebugMode && debugEnhanceBuildTimelineArguments) {
              debugTimelineArguments = newWidget.toDiagnosticsNode().toTimelineArguments();
            }
            return true;
          }());
          FlutterTimeline.startSync(
            '${newWidget.runtimeType}',
            arguments: debugTimelineArguments,
          );
        }
        child.update(newWidget);
        if (isTimelineTracked) {
          FlutterTimeline.finishSync();
        }
        assert(child.widget == newWidget);
        assert(() {
          child.owner!._debugElementWasRebuilt(child);
          return true;
        }());
        newChild = child;
      } else {
        deactivateChild(child);
        assert(child._parent == null);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }

    assert(() {
      if (child != null) {
        _debugRemoveGlobalKeyReservation(child);
      }
      final Key? key = newWidget.key;
      if (key is GlobalKey) {
        assert(owner != null);
        owner!._debugReserveGlobalKeyFor(this, newChild, key);
      }
      return true;
    }());

    return newChild;
  }
abstract class Widget extends DiagnosticableTree {

  final Key? key;

  @protected
  @factory
  Element createElement();

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  ...
}

canupdate()方法:
canUpdate(...)是一個靜態方法,它主要用于在 widget 樹重新build時復用舊的 widget ,其實具體來說,應該是:是否用新的 widget 對象去更新舊UI樹上所對應的Element對象的配置;通過其源碼我們可以看到,只要newWidget與oldWidget的runtimeType和key同時相等時就會用new widget去更新Element對象的配置,否則就會創建新的Element。
Key: 這個key屬性類似于 React/Vue 中的key,主要的作用是決定是否在下一次build時復用舊的 widget ,決定的條件在canUpdate()方法中
布局,從RenderBox開始,對RenderObject Tree從上至下進行布局。

abstract class RenderBox extends RenderObject {
  @override
  void setupParentData(covariant RenderObject child) {
    if (child.parentData is! BoxParentData) {
      child.parentData = BoxParentData();
    }
  }

2. Flutter渲染3顆樹案例分析

從一個demo分析出Widget,Element 和 RenderObject 3者的關系:

  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: Column(
        children: <Widget>[
          SizedBox(height: 300,width: 100,),
          Text("dd"),
          Text("aa"),
          Text("bb"),
        ],
      ),);
  }
2.1代碼結構對應的widget
原始組件.jpg

組合widget: 藍色
渲染widget: 黃色

class Container extends StatelessWidget {  
    
Widget build(BuildContext context) {                                          
  Widget? current = child;                                                    
                                                                                                                               
  if (decoration != null) {                                                   
    current = DecoratedBox(decoration: decoration!, child: current);          
  }                                                                           
widget組件.jpg
2.2 Element:
element.jpg
2.3RenderObject :

舉例: container是如何對應RenderDecoratedBox!
Container具體的渲染widget是DecoratedBox! DecoratedBox里面創建RenderObject

class Container extends StatelessWidget {  
     Widget build(BuildContext context) {                                                                                       
     current = DecoratedBox(decoration: decoration!, child: current);          
   }                                                                           
    
class DecoratedBox extends SingleChildRenderObjectWidget {  
    
@override                                                           
RenderDecoratedBox createRenderObject(BuildContext context) {       
  return RenderDecoratedBox(                                        
    decoration: decoration,                                         
    position: position,                                             
    configuration: createLocalImageConfiguration(context),          
  );                                                                
 }       
}
renderobject.jpg
blog3顆樹完整1 .jpg
完整的圖片過程: 寫的widet--->實際widget--->Element----->RenderObject
jietu-1705565019958.jpg
3顆樹總結: 三者的關系:
Flutter渲染過程,可以分為這么三步:

首先,通過Widget樹生成對應的Element樹;
然后,創建相應的RenderObject并關聯到Element.renderObject屬性上;
最后,構建成RenderObject樹,以完成最終的渲染。

可以大致總結出三者的關系是:配置文件 Widget 生成了 Element,

而后創建 RenderObject 關聯到 Element 的內部 renderObject 對象上,
最后Flutter 通過 RenderObject 數據來布局和繪制。
理論上你也可以認為 RenderObject 是最終給 Flutter 的渲染數據,它保存了大小和位置等信息,Flutter 通過它去繪制出畫面。
Widget的渲染原理

所有的Widget都會創建一個或者多個Element對象
并不是所有的Widget都會被獨立渲染!只有繼承RenderObjectWidget的才會創建RenderObject對象!(Container就不會創建RenderObject、column和padding這些可以創建RenderObject)
在Flutter渲染的流程中,有三顆重要的樹!Flutter引擎是針對Render樹進行渲染!
Widget樹、Element樹、Render樹
每一個Widget都會創建一個Element對象
隱式調用createElement方法。Element加入Element樹中,它會創建RenderElement、ComponentElement(又分為StatefulElement和StatelessElement)。
RenderElement主要是創建RenderObject對象, 繼承RenderObjectWidget的Widget會創建RenderElement
創建RanderElement
Flutter會調用mount方法,調用createRanderObject方法
StatefulElement繼承ComponentElement,StatefulWidget會創建StatefulElement
調用createState方法,創建State
將Widget賦值給state
調用state的build方法 并且將自己(Element)傳出去,build里面的context 就是Widget的Element !
StatelessElement繼承ComponentElement,StatelessWidget會創建StatelessElement
mount方法 -> firstBuild -> rebuild -> performBuild -> build -> _widget.build
-主要就是調用build方法 并且將自己(Element)傳出去

問題:
1. 已知widget, 如何找到對應的renderObject?

先找到對應的渲染的widget, 然后在里面找renderObject!
renderObject創建是在渲染的widget里面創建的, 而不是Element創建的!

2. Widget會重新創建么?Element會重新創建么?RenderObject 會重新創建么?

Widget 重新創建,Element 樹和 RenderObject 樹并不會完全重新創建。
Element什么時候創建?
在每一次創建Widget的時候,會創建一個對應的Element,然后將該元素插入樹中

3. flutter的屏幕渲染原理Vsync機制

在計算機系統中,圖像的顯示需要 CPU、GPU 和顯示器一起配合完成:CPU 負責圖像數據計算,GPU 負責圖像數據渲染,而顯示器則負責最終圖像顯示。

CPU 把計算好的、需要顯示的內容交給 GPU,由 GPU 完成渲染后放入幀緩沖區,隨后視頻控制器根據垂直同步信號(VSync)以每秒 60 次的速度,從幀緩沖區讀取幀數據交由顯示器完成圖像顯示。

屏幕渲染原理:

blog 屏幕渲染原理.jpg

問題: 原生渲染和flutter渲染的共同點和差異是啥?

底層是用skia, 其他封裝調用不一樣!

問題: Skia是什么?

要想了解Flutter,你必須先了解它的底層圖像渲染引擎Skia。

因為,Flutter只關心如何向GPU提供視圖數據,而Skia就是它向GPU提供視圖數據的好幫手。

Skia是一款用C++開發的、性能彪悍的2D圖像繪制引擎,其前身是一個向量繪圖軟件。2005年被Google公司收購后,因為其出色的繪制表現被廣泛應用在Chrome和Android等核心產品上。Skia在圖形轉換、文字渲染、位圖渲染方面都表現卓越,并提供了開發者友好的API。

因此,架構于Skia之上的Flutter,也因此擁有了徹底的跨平臺渲染能力。通過與Skia的深度定制及優化,Flutter可以最大限度地抹平平臺差異,提高渲染效率與性能。

底層渲染能力統一了,上層開發接口和功能體驗也就隨即統一了,開發者再也不用操心平臺相關的渲染特性了。也就是說,Skia保證了同一套代碼調用在Android和iOS平臺上的渲染效果是完全一致的。

問題: flutter新引擎impller與skia的區別

字節跳動: 深入解析Flutter下一代渲染引擎Impeller (ok)

https://juejin.cn/user/3368492743792695/posts

渲染的源碼分析:

請求 Vsync 信號

SchedulerBinding:

mixin SchedulerBinding on BindingBase {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;

    if (!kReleaseMode) {
      addTimingsCallback((List<FrameTiming> timings) {
        timings.forEach(_profileFramePostEvent);
      });
    }
  }
  /// dart
  /// 調用 C++ 到 Native 層,請求 Vsync 信號
   void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled) {
      return;
    }
    assert(() {
      if (debugPrintScheduleFrameStacks) {
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      }
      return true;
    }());
    ensureFrameCallbacksRegistered();
    platformDispatcher.scheduleFrame();
    _hasScheduledFrame = true;
  }

  void ensureFrameCallbacksRegistered() {
    platformDispatcher.onBeginFrame ??= _handleBeginFrame;
    platformDispatcher.onDrawFrame ??= _handleDrawFrame;
  }
void scheduleFrame() native 'Window_scheduleFrame'; // c++方法
  void _handleBeginFrame(Duration rawTimeStamp) {
    if (_warmUpFrame) {
      // "begin frame" and "draw frame" must strictly alternate. Therefore
      // _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
      // reset by _handleDrawFrame.
      assert(!_rescheduleAfterWarmUpFrame);
      _rescheduleAfterWarmUpFrame = true;
      return;
    }
    handleBeginFrame(rawTimeStamp);
  }
  void _handleDrawFrame() {
    if (_rescheduleAfterWarmUpFrame) {
      _rescheduleAfterWarmUpFrame = false;
      // Reschedule in a post-frame callback to allow the draw-frame phase of
      // the warm-up frame to finish.
      addPostFrameCallback((Duration timeStamp) {
        // Force an engine frame.
        //
        // We need to reset _hasScheduledFrame here because we cancelled the
        // original engine frame, and therefore did not run handleBeginFrame
        // who is responsible for resetting it. So if a frame callback set this
        // to true in the "begin frame" part of the warm-up frame, it will
        // still be true here and cause us to skip scheduling an engine frame.
        _hasScheduledFrame = false;
        scheduleFrame();
      });
      return;
    }
    handleDrawFrame();
  }

以安卓為例,最終會執行到 JNI_OnLoad 注冊的 Java 接口 AsyncWaitForVsyncDelegate.asyncWaitForVsync,這個接口在 Flutter 啟動時初始化。實現內容如下
Flutter引擎啟動時,向系統的Choreographer實例注冊接收Vsync的回調函數,GPU硬件發出Vsync后,系統會觸發該回調函數,并驅動UI線程進行layout和繪制。

new FlutterJNI.AsyncWaitForVsyncDelegate() {
        @Override
        public void asyncWaitForVsync(long cookie) {
          Choreographer.getInstance()
              .postFrameCallback(
                  new Choreographer.FrameCallback() {
                    @Override
                    public void doFrame(long frameTimeNanos) {
                      float fps = windowManager.getDefaultDisplay().getRefreshRate();
                      long refreshPeriodNanos = (long) (1000000000.0 / fps);
                      FlutterJNI.nativeOnVsync(
                          frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
                    }
                  });
        }
      }

Choreographer.getInstance().postFrameCallback 用于監聽系統垂直同步信號,在下一個垂直信號來臨時回調 doFrame,通過 FlutterJNI.nativeOnVsync 走到 c++ 中。經過復雜的鏈路,將下面的任務添加到到 UI Task Runner 中的事件隊列中:

lib/ui/window/platform_configuration.cc

void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime) {
  .................
  // 調用 dart 中的 _window.onBeginFrame
  tonic::LogIfError(
      tonic::DartInvoke(begin_frame_.Get(), {Dart_NewInteger(microseconds),}));
  // 執行 microTask                                          
  UIDartState::Current()->FlushMicrotasksNow();
  // 調用 dart 中的 _window.onDrawFrame
  tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
}

當接收到Vsync時,會調用到RendererBinding的drawFrame方法;

[源碼路徑:flutter/lib/src/rendering/binding.dart]
  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();    // 觸發RenderObject執行布局
    pipelineOwner.flushCompositingBits();    //繪制之前的預處理操作
    pipelineOwner.flushPaint();    // 觸發RenderObject執行繪制
    renderView.compositeFrame(); // 將需要繪制的比特數據發給GPU
    pipelineOwner.flushSemantics(); // 發送語義化給系統,用于輔助功能等
  }

Window 正是 Flutter Framework 連接宿主操作系統的接口

class Window {
    
  // 當前設備的DPI,即一個邏輯像素顯示多少物理像素,數字越大,顯示效果就越精細保真。
  // DPI是設備屏幕的固件屬性,如Nexus 6的屏幕DPI為3.5 
  double get devicePixelRatio => _devicePixelRatio;
  
  // Flutter UI繪制區域的大小
  Size get physicalSize => _physicalSize;

  // 當前系統默認的語言Locale
  Locale get locale;
    
  // 當前系統字體縮放比例。  
  double get textScaleFactor => _textScaleFactor;  
    
  // 當繪制區域大小改變回調
  VoidCallback get onMetricsChanged => _onMetricsChanged;  
  // Locale發生變化回調
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  // 系統字體縮放變化回調
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  // 繪制前回調,一般會受顯示器的垂直同步信號VSync驅動,當屏幕刷新時就會被調用
  FrameCallback get onBeginFrame => _onBeginFrame;
runApp這個方法總的來說就是做了以下的事情

在Flutter的framework層和engine層建立一個連接WidgetsFlutterBinding,注冊Vsync回調后,每一幀調用的時候都會觸發WidgetsFlutterBinding里面的方法,從而去調用framework層的處理邏輯

為傳入的widget構建節點樹,將節點樹中的RenderObjct樹的結果交給enginee層的SingletonFlutterWindow,然后通知到GPU進行渲染

總結:

1)、scheduleFrame 回調 C++ 注冊 Vsync 信號

2)、Vsnc 來臨,回調 C++ 調用 window.onBeginFrame,這個方法映射到 dart 中的 handleBeginFrame(), window.onDrawFrame

對應 dart 中的 drawFrame()

3)、drawFrame 中,build 階段根據 widget 生成 element 和 renderObject 樹;layout 測量繪制元素的大小和位置;paint 階段生成 layer 樹;

4). 然后Framework層通過Window的render方法回到了Engine層,Engine再向GPU線程提交繪制任務;最終渲染出來

dart請求繪制---->jni----->dart
總結:整個流程由 Dart 發起,通過 C++ 回調到 Native 層,注冊一次垂直同步信號的監聽。等到信號來到,再通知 Dart 進行渲染。可以看出,Flutter 上的渲染,是先由 Dart 側主動發起,而不是被動等待垂直信號的通知。這可以解釋,比如一些靜態頁面時,整個屏幕不會多次渲染。并且由于是 Native 層的垂直同步信號,所以也完全適配高刷的設備

上面整個流程下來就是每一次Vsync信號,在Dart層所做的處理。

核心就是執行drawFrame方法。而drawFrame方法主要有三個步驟:
drawFrame() 總結:
1).遍歷需要layout的RenderObject,讓它執行performLayout方法;整個調用棧是:RenderView.performLayout->child.layout->child.performLayout->child.layout->…;
2). 遍歷需要paint的RenderObject,讓它執行paint方法;整個調用棧是:RenderView.paint->PaintingContext.paintChild->child.paint->PaintingContext.paintChild->…;
3). 通過PaintingContext.canvas可以把RenderObject繪制的內容繪制到PaintingContext._currentLayer上,最終構造出Scene實例,通過Window.render方法把Scene發送給Engine層,最終由Engine將內容渲染在設備屏幕上。
————————————————

渲染管道7個步驟:

flutter完整的渲染管道涉及到很多步驟:

1). 準備前3個子步驟: 首先得到用戶的輸入,例如觸摸屏幕事件,一些動畫可能會隨之產生,然后開始構建組件并去渲染它們;
Build:還記得一開始 setState() 將 element 加入了臟集合么?這個階段,Flutter 會通過 widget 更新所有臟集合中的節點(需要更新)中的 element 與 RenderObject 樹。
2). 渲染可以細分為3個子步驟;
2.1. Layout(布局),它的作用是在屏幕上確定每個組件的大小和位置;
2.2. Paint(繪制),它提供一系列方法把組件渲染成用戶看到的樣子;
2.3. Composite(圖層合成)它把繪制步驟生成的圖層或者紋理堆疊在一起,按照順序組織它們,以便它們可以高效的在屏幕上進行呈現,圖層合成是組件最終呈現在屏幕上之前很關鍵的也是最后的一個優化步驟;
3). 最后是光柵化,它把抽象的表現映射成物理像素顯示在屏幕上。Paint 階段會觸發 RenderObject 對象繪制,生成第四棵樹:Layer Tree,最終合成光柵化后完成渲染。

blog7部.jpg

合成

所有的圖層根據大小、層級、透明度等規則計算出最終的顯示效果,將相同的圖層歸類合并,簡化渲染樹,提高渲染效率

渲染

合并完成后,Flutter會將幾何圖層數據交由Skia引擎加工成二維圖像數據,最終交由GPU進行渲染,完成界面的展示

  • Build:還記得一開始 setState() 將 element 加入了臟集合么

  • Layout:RenderObject 樹進行布局測量,用于確定每一個展示元素的大小和位置。

  • Paint:Paint 階段會觸發 RenderObject 對象繪制,生成第四棵樹:Layer Tree,最終合成光柵化后完成渲染。

最詳細的: dart-->native ----> 屏幕顯示的完整過程

完整的sync圖片.jpg

1). Flutter 引擎啟動時,向系統的 Choreographer 實例注冊接收 Vsync 的回調。
2). 平臺發出 Vsync 信號后,上一步注冊的回調被調用,一系列調用后,執行到 VsyncWaiter::fireCallback。

  1. .VsyncWaiter::fireCallback實際上會執行Animator類的成員函數BeginFrame。
    4). BeginFrame 經過一系列調用執行到 Window 的 BeginFrame,Window 實例是連接底層 Engine 和 Dart framework 的重要橋梁,基本上所以跟平臺相關的操作都會由 Window 實例來串聯,包括事件,渲染,無障礙等。
    5). 通過 Window 的 BeginFrame 調用到 Dart Framework的RenderBinding 類,其有一個方法叫 drawFrame ,這個方法會去驅動 UI 上的 dirty 節點進行重排和繪制,如果遇到圖片的顯示,會丟到 IO 線程以及去 worker 線程去執行圖片加載和解碼,解碼完成后,再次丟到 IO 線程去生成圖片紋理,由于 IO 線程和 GPU 線程是 share GL context 的,所以在 IO 線程生成的圖片紋理在 GPU 線程可以直接被 GPU 所處理和顯示。
    6). Dart 層繪制所產生的繪制指令以及相關的渲染屬性配置都會存儲在 LayerTree 中,通過 Animator::RenderFrame 把 LayerTree 提交到 GPU 線程,GPU 線程拿到 LayerTree 后,進行光柵化并做上屏操作(關于LayerTree我們后面會詳細講解)。之后通過 Animator::RequestFrame 請求接收系統下一次的Vsync信號,這樣又會從第1步開始,循環往復,驅動 UI 界面不斷的更新。

4.渲染三部曲:

RenderObject 繪制3部曲

4.1 Layout:
measure : 沒有看到測量, 這個和layout在一起

Flutter采用深度優先機制遍歷渲染對象樹,決定渲染對象樹中各渲染對象在屏幕上的位置和尺寸。在布局過程中,渲染對象樹中的每個渲染對象都會接收父對象的布局約束參數,決定自己的大小,然后父對象按照控件邏輯決定各個子對象的位置,完成布局過程

  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    assert(!_debugDisposed);
    if (!kReleaseMode && debugProfileLayoutsEnabled) {
      Map<String, String>? debugTimelineArguments;
      assert(() {
        if (debugEnhanceLayoutTimelineArguments) {
          debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
        }
        return true;
      }());
      FlutterTimeline.startSync(
        '$runtimeType',
        arguments: debugTimelineArguments,
      );
    }
    assert(constraints.debugAssertIsValid(
      isAppliedConstraint: true,
      informationCollector: () {
        final List<String> stack = StackTrace.current.toString().split('\n');
        int? targetFrame;
        final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +Render(?:Object|Box).layout \(');
        for (int i = 0; i < stack.length; i += 1) {
          if (layoutFramePattern.matchAsPrefix(stack[i]) != null) {
            targetFrame = i + 1;
          } else if (targetFrame != null) {
            break;
          }
        }
        if (targetFrame != null && targetFrame < stack.length) {
          final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$');
          final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]);
          final String? problemFunction = (targetFrameMatch != null && targetFrameMatch.groupCount > 0) ? targetFrameMatch.group(1) : stack[targetFrame].trim();
          return <DiagnosticsNode>[
            ErrorDescription(
              "These invalid constraints were provided to $runtimeType's layout() "
              'function by the following function, which probably computed the '
              'invalid constraints in question:\n'
              '  $problemFunction',
            ),
          ];
        }
        return <DiagnosticsNode>[];
      },
    ));
    assert(!_debugDoingThisResize);
    assert(!_debugDoingThisLayout);
    final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject;
    final RenderObject relayoutBoundary = isRelayoutBoundary ? this : parent!._relayoutBoundary!;
    assert(() {
      _debugCanParentUseSize = parentUsesSize;
      return true;
    }());

    if (!_needsLayout && constraints == _constraints) {
      assert(() {
        // in case parentUsesSize changed since the last invocation, set size
        // to itself, so it has the right internal debug values.
        _debugDoingThisResize = sizedByParent;
        _debugDoingThisLayout = !sizedByParent;
        final RenderObject? debugPreviousActiveLayout = _debugActiveLayout;
        _debugActiveLayout = this;
        debugResetSize();
        _debugActiveLayout = debugPreviousActiveLayout;
        _debugDoingThisLayout = false;
        _debugDoingThisResize = false;
        return true;
      }());

      if (relayoutBoundary != _relayoutBoundary) {
        _relayoutBoundary = relayoutBoundary;
        visitChildren(_propagateRelayoutBoundaryToChild);
      }

      if (!kReleaseMode && debugProfileLayoutsEnabled) {
        FlutterTimeline.finishSync();
      }
      return;
    }
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      // The local relayout boundary has changed, must notify children in case
      // they also need updating. Otherwise, they will be confused about what
      // their actual relayout boundary is later.
      visitChildren(_cleanChildRelayoutBoundary);
    }
    _relayoutBoundary = relayoutBoundary;
    assert(!_debugMutationsLocked);
    assert(!_doingThisLayoutWithCallback);
    assert(() {
      _debugMutationsLocked = true;
      if (debugPrintLayouts) {
        debugPrint('Laying out (${sizedByParent ? "with separate resize" : "with resize allowed"}) $this');
      }
      return true;
    }());
    if (sizedByParent) {
      assert(() {
        _debugDoingThisResize = true;
        return true;
      }());
      try {
        performResize();
        assert(() {
          debugAssertDoesMeetConstraints();
          return true;
        }());
      } catch (e, stack) {
        _reportException('performResize', e, stack);
      }
      assert(() {
        _debugDoingThisResize = false;
        return true;
      }());
    }
    RenderObject? debugPreviousActiveLayout;
    assert(() {
      _debugDoingThisLayout = true;
      debugPreviousActiveLayout = _debugActiveLayout;
      _debugActiveLayout = this;
      return true;
    }());
    try {
      performLayout();
      markNeedsSemanticsUpdate();
      assert(() {
        debugAssertDoesMeetConstraints();
        return true;
      }());
    } catch (e, stack) {
      _reportException('performLayout', e, stack);
    }
    assert(() {
      _debugActiveLayout = debugPreviousActiveLayout;
      _debugDoingThisLayout = false;
      _debugMutationsLocked = false;
      return true;
    }());
    _needsLayout = false;
    markNeedsPaint();

    if (!kReleaseMode && debugProfileLayoutsEnabled) {
      FlutterTimeline.finishSync();
    }
  }

4.2 繪制paint:
void paint(PaintingContext context, Offset offset) { }

布局完成后,渲染對象樹中的每個節點都有了明確的尺寸和位置。Flutter會把所有的渲染對象繪制到不同的圖層上。
與布局過程一樣,繪制過程也是深度優先遍歷,而且總是先繪制自身,再繪制子節點。
Flutter提出了與布局邊界對應的機制——重繪邊界(Repaint Boundary)。在重繪邊界內,Flutter會強制切換新的圖層,這樣就可以避免邊界內外的互相影響,避免無關內容置于同一圖層引起不必要的重繪
重繪邊界的一個典型場景是Scrollview。ScrollView滾動的時候需要刷新視圖內容,從而觸發內容重繪。而當滾動內容重繪時,一般情況下其他內容是不需要重繪的,這時候重繪邊界就派上用場了

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容