Flutter学习之视图体系

2019-04-01 16:48:34 浏览数 (1)

image.png

一、前言

经过之前的学习,可以知道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的定义如下:

widgetelement(下面再描述)提供配置信息,这里可以知道widgetelement存在某种联系。Widgets在Flutter framework是中心类层次结构,widget是不可变的对象并且是界面的一部分,widget会被渲染在elements上,并(elelments)管理底层渲染树(render tree),这里可以得到一个信息:widget在渲染的时候会最终转换成element。继续往下看:

Wigets本身是没有可变的状态(其所有的字段必须是final)。如果你想吧可变状态和一个widget关联起来,可以使用StatefulWidgetStatefulWidget通过使用StatefulWidget.createState方法创建State对象,并且扩充到element和合并到树中。那么这段可以得出的信息是:widget并不会直接渲染和管理状态,管理状态是交给State对象负责。

给定的widget可以零次或者多次被包含在树中,一个给定的widget可以多次放置在树中,每次将一个widget放入树中,他都会被扩充到一个Element,这就意味着多次并入树中的widget将会多次扩充到对应的Element。这段可以这么理解:在一个界面中,有多个Text被挂载在视图树上,这些Textwidget会被填充进自己独立的Element中,就算widget被重复使用,还是会创建多个不同的element对象。继续往下看:

每一个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源码:

代码语言:javascript复制
@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方法,简单看一下:

代码语言:javascript复制
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就是员工,真正干活。

widget描述如何配置子树,由于widgets是不可变的,所以可以用相同的widget来同时配置多个子树,Element表示widget配置树中的特定位置的实例,随着时间的推移,和给定的Element关联的Widget可能会随时变化,例如,如果父widget重建并为此位置创建新的widget

Elements构成一棵树,大多数elements都会有唯一的孩子,但是一些widgets(如RenderObjectElement)可以有多个孩子。

** 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:

代码语言:javascript复制
new Text("hello flutter");

下面看下new Text的源码:

代码语言:javascript复制
  ...
  @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里面看:

代码语言:javascript复制
  ....
  @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,继续往里看:

代码语言:javascript复制
class RichText extends LeafRenderObjectWidget {
}

可以发现RichText继承LeafRenderObjectWidget,继续往下看:

代码语言:javascript复制
//用于配置RenderObject子类的RenderObjectWidgets的超类,没有孩子,也就是没有字节点(child)
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
  const LeafRenderObjectWidget({ Key key }) : super(key: key);

  //构建出类型是LeafRenderObjectElement
  @override
  LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}

可以看到LeafRenderObjectWidget是抽象类,并且继承了RenderObjectWidget,继续往下看:

代码语言:javascript复制
/// 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继承关系:

image.png

看看文档的一一介绍:

3.1.SingleChildRenderObjectWidget

image.png

官方文档写的很清楚: 是RenderObjectWidgets的超类,用于配置有单个孩子的RenderObject子类(为子类提供存储,实际不提供更新逻辑),并列了具体的实际widget,如常用的Align、ClipRect、DecoratedBox都是属于这类。

3.2.MultiChildRenderObjectWidget

image.png

同样也是RenderObjectWidgets的超类,用于配置有单个children(也就是多个child)的RenderObject子类,如Flex、Flow、Stack都属于这类。

3.3.LeafRenderObjectWidget

image.png

同样也是RenderObjectWidgets的超类,用于配置没有孩子的RenderObject子类,如:RichText(平时的Text)、RawImage、Texture等。 这里总结一下:

  • SingleChildRenderObjectWidget用作只有一个child的widget
  • MultiChildRenderObjectWidget用作有children(多个孩子)的widget
  • LeafRenderObjectWidget用作没有child的widget
  • RenderObjectWidget定义了创建,更新,删除RenderObject的方法,子类必须实现,RenderObject是最终布局、UI渲染的实际对象。

那么假如我现在界面上,假如布局如下:

image.png

那么渲染流程如下:

image.png

这时候会想:为什么要加中间层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

代码语言:javascript复制
//app入口
void main() => runApp(MyApp());

app入口函数就是调用了runApp方法,看看runApp方法里面做了什么:

代码语言:javascript复制
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

首先参数(Widget app)是一个widget,下面一行一行分析,进入WidgetsFlutterBinding.ensureInitialized()方法:

代码语言:javascript复制
/// 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:

代码语言:javascript复制
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中的作用看看官方对它的定义:

image.png

意思是:链接宿主操作系统的接口,也就是Flutter framework 链接宿主操作系统的接口。系统中有一个Window实例,可以从window属性来获取,看看源码:

代码语言:javascript复制
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挂载。点进去看:

代码语言:javascript复制
  //如果这个widget有必要创建,就将它附加到[renderViewElement]
  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement);
  }

renderView是UI渲染树的根节点:

代码语言:javascript复制
  // The render tree that's attached to the output surface.
  //挂载在渲染树 rootNode根节点
  RenderView get renderView => _pipelineOwner.rootNode;

rootWidget是外面所传进来的根Widget,这里不用管它,下面看attachToRenderTree(buildOwner, renderViewElement)这个方法,根据意思,这个方法应该构建RenderTree,这个方法需要两个参数,首先看buildOwner这个参数:

代码语言:javascript复制
  /// 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();

看官网对它的定义:

image.png

意思是:是widget framework的管理类,用来跟踪哪些widget需要重建,并处理widget树的其他任务,例如管理树的非活动元素列表,并在调试时在热重载期间在必要时触发“重组”命令,下面看另外一个参数renderViewElement,代码注释如下:

代码语言:javascript复制
  /// 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的源码看:

代码语言:javascript复制
  /// 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方法:

代码语言:javascript复制
  /// 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
代码语言:javascript复制
  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
代码语言:javascript复制
  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.渲染绘制

代码语言:javascript复制
  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添加了回调:

代码语言:javascript复制
  void addPersistentFrameCallback(FrameCallback callback) {
    _persistentCallbacks.add(callback);
  }

再看_handlePersistentFrameCallback这个回调做了什么:

代码语言:javascript复制
  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }
代码语言:javascript复制
  @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
代码语言:javascript复制
  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
代码语言:javascript复制
  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
代码语言:javascript复制
  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
代码语言:javascript复制
  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,回到最开始来看:

代码语言:javascript复制
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方法:

代码语言:javascript复制
  @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把图像画在屏幕上。
【附】相关架构及资料

加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

image

0 人点赞