Flutter學習之檢視體系

真丶深紅騎士發表於2019-03-29

一、前言

經過之前的學習,可以知道Flutter是一種全新的響應式跨平臺的移動開發框架,越來越多的開發者參與學習或者研究中,在iOSAndroid平臺上能夠用一套程式碼構建出效能比較高的應用程式。我剛開始接觸FlutterFlutter中文網看到這麼一句話:WidgetFlutter應用程式使用者介面的基本構建塊。每個Widget都是使用者介面一部分的不可變宣告。與其他將試圖、控制器、佈局和其他屬性分離的框架不同,Flutter具有一致的統一物件模型:Widget。在開發過程中也可以知道Widget可以被定義按鈕(button)、樣式(style)、填充(Padding)、佈局(Row)、手勢(GestureDetector)等,我剛開始以為這個Widget就是眼中所看到的檢視,然而並不是這樣的,下面慢慢講述。

二、檢視基礎

1.Widget

Flutter官方網站介紹Widgets開篇有這麼一段話:

Flutter widgets are built using a modern react-style framework, which takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

這段話的意思是:Flutter widgets是採取React思想使用響應式框架構建的。核心思想就是使用widgets構建出UI(介面)。Widgets根據其當前配置和狀態描述了它們的檢視。當某個widget的狀態發生更改時,widget會重新構建所描述的檢視,framework會根據前面所描述的檢視(狀態沒改變時)進行區分,以確定底層呈現樹從一個狀態轉換到下一個狀態所需的最小更改步驟。

Flutter開發者文件Widget的定義如下:

Describes the configuration for an Element.

Widgets are the central class hierarchy in the Flutter framework. A widget is an immutable description of part of a user interface. Widgets can be inflated into elements, which manage the underlying render tree.

意思是:widgetelement(下面再描述)提供配置資訊,這裡可以知道widgetelement存在某種聯絡。Widgets在Flutter framework是中心類層次結構,widget是不可變的物件並且是介面的一部分,widget會被渲染在elements上,並(elelments)管理底層渲染樹(render tree),這裡可以得到一個資訊:widget在渲染的時候會最終轉換成element。繼續往下看:

Widgets themselves have no mutable state (all their fields must be final). If you wish to associate mutable state with a widget, consider using a StatefulWidget, which creates a State object (via StatefulWidget.createState) whenever it is inflated into an element and incorporated into the tree.

意思是:Wigets本身是沒有可變的狀態(其所有的欄位必須是final)。如果你想吧可變狀態和一個widget關聯起來,可以使用StatefulWidgetStatefulWidget通過使用StatefulWidget.createState方法建立State物件,並且擴充到element和合併到樹中。那麼這段可以得出的資訊是:widget並不會直接渲染和管理狀態,管理狀態是交給State物件負責。繼續往下一段看:

A given widget can be included in the tree zero or more times. In particular a given widget can be placed in the tree multiple times. Each time a widget is placed in the tree, it is inflated into an Element, which means a widget that is incorporated into the tree multiple times will be inflated multiple times.

意思是:給定的widget可以零次或者多次被包含在樹中,一個給定的widget可以多次放置在樹中,每次將一個widget放入樹中,他都會被擴充到一個Element,這就意味著多次併入樹中的widget將會多次擴充到對應的Element。這段可以這麼理解:在一個介面中,有多個Text被掛載在檢視樹上,這些Textwidget會被填充進自己獨立的Element中,就算widget被重複使用,還是會建立多個不同的element物件。繼續往下看:

The key property controls how one widget replaces another widget in the tree. If the runtimeType and key properties of the two widgets are operator==, respectively, then the new widget replaces the old widget by updating the underlying element (i.e., by calling Element.update with the new widget). Otherwise, the old element is removed from the tree, the new widget is inflated into an element, and the new element is inserted into the tree.

每一個widget都有自己的唯一的key,這裡也很容易理解,就是藉助key作為唯一識別符號。這段話的意思是:key這個屬性控制一個widget如何替換樹中的另一個widget。如果兩個widgetruntimeTypekey屬性相等==,則新的widget通過更新Element(通過新的widget來來呼叫Element.update)來替換舊的widget。否則,如果兩個widgetruntimeTypekey屬性不相等,則舊的Element將從樹中被移除,新的widget將被擴充到一個新的Element中,這個新的Element將被插入樹中。這裡可以得出:如果涉及到widget的移動或者刪除操作前,會根據widgetruntimekey進行對比。

綜上所述:

  • widget向Element提供配置資訊(資料),介面構造出來的widget樹其實只是一顆配置資訊樹,為了構造出Element樹。
  • Element和widget有對應關係,因為,element是通過widget來生成的。
  • 同一個widget可以建立多個element,也就是一個widget物件可以對應多個element物件

下面初步看看widget原始碼:

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  //省略註釋
  final Key key;

  //構建出element
  @protected
  Element createElement();

  //簡短文字描述這個widget
  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }
  
  //根據字面意思 應該是除錯診斷樹的資訊
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }


  //靜態方法,跟上一段解釋一樣,就是是否用新的widget物件去更新舊UI渲染樹的配置
  //如果oldWidget和newWidget的runtimeType和key同時相等就會用newWidget物件去更新對應element資訊
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
複製程式碼

還要注意:widget是抽象類,在平時,一般繼續StatelessWidgetStatefulWidget,而這兩個類其實也是繼承Widget,這兩個類肯定會實現這個createElement方法,簡單看一下:

StatelessWidget
abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key key }) : super(key: key);
  ....
  @override
  StatelessElement createElement() => StatelessElement(this);
  ....
}

StatefulWidget
abstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key key }) : super(key: key);
  .....
  @override
  StatefulElement createElement() => StatefulElement(this);
  .....
    
}
複製程式碼

確實可以看出,都會建立Element,只是StatelessWidgetStatefulWidget所建立的Element型別不一樣,這裡就先不深入了。上面可以知道widgetelement存在對應的關係,那下面看看element

2.Element

看看官方開發者文件中開篇看到:

An instantiation of a Widget at a particular location in the tree.

意思是:element是樹中特定位置的widget例項。這裡描述的很明顯,也就是Widget是總監,部署技術規劃,而element就是員工,真正幹活。繼續往下閱讀:

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An Element represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location.

意思是:widget描述如何配置子樹,由於widgets是不可變的,所以可以用相同的widget來同時配置多個子樹,Element表示widget配置樹中的特定位置的例項,隨著時間的推移,和給定的Element關聯的Widget可能會隨時變化,例如,如果父widget重建併為此位置建立新的widget

Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of RenderObjectElement) can have multiple children.

Elements構成一棵樹,大多數elements都會有唯一的孩子,但是一些widgets(如RenderObjectElement)可以有多個孩子。

Elements have the following lifecycle:

  • The framework creates an element by calling Widget.createElement on the widget that will be used as the element's initial configuration.
  • The framework calls mount to add the newly created element to the tree at a given slot in a given parent. The mount method is responsible for inflating any child widgets and calling attachRenderObject as necessary to attach any associated render objects to the render tree.
  • At this point, the element is considered "active" and might appear on screen.
  • At some point, the parent might decide to change the widget used to configure this element, for example because the parent rebuilt with new state. When this happens, the framework will call update with the new widget. The new widget will always have the same runtimeType and key as old widget. If the parent wishes to change the runtimeType or key of the widget at this location in the tree, can do so by unmounting this element and inflating the new widget at this location.
  • At some point, an ancestor might decide to remove this element (or an intermediate ancestor) from the tree, which the ancestor does by calling deactivateChild on itself. Deactivating the intermediate ancestor will remove that element's render object from the render tree and add this element to the owner's list of inactive elements, causing the framework to call deactivate on this element.
  • At this point, the element is considered "inactive" and will not appear on screen. An element can remain in the inactive state only until the end of the current animation frame. At the end of the animation frame, any elements that are still inactive will be unmounted. *If the element gets reincorporated into the tree (e.g., because it or one of its ancestors has a global key that is reused), the framework will remove the element from the owner's list of inactive elements, call activate on the element, and reattach the element's render object to the render tree. (At this point, the element is again considered "active" and might appear on screen.)
  • If the element does not get reincorporated into the tree by the end of the current animation frame, the framework will call unmount on the element. At this point, the element is considered "defunct" and will not be incorporated into the tree in the future.
  • At this point, the element is considered "defunct" and will not be incorporated into the tree in the future.

意思如下: Element具有以下生命週期:

  • framework通過呼叫即將用來作element的初始化配置資訊的WidgetWidget.createElement方法來建立一個element
  • framework通過呼叫mount方法以將新建立的Element新增到給定父級中給定槽點的樹上。 mount方法負責將任何子Widget擴充到Widget並根據需要呼叫attachRenderObject,以將任何關聯的渲染物件附加到渲染樹上。
  • 此時,element被視為啟用,可能出現在螢幕上。
  • 在某些情況下,父可能會更改用於配置此Element的Widget,例如因為父重新建立了新狀態。發生這種情況時,framework將呼叫新的Widget的update方法。新Widget將始終具有與舊Widget相同的runtimeTypekey屬性。如果父希望在樹中的此位置更改WidgetruntimeTypekey,可以通過unmounting(解除安裝)此Element並在此位置擴充新Widget來實現。
  • 在某些時候,祖先(Element)可能會決定從樹中移除該element(或中間祖先),祖先自己通過呼叫deactivateChild來完成該操作。停用中間祖先將從渲染樹中移除該element的渲染物件,並將此element新增到所有者屬性中的非活動元素列表中,從而framework呼叫deactivate方法作用在此element上。
  • 此時,該element被視為“無效”,不會出現在螢幕上。一個element直到動畫幀結束前都可以儲存“非活動”狀態。動畫幀結束時,將解除安裝仍處於非活動狀態的所有element
  • 如果element被重寫組合到樹中(例如,因為它或其祖先之一有一個全域性建(global key)被重用),framework將從所有者的非活動elements列表中移除該element,並呼叫該elementactivate方法,並重新附加到element的渲染物件到渲染樹上。(此時,該元素再次被視為“活動”並可能出現在螢幕上)
  • 如果element在當前動畫幀的末尾(最後一幀)沒有被重新組合到樹中,那麼framework將會呼叫該元素的unmount方法

這裡可以知道element的生命週期。並且平時開發沒有接觸到Element,都是直接操控widget,也就是說Flutter已經幫我們對widget的操作對映到element上,我這裡想象到的有點事降低開發複雜。下面結合一個例子(繪製Text),看看element是不是最後渲染出來的view:

new Text("hello flutter");
複製程式碼

下面看下new Text的原始碼:

  ...
  @override
  Widget build(BuildContext context) {
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
    TextStyle effectiveTextStyle = style;
    if (style == null || style.inherit)
      effectiveTextStyle = defaultTextStyle.style.merge(style);
    if (MediaQuery.boldTextOverride(context))
      effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
    Widget result = RichText( ---->Text原來是通過RichText這個widget來構建樹
      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
      textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
      locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
      softWrap: softWrap ?? defaultTextStyle.softWrap,
      overflow: overflow ?? defaultTextStyle.overflow,
      textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
      maxLines: maxLines ?? defaultTextStyle.maxLines,
      strutStyle: strutStyle,
      text: TextSpan(
        style: effectiveTextStyle,
        text: data,
        children: textSpan != null ? <TextSpan>[textSpan] : null,
      ),
    );
    ....
複製程式碼

繼續往RichText裡面看:

  ....
  @override
  RenderParagraph createRenderObject(BuildContext context) {
    assert(textDirection != null || debugCheckHasDirectionality(context));
    //返回RenderParagraph widget 
    return RenderParagraph(text,
      textAlign: textAlign,
      textDirection: textDirection ?? Directionality.of(context),
      softWrap: softWrap,
      overflow: overflow,
      textScaleFactor: textScaleFactor,
      maxLines: maxLines,
      strutStyle: strutStyle,
      locale: locale ?? Localizations.localeOf(context, nullOk: true),
    );
  }
  ....
複製程式碼

發現最終它會返回RenderParagraphwidget,繼續往裡看:

class RichText extends LeafRenderObjectWidget {
}
複製程式碼

可以發現RichText繼承LeafRenderObjectWidget,繼續往下看:

//用於配置RenderObject子類的RenderObjectWidgets的超類,沒有孩子,也就是沒有位元組點(child)
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
  const LeafRenderObjectWidget({ Key key }) : super(key: key);
  
  //構建出型別是LeafRenderObjectElement
  @override
  LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}
複製程式碼

可以看到LeafRenderObjectWidget是抽象類,並且繼承了RenderObjectWidget,繼續往下看:

/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
//上面註釋是:RenderObjectWidgets向[RenderObjectElement]提供了配置資訊,包裝了[RenderObject],在應用程式了提供了實際的渲染
abstract class RenderObjectWidget extends Widget {

  const RenderObjectWidget({ Key key }) : super(key: key);
  
  //建立element
  @override
  RenderObjectElement createElement();
  //建立RenderObject
  @protected
  RenderObject createRenderObject(BuildContext context);

  //更新RenderObject
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  
  //解除安裝RenderObject
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製程式碼

由註釋可以知道RenderObject才是繪製UI背後的真正物件,那下面繼續簡單跟蹤:

3.RenderObjectWidget

先看看RenderObjectWidget繼承關係:

RenderObjectWidget繼承關係
看看文件的一一介紹:

3.1.SingleChildRenderObjectWidget

SingleChildRenderObjectWidget
官方文件寫的很清楚: 是RenderObjectWidgets的超類,用於配置有單個孩子的RenderObject子類(為子類提供儲存,實際不提供更新邏輯),並列了具體的實際widget,如常用的Align、ClipRect、DecoratedBox都是屬於這類。

3.2.MultiChildRenderObjectWidget

MultiChildRenderObjectWidget
同樣也是RenderObjectWidgets的超類,用於配置有單個children(也就是多個child)的RenderObject子類,如Flex、Flow、Stack都屬於這類。

3.3.LeafRenderObjectWidget

LeafRenderObjectWidget
同樣也是RenderObjectWidgets的超類,用於配置沒有孩子的RenderObject子類,如:RichText(平時的Text)、RawImage、Texture等。 這裡總結一下:

  • SingleChildRenderObjectWidget用作只有一個child的widget
  • MultiChildRenderObjectWidget用作有children(多個孩子)的widget
  • LeafRenderObjectWidget用作沒有child的widget
  • RenderObjectWidget定義了建立,更新,刪除RenderObject的方法,子類必須實現,RenderObject是最終佈局、UI渲染的實際物件。

那麼假如我現在介面上,假如佈局如下:

例子佈局
那麼渲染流程如下:

渲染轉換
這時候會想:為什麼要加中間層Element呢,不直接由Widget直接轉換成RendObject不是直接更好麼?其實並不是這樣的。首先知道Flutter是響應式框架,在某一個時刻,可能會受到不同的輸入流影響,中間層Element對這一時刻的事件做了彙總,最後將需要修改的部分同步到RendObjecttree上,也就是:

  • 儘可能的降低RenderObjecttree的更改,提高頁面渲染效率。
  • 沒有直接操作UI,通過資料驅動檢視,程式碼更容易理解和簡潔。

上面得出UI樹是由一個個element節點組成,但是最終的渲染是通過RenderObject來完成,一個widget從建立到顯示到介面的流程是:widget生成element,然後通過createRenderObject方法建立對應的RenderObject關聯到Element.renderObject上,最後通過RenderObject來完成繪製。

三、啟動到顯示

上面知道,widget並不是真正顯示的物件,知道了一個widget是怎樣渲染在螢幕上顯示,那下面就從main()方法入口,看看一個app從點選啟動到執行的流程:

1.WidgetsFlutterBinding.ensureInitialized

//app入口
void main() => runApp(MyApp());
複製程式碼

app入口函式就是呼叫了runApp方法,看看runApp方法裡面做了什麼:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

首先引數(Widget app)是一個widget,下面一行一行分析,進入**WidgetsFlutterBinding.ensureInitialized()**方法:

/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
//意思:基於widget framework的應用程式的具體繫結
//這是將framework widget和Flutter engine繫結的橋樑
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  //上面註釋意思是:返回[WidgetsBinding]的具體例項,必須建立和初始化,如果已//經建立了,那麼它就是一個[WidgetsFlutterBinding],如果已經初始化了,那麼//至少要實現[WidgetsBinding]
  //如果你只想呼叫這個方法,那麼你需要在呼叫runApp之前繫結並且初始化
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}
複製程式碼

看到這個WidgetsFlutterBinding混入(with)很多的Binding,下面先看父類BindingBase:

abstract class BindingBase {
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');

    assert(!_debugInitialized);
    initInstances();
    assert(_debugInitialized);

    assert(!_debugServiceExtensionsRegistered);
    initServiceExtensions();
    assert(_debugServiceExtensionsRegistered);

    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});

    developer.Timeline.finishSync();
  }

  static bool _debugInitialized = false;
  static bool _debugServiceExtensionsRegistered = false;
  ui.Window get window => ui.window;//獲取window例項
  @protected
  @mustCallSuper
  void initInstances() {
    assert(!_debugInitialized);
    assert(() { _debugInitialized = true; return true; }());
  }
}
複製程式碼

看到有句程式碼ui.Window get window => ui.window;,在Android中所有的檢視都是通過window來呈現的,那Flutter中也有window,那看看window在Flutter中的作用看看官方對它的定義:

window定義
意思是:連結宿主作業系統的介面,也就是Flutter framework 連結宿主作業系統的介面。系統中有一個Window例項,可以從window屬性來獲取,看看原始碼:

class Window {
  Window._();

  //返回DPI,DPI是每英寸的畫素點數,是裝置螢幕的韌體屬性
  //獲取可能不準確
  double get devicePixelRatio => _devicePixelRatio;
  double _devicePixelRatio = 1.0;

  //繪製UI的區域大小
  Size get physicalSize => _physicalSize;
  Size _physicalSize = Size.zero;

  //獲取矩形的物理畫素
  WindowPadding get viewInsets => _viewInsets;
  WindowPadding _viewInsets = WindowPadding.zero;

  //獲取內邊距
  WindowPadding get padding => _padding;
  WindowPadding _padding = WindowPadding.zero;

  //當繪製區域改變時觸發
  VoidCallback get onMetricsChanged => _onMetricsChanged;
  VoidCallback _onMetricsChanged;
  Zone _onMetricsChangedZone;
  set onMetricsChanged(VoidCallback callback) {
    _onMetricsChanged = callback;
    _onMetricsChangedZone = Zone.current;
  }

  //當系統語言發生變化時觸發回撥
  Locale get locale {
    if (_locales != null && _locales.isNotEmpty) {
      return _locales.first;
    }
    return null;
  }

  //獲取系統語言
  List<Locale> get locales => _locales;
  List<Locale> _locales;

  //當Local發生改變時觸發回撥
  VoidCallback get onLocaleChanged => _onLocaleChanged;
  VoidCallback _onLocaleChanged;
  Zone _onLocaleChangedZone;
  set onLocaleChanged(VoidCallback callback) {
    _onLocaleChanged = callback;
    _onLocaleChangedZone = Zone.current;
  }

  //當系統字型大小改變時觸發回撥
  VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  VoidCallback _onTextScaleFactorChanged;
  Zone _onTextScaleFactorChangedZone;
  set onTextScaleFactorChanged(VoidCallback callback) {
    _onTextScaleFactorChanged = callback;
    _onTextScaleFactorChangedZone = Zone.current;
  }

  //螢幕亮度改變時觸發回撥
  VoidCallback get onPlatformBrightnessChanged => _onPlatformBrightnessChanged;
  VoidCallback _onPlatformBrightnessChanged;
  Zone _onPlatformBrightnessChangedZone;
  set onPlatformBrightnessChanged(VoidCallback callback) {
    _onPlatformBrightnessChanged = callback;
    _onPlatformBrightnessChangedZone = Zone.current;
  }

  //螢幕重新整理時會回撥
  FrameCallback get onBeginFrame => _onBeginFrame;
  FrameCallback _onBeginFrame;
  Zone _onBeginFrameZone;
  set onBeginFrame(FrameCallback callback) {
    _onBeginFrame = callback;
    _onBeginFrameZone = Zone.current;
  }

  //繪製螢幕時回撥
  VoidCallback get onDrawFrame => _onDrawFrame;
  VoidCallback _onDrawFrame;
  Zone _onDrawFrameZone;
  set onDrawFrame(VoidCallback callback) {
    _onDrawFrame = callback;
    _onDrawFrameZone = Zone.current;
  }

  //點選或者指標事件觸發回撥
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  PointerDataPacketCallback _onPointerDataPacket;
  Zone _onPointerDataPacketZone;
  set onPointerDataPacket(PointerDataPacketCallback callback) {
    _onPointerDataPacket = callback;
    _onPointerDataPacketZone = Zone.current;
  }

  //獲取請求的預設路由
  String get defaultRouteName => _defaultRouteName();
  String _defaultRouteName() native 'Window_defaultRouteName';

  //該方法被呼叫後,onBeginFrame和onDrawFrame將緊連著會在合適時機呼叫
  void scheduleFrame() native 'Window_scheduleFrame';

  //更新應用在GPU上的渲染,這方法會直接呼叫Flutter engine的Window_render方法
  void render(Scene scene) native 'Window_render';

  //視窗的語義內容是否改變
  bool get semanticsEnabled => _semanticsEnabled;
  bool _semanticsEnabled = false;

  //當視窗語言發生改變時回撥 
  VoidCallback get onSemanticsEnabledChanged => _onSemanticsEnabledChanged;
  VoidCallback _onSemanticsEnabledChanged;
  Zone _onSemanticsEnabledChangedZone;
  set onSemanticsEnabledChanged(VoidCallback callback) {
    _onSemanticsEnabledChanged = callback;
    _onSemanticsEnabledChangedZone = Zone.current;
  }

  //當使用者表達寫的動作時回撥
  SemanticsActionCallback get onSemanticsAction => _onSemanticsAction;
  SemanticsActionCallback _onSemanticsAction;
  Zone _onSemanticsActionZone;
  set onSemanticsAction(SemanticsActionCallback callback) {
    _onSemanticsAction = callback;
    _onSemanticsActionZone = Zone.current;
  }

  //啟用其他輔助功能回撥
  VoidCallback get onAccessibilityFeaturesChanged => _onAccessibilityFeaturesChanged;
  VoidCallback _onAccessibilityFeaturesChanged;
  Zone _onAccessibilityFlagsChangedZone;
  set onAccessibilityFeaturesChanged(VoidCallback callback) {
    _onAccessibilityFeaturesChanged = callback;
    _onAccessibilityFlagsChangedZone = Zone.current;
  }

  //更新此視窗的語義資料
  void updateSemantics(SemanticsUpdate update) native 'Window_updateSemantics';

  //設定Isolate除錯名稱
  void setIsolateDebugName(String name) native 'Window_setIsolateDebugName';

  //向特定平臺傳送訊息
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) {
    final String error =
        _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
    if (error != null)
      throw new Exception(error);
  }
  String _sendPlatformMessage(String name,
                              PlatformMessageResponseCallback callback,
                              ByteData data) native 'Window_sendPlatformMessage';

  //獲取平臺訊息的回撥
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  PlatformMessageCallback _onPlatformMessage;
  Zone _onPlatformMessageZone;
  set onPlatformMessage(PlatformMessageCallback callback) {
    _onPlatformMessage = callback;
    _onPlatformMessageZone = Zone.current;
  }

  //由_dispatchPlatformMessage呼叫
  void _respondToPlatformMessage(int responseId, ByteData data)
      native 'Window_respondToPlatformMessage';

  //平臺資訊響應回撥
  static PlatformMessageResponseCallback _zonedPlatformMessageResponseCallback(PlatformMessageResponseCallback callback) {
    if (callback == null)
      return null;

    // Store the zone in which the callback is being registered.
    final Zone registrationZone = Zone.current;

    return (ByteData data) {
      registrationZone.runUnaryGuarded(callback, data);
    };
  }
}
複製程式碼

可以知道window包含當前裝置系統的一些資訊和回撥,那麼現在看看混入的各種Binding:

  • GestureBinding:繫結手勢子系統,提供onPointerDataPacket回撥。
  • ServicesBinding:繫結平臺訊息通道,提供onPlatformMessage回撥。
  • SchedulerBinding:繫結繪製排程子系統,提供onBeginFrameonDrawFrame回撥。
  • PaintingBinding:繫結繪製庫,處理影像快取。
  • SemanticsBinding:語義層和flutter engine的橋樑,對輔助功能的底層支援。
  • RendererBinding:是渲染樹和Flutter engine的橋樑,提供onMetricsChangedonTextScaleFactorChanged回撥。
  • WidgetsBinding:是Flutter widget和engine的橋樑,提供onLocaleChangedonBuildSchedule回撥。

也就是WidgetsFlutterBinding.ensureInitialized()這行程式碼看名字是將WidgetsFlutterBinding例項初始化。其實並非那麼簡單,另外獲取一些系統基本資訊和初始化監聽window物件的一些事件,然後將這些事件按照上層Framework模型規則進行包裝、抽象最後分發。簡而言之WidgetsFlutterBindingFlutter engineFramework的橋樑。

2.attachRootWidget(app)

第二行是attachRootWidget(app),看名字意思是將根RootWidget掛載。點進去看:

  //如果這個widget有必要建立,就將它附加到[renderViewElement]
  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement);
  }
複製程式碼

renderView是UI渲染樹的根節點:

  // The render tree that's attached to the output surface.
  //掛載在渲染樹 rootNode根節點
  RenderView get renderView => _pipelineOwner.rootNode;
複製程式碼

rootWidget是外面所傳進來的根Widget,這裡不用管它,下面看attachToRenderTree(buildOwner, renderViewElement)這個方法,根據意思,這個方法應該構建RenderTree,這個方法需要兩個引數,首先看buildOwner這個引數:

  /// The [BuildOwner] in charge of executing the build pipeline for the
  /// widget tree rooted at this binding.
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = BuildOwner();
複製程式碼

看官網對它的定義:

BuildOwner
意思是:是widget framework的管理類,用來跟蹤哪些widget需要重建,並處理widget樹的其他任務,例如管理樹的非活動元素列表,並在除錯時在熱過載期間在必要時觸發“重組”命令,下面看另外一個引數renderViewElement,程式碼註釋如下:

  /// The [Element] that is at the root of the hierarchy (and which wraps the
  /// [RenderView] object at the root of the rendering hierarchy).
  ///
  /// This is initialized the first time [runApp] is called.
  Element get renderViewElement => _renderViewElement;
  Element _renderViewElement;

複製程式碼

renderViewElementrenderView對應的Element物件,因為renderView是樹的根,所以renderViewElement位於層次結構的根部,那下面點選attachToRenderTree的原始碼看:

  /// Inflate this widget and actually set the resulting [RenderObject] as the
  /// child of [container].
  ///
  /// If `element` is null, this function will create a new element. Otherwise,
  /// the given element will have an update scheduled to switch to this widget.
  ///
  /// Used by [runApp] to bootstrap applications.
  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();
        assert(element != null);
        element.assignOwner(owner);
      });
      owner.buildScope(element, () {
        element.mount(null, null);
      });
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }
複製程式碼

跟著程式碼往下走如果根element沒有建立,那麼就呼叫createElement建立根elementelementwidget進行關聯。接著呼叫element.assignOwner(owner),這個方法其實就是設定這個element的跟蹤,最後呼叫owner.buildScope這個方法,這個方法是確定更新widget的範圍。如果element已經建立了,將根element和關聯的widget設為新的,並且重新構建這個element,為了後面的複用。

3.scheduleWarmUpFrame

runApp()方法最後一行執行scheduleWarmUpFrame方法:

  /// Schedule a frame to run as soon as possible, rather than waiting for
  /// the engine to request a frame in response to a system "Vsync" signal.
  ///
  /// This is used during application startup so that the first frame (which is
  /// likely to be quite expensive) gets a few extra milliseconds to run.
  ///
  /// Locks events dispatching until the scheduled frame has completed.
  ///
  /// If a frame has already been scheduled with [scheduleFrame] or
  /// [scheduleForcedFrame], this call may delay that frame.
  ///
  /// If any scheduled frame has already begun or if another
  /// [scheduleWarmUpFrame] was already called, this call will be ignored.
  ///
  /// Prefer [scheduleFrame] to update the display in normal operation.
  void scheduleWarmUpFrame() {
    if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
      return;

    _warmUpFrame = true;
    Timeline.startSync('Warm-up frame');
    final bool hadScheduledFrame = _hasScheduledFrame;
    // We use timers here to ensure that microtasks flush in between.
    Timer.run(() {
      assert(_warmUpFrame);
      handleBeginFrame(null);--->1
    });
    Timer.run(() {
      assert(_warmUpFrame);
      handleDrawFrame(); ---->2
      // We call resetEpoch after this frame so that, in the hot reload case,
      // the very next frame pretends to have occurred immediately after this
      // warm-up frame. The warm-up frame's timestamp will typically be far in
      // the past (the time of the last real frame), so if we didn't reset the
      // epoch we would see a sudden jump from the old time in the warm-up frame
      // to the new time in the "real" frame. The biggest problem with this is
      // that implicit animations end up being triggered at the old time and
      // then skipping every frame and finishing in the new time.
      resetEpoch();
      _warmUpFrame = false;
      if (hadScheduledFrame)
        scheduleFrame();
    });

    // Lock events so touch events etc don't insert themselves until the
    // scheduled frame has finished.
    lockEvents(() async {
      await endOfFrame;
      Timeline.finishSync();
    });
  }
複製程式碼

首先這個方法在SchedulerBinding裡,這兩個方法主要執行了handleBeginFramehandleDrawFrame方法:

3.1.handleBeginFrame

  void handleBeginFrame(Duration rawTimeStamp) {
    ...
    try {
      // TRANSIENT FRAME CALLBACKS
      Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
      _transientCallbacks = <int, _FrameCallbackEntry>{};
      callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
        if (!_removedIds.contains(id))
          _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
      });
      _removedIds.clear();
    } finally {
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }
複製程式碼

可以看到主要是對transientCallbacks佇列操作,這個集合主要是放一些臨時回撥,存放動畫回撥。

3.2.handleDrawFrame

  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      // PERSISTENT FRAME CALLBACKS ----->
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS  ------>
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      Timeline.finishSync(); // end the Frame
      profile(() {
        _profileFrameStopwatch.stop();
        _profileFramePostEvent();
      });
      assert(() {
        if (debugPrintEndFrameBanner)
          debugPrint('▀' * _debugBanner.length);
        _debugBanner = null;
        return true;
      }());
      _currentFrameTimeStamp = null;
    }
  }
複製程式碼

可以看到執行persistentCallbacks佇列,這個佇列用於存放一些持久的回撥,不能再此類回撥中在請求新的繪製幀,持久回撥一經註冊則不能移除。接著執行postFrameCallbacks這個佇列在每一Frame(一次繪製)結束時只會呼叫一次,呼叫後被系統移除。

也就是scheduleWarmUpFrame這個方法安排幀儘快執行,當一次幀繪製結束之前不會響應各種事件,這樣保證繪製過程中不觸發重繪。上面說過:

RendererBinding:是渲染樹和Flutter engine的橋樑,提供onMetricsChanged和onTextScaleFactorChanged回撥

Flutter真正渲染和繪製是在這個繫結裡:

4.渲染繪製

  void initInstances() {
    super.initInstances();
    _instance = this;//初始化
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    //新增設定監聽
    window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    //新增persistentFrameCallback
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    //建立觸控管理
    _mouseTracker = _createMouseTracker();
  }
複製程式碼

addPersistentFrameCallback這個方法主要向persistentFrameCallback新增了回撥:

  void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
  }
複製程式碼

再看_handlePersistentFrameCallback這個回撥做了什麼:

  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }
複製程式碼
  @protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();//更新佈局資訊
    pipelineOwner.flushCompositingBits();//在flushLayout只後呼叫,在flushPaint之前呼叫,更新RenderObject是否需要重繪
    pipelineOwner.flushPaint();//更新繪製RenderObject
    renderView.compositeFrame(); // 傳送bit資料給GPU
    pipelineOwner.flushSemantics(); // 傳送語義資料給作業系統
  }
複製程式碼

下面一個一個方法走:

4.1.flushLayout

  void flushLayout() {
    profile(() {
      Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
    });
    assert(() {
      _debugDoingLayout = true;
      return true;
    }());
    try {
      // TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {
      assert(() {
        _debugDoingLayout = false;
        return true;
      }());
      profile(() {
        Timeline.finishSync();
      });
    }
  }
複製程式碼

看原始碼得知首先獲取哪些標記為RenderObject的佈局資訊,然後通過ode._layoutWithoutResize();重新調整這些RenderObject

4.2.flushCompositingBits

  void flushCompositingBits() {
    profile(() { Timeline.startSync('Compositing bits'); });
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    profile(() { Timeline.finishSync(); });
  }
複製程式碼

檢查RenderObject是否需要重繪,並且通過node._updateCompositingBits();更新_needsCompositing這個屬性,若為true就要重新繪製,否則不需要。

4.3.flushPaint

  void flushPaint() {
    profile(() { Timeline.startSync('Paint', arguments: timelineWhitelistArguments); });
    assert(() {
      _debugDoingPaint = true;
      return true;
    }());
    try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      // Sort the dirty nodes in reverse order (deepest first).
      //方向遍歷這些標記過的node
      for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        assert(node._layer != null);
        if (node._needsPaint && node.owner == this) {
          if (node._layer.attached) {
            //重新繪製
            PaintingContext.repaintCompositedChild(node);
          } else {
            node._skippedPaintingOnLayer();
          }
        }
      }
      assert(_nodesNeedingPaint.isEmpty);
    } finally {
      assert(() {
        _debugDoingPaint = false;
        return true;
      }());
      profile(() { Timeline.finishSync(); });
    }
  }

複製程式碼

這個方法通過反向遍歷(dirty標記)取得需要重繪的RenderObject,最後通過PaintingContext.repaintCompositedChild(node);重繪。

4.4.compositeFrame

  void compositeFrame() {
    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
    try {
      //建立Scene物件
      final ui.SceneBuilder builder = ui.SceneBuilder();
      final ui.Scene scene = layer.buildScene(builder);
      if (automaticSystemUiAdjustment)
        _updateSystemChrome();
      //使用render方法將Scene物件顯示在螢幕上    
      _window.render(scene);//呼叫flutter engine的渲染API
      scene.dispose();
      assert(() {
        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
        return true;
      }());
    } finally {
      Timeline.finishSync();
    }
  }
複製程式碼

Scene是用來儲存渲染後的最終畫素資訊,這個方法將Canvas畫好的Scene物件傳給window.render()方法,該方法會直接將Scene資訊傳送給Flutter engine,最終Flutter engine將影像畫在裝置螢幕上,這樣整個繪製流程就算完了。

注意:RendererBinding只是混入物件,最終混入到WidgetsBinding,回到最開始來看:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}
複製程式碼

所以應該WidgetsBinding來重寫實現drawFrame方法:

  @override
  void drawFrame() {
    assert(!debugBuildingDirtyElements);
    assert(() {
      debugBuildingDirtyElements = true;
      return true;
    }());
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame(); //呼叫Renderbinding的drawFrame方法
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    profile(() {
      if (_needToReportFirstFrame && _reportFirstFrame) {
        developer.Timeline.instantSync('Widgets completed first useful frame');
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
        _needToReportFirstFrame = false;
      }
    });
  }
複製程式碼

四、總結

  • widget的功能是向element提供配置資訊,每一個widget在Flutter裡是一份配置資料,而代表螢幕背後的元素是element,而真正的佈局、渲染是通過RenderObject來完成的,從建立到渲染的主要流程是:widget資訊生成element,建立對應的RenderObject關聯到Element.renderObject屬性上,最後通過RenderObject佈局和繪製。
  • Flutter從啟動到顯示影像在螢幕主要經過:首先監聽處理window物件的事件,將這些事件處理包裝為Framework模型進行分發,通過widget建立element樹,接著通過scheduleWarmUpFrame進行渲染,接著通過Rendererbinding進行佈局,繪製,最後通過呼叫ui.window.render(scene)Scene資訊發給Flutter engine,Flutter engine最後呼叫渲染API把影像畫在螢幕上。

資料參考:

如有錯誤,歡迎指出指正,謝謝~

相關文章