使用发动

Flutter的入口在”lib/main.dart”的main()函数中,它是Dart使用程序的起点。在Flutter使用中,main()函数最简略的完结如下:

void main() => runApp(MyApp());

runApp()完结:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

参数app是一个Widget,它是Flutter使用发动后要展现的第一个组件。而WidgetsFlutterBinding正是绑定widget结构和Flutter引擎的桥梁:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

能够看到WidgetsFlutterBinding承继自BindingBase并混入了很多Binding,介绍Binding之前,先介绍一下Window,官方解说:

The most basic interface to the host operating system’s yser interface

Window正是Flutter Framework连接宿主操作系统的接口。
Window界说:

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;
  // 制作回调  
  VoidCallback get onDrawFrame => _onDrawFrame;
  // 点击或指针事情回调
  PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  // 调度Frame,该办法履行后,onBeginFrame和onDrawFrame将紧接着会在合适机遇被调用,
  // 此办法会直接调用Flutter engine的Window_scheduleFrame办法
  void scheduleFrame() native 'Window_scheduleFrame';
  // 更新使用在GPU上的烘托,此办法会直接调用Flutter engine的Window_render办法
  void render(Scene scene) native 'Window_render';
  // 发送渠道音讯
  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) ;
  // 渠道通道音讯处理回调  
  PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  ... //其他属性及回调
}

window类包括了当时设备和系统的一些信息以及Flutter Engine的一些回调。
WidgetsFlutterBinding混入的各种Binding,经过源码发现这些Binding基本都是监听处理window目标的一些事情,然后将这些事情按照Framework的模型包装、抽象然后分发。WidgetsFlutterBinding正是粘连Flutter engine与上层Framework的胶水。

  • GestureBinding:供给了window.onPointerDataPacket回调,绑定Framework手势子系统,是Framework事情模型与底层事情的绑定入口。
  • ServicesBinding:供给了window.onPlatformMessage回调,用于绑定渠道音讯通道(message channel),首要处理原生和Flutter通信。
  • SchedulerBinding:供给window.onBeginFrame和window.onDrawFrame回调,监听改写事情,绑定Framework制作调度子系统。
  • PaintingBinding:绑定制作库,首要用于处理图片缓存
  • SemanticsBinding:语义化层与Flutter engine的桥梁,首要是辅助功能的底层支持。
  • RenderBinding:供给了window。onMetricsChanged、window.onTextScaleFactorChanged等回调。它是烘托树与Flutter engine的桥梁。
  • WidgetsBinding:供给了window.onLocaleChanged、onBuildScheduled等回调。它是Flutter widget层与engine的桥梁。

WidgetsFlutterBinding.ensureInitialized()担任初始化一个WidgetsBinding的大局单例,紧接着会调用WidgetsBinding的sttachRootWidget办法,该办法担任将WIdget增加到RenderView上:

void attachRootWidget(Widget rootWidget) {
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView, 
    debugShortDescription: '[root]',
    child: rootWidget
  ).attachToRenderTree(buildOwner, renderViewElement);
}

留意代码中有renderView和renerViewElement两个变量,renderView是一个RenderObject,它是烘托树的根,而renderViewElement是renderView对应的Element目标,可见该办法首要完结了根widget到根RenderObject再到根Element的整个相关进程。attachToRenderTree源码:

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,即RenderObjectToWidgetElement,并且将element与widget进行相关,即创立出widget树与对应的element树。假如element现已创立过了,则将根element中相关的widget设为新的,由此能够看出element只会创立一次,后边会进行复用。那么BuildOwner是widget framework的管理类,它盯梢哪些widget需求从头构建。

组件树在构建(build)完毕后,回到runApp的完结中,当调用完attachRootWidget后,终究一行会调用WidgetsFlutterBinding示例的scheduleWarmUpFrame()办法,该办法的完结在SchedulerBinding中,它被调用后会当即进行一次制作,在此次制作完毕前,该办法会确认事情分发,也就是说在本次制作完毕前Flutter将不会呼应各种事情,这能够保证在制作进程中不会再触发新的制作。

烘托管线

  1. Frame 一次制作进程,称之为一帧(frame)。Flutter能够完结60fps(Frame Pre-Second)就是一秒钟最多能够触发60次重绘,FPS值越大,界面就越流畅。需求阐明的是Flutter中的frame概念并不等同于屏幕改写帧,由于Flutter UI结构的frame并不是每次屏幕改写都会触发,由于,假如UI在一段时间不变,那么每次屏幕改写都从头走一遍烘托流畅是不必要的,因而,Flutter在第一帧烘托完毕后会采纳一种自动恳求frame的办法来完结只要当UI或许会改动时才会从头走烘托流程。
  2. Flutter在window上注册一额onBeginFrame和一个onDrawFrame回调,在onDrawFrame回调中终究会调用drawFrame。
  3. 当调用window.scheduleFrame()办法之后,Flutter引擎会在合适的机遇(能够认为是在屏幕下一次改写之前,具体取决于Flutter引擎的完结)来调用onBeginFrame和onDrawFrame。

只要自动调用scheduleFrame()才会履行drawFrame。所以在Flutter中说到的frame时,如无特别阐明,则是和drawFrame()的调用对应,而不是和屏幕的改写频率对应。 2. Flutter调度进程SchedulerPhase Flutter 使用履行进程简略来讲分为idle和frame两种状况,idle状况代表没有frame处理,假如使用状况改动需求改写UI,则需求经过scheduleFrame()去恳求新的frame,当frame到来时,就进入了frame状况,整个Flutter使用的生命周期就是在idle和frame两种状况间切换。

frame处理流程 当有新的frame到来时,具体处理进程是一次履行四个任务行列:transientCallbacks、minFrameMicrotasks、persistentCallbacks、postFrameCallbacks,当四个任务行列履行完毕后,当时frame完毕。综上,Flutter将整个生命周期分为五种状况,经过SchedulerPhase枚举来表示:

enum SchedulerPhase {
  /// 空闲状况,并没有 frame 在处理。这种状况代表页面未发生改动,并不需求从头烘托。
  /// 假如页面发生改动,需求调用`scheduleFrame()`来恳求 frame。
  /// 留意,空闲状况只是指没有 frame 在处理,一般微任务、定时器回调或许用户事情回调都
  /// 或许被履行,比方监听了tap事情,用户点击后咱们 onTap 回调就是在idle阶段被履行的。
  idle,
  /// 履行”暂时“回调任务,”暂时“回调任务只能被履行一次,履行后会被移出”暂时“任务行列。
  /// 典型的代表就是动画回调会在该阶段履行。
  transientCallbacks,
  /// 在履行暂时任务时或许会发生一些新的微任务,比方在履行第一个暂时任务时创立了一个
  /// Future,且这个 Future 在一切暂时任务履行完毕前就现已 resolve 了,这中情况
  /// Future 的回调将在[midFrameMicrotasks]阶段履行
  midFrameMicrotasks,
  /// 履行一些耐久的任务(每一个frame都要履行的任务),比方烘托管线(构建、布局、制作)
  /// 就是在该任务行列中履行的.
  persistentCallbacks,
  /// 在当时 frame 在完毕之前将会履行 postFrameCallbacks,一般进行一些清理工作和
  /// 恳求新的 frame。
  postFrameCallbacks,
}

烘托管线就是在persistentCallbacks中履行的。

烘托管线

当新的frame到来时,调用到WidgetsBinding的drawFrame()办法:

@override
void drawFrame() {
 ...//省略无关代码
  try {
    buildOwner.buildScope(renderViewElement); // 先履行构建
    super.drawFrame(); //然后调用父类的 drawFrame 办法
  } 
}

实践上关键代码就两行:先从头构建(build),然后再调用父类的drawFrame办法:

void drawFrame() {
  buildOwner!.buildScope(renderViewElement!); // 1.从头构建widget树
  //下面是 打开 super.drawFrame() 办法
  pipelineOwner.flushLayout(); // 2.更新布局
  pipelineOwner.flushCompositingBits(); //3.更新“层组成”信息
  pipelineOwner.flushPaint(); // 4.重绘
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // 5. 上屏,会将制作出的bit数据发送给GPU
    ...
  }
}

父类drawFrame首要做五件事:

  1. 从头构建widget树
  2. 更新布局
  3. 更新层组成信息
  4. 重绘
  5. 上屏:将制作的产品显现在屏幕上

上面的5步为rendering pipeline,中文翻译为“烘托流水线”。以setState的履行更新为例先对整个更新流程有一个大概的形象。

setState履行流

setState调用后:

  1. 首先调用当时element的markNeedsBuild办法,将当时element标记为dirty。
  2. 接着调用scheduleBuildFor,将element增加到pipelineOwner的dirtyElements列表。
  3. 终究恳求一个新的frame,随后会制作新的frame:onBuildScheduled->ensureVisualUpdate->scheduleFrame()。当新的frame到来时履行烘托管线。
void drawFrame() {
  buildOwner!.buildScope(renderViewElement!); //从头构建widget树
  pipelineOwner.flushLayout(); // 更新布局
  pipelineOwner.flushCompositingBits(); //更新组成信息
  pipelineOwner.flushPaint(); // 更新制作
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // 上屏,会将制作出的bit数据发送给GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}
  1. 从头构建widget树:假如dirtyElements列表不为空,则遍历该列表,调用每一个element的rebuild办法从头构建新的widget(树),由于新的widget(树)使用新的状况构建,所以或许导致widget布局信息(占用的空间和方位)发生改动,假如发生改动,则会调用其renderObject的markNeedsLayout办法,该办法会从当时节点向父级查找,知道找到一个relayoutBpundary,则将根节点增加到nodesNeedingLayout列表中。
  2. 更新布局:遍历nodesNeedingLayout数组,对每一个renderObject从头布局(调用其layout办法),确认新的大小和偏移。layout办法中会调用markNeedsPaint(),该办法和markNeedsLayout办法功能类似,也会从当时节点向父级查找,直达找到一个isRepaintBoundary属性为true的父节点,然后将它增加到一个大局的nodesNeedingPaint列表中;由于根节点(RenderView)的isRepaintBoundary为true,所以必会找到一个。查找进程完毕后,会调用buildOwner.requestVisualUpdate办法,该办法终究会调用scheduleFrame(),该办法中会先判别是否现已恳求过新的frame,假如没有则恳求一个新的frame。
  3. 更新组成信息:后续再解说
  4. 更新制作:遍历nodesNeedingPaint列表,调用每一个节点的paint办法进行重绘,制作进程会生成Layer。需求阐明一下,flutter中制作成果是保存在Layer中的,也就是说只需Layer不释放,那么制作的成果就会被缓存,因而Layer能够跨frame来缓存制作成果,避免不必要的重绘开销。Flutter结构制作进程中,遇到isRepaintBoundary为true的节点时,才会生成一个新的Layer。可见Layer和renderObject不是一一对应的关系,父子节点能够共享。假如是自界说组件,能够在renderObject中手动增加任意多个Layer,这一般用于只需一次制作而随后不会发生改动的制作元素的缓存场景。
  5. 上屏:制作完结后,得到一棵Layer树,因而,终究需求将Layer树中的制作信息在屏幕上显现。Flutter是自完结的烘托引擎,需求将制作信息提交给Flutter engine,而renderView.compositeFrame正是完结这个任务。

以上就是setState调用UI的大概更新进程,实践流程会更加杂乱一些,比方build进程中是不允许再调用setState的,结构需求做一些检查。又比方在frame中涉及到动画的调度、在上屏时会将一切的Layer增加到场景(Scene)目标后,再烘托Scene。

setState履行机遇问题

setState会触发build,而build是在履行persistentCallbacks阶段履行的,因而只需不是在该阶段履行setState就肯定安全,可是这样的粒度太大了,比方在transientCallbacks和midFrameMicrotasks阶段,假如使用状况发生改动,终究的办法是只将组件标记为dirty,而不用再去恳求新的frame,由于当时frame还没履行到persistentCallbacks,因而后边履行到后就会在当时帧烘托管线中改写UI。因而,setState在标记完dirty后,会先判别一下调度状况,假如是idle或履行postFrameCallbacks阶段才会去恳求新的frame:

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame(); // 恳求新的frame
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks: // 留意这一行
      return;
  }
}

上面的代码在大多数情况下是没有问题的,可是假如在build阶段又调用setState的话,还是会有问题,由于假如在build阶段又调用setState的话就会导致build…这样将导致循环调用,因而flutter结构发现在build阶段调用setState的话就会报错,如:

@override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, c) {
        // build 阶段不能调用 setState, 会报错
        setState(() {
          ++index;
        });
        return Text('xx');
      },
    );
  }

运转后台报错,控制台会打印:

==== Exception caught by widgets library ====
The following assertion was thrown building LayoutBuilder:
setState() or markNeedsBuild() called during build.

需求留意,假如直接在build中调用setState,代码如下

@override
Widget build(BuildContext context) {
  setState(() {
    ++index;
  });
  return Text('$index');
}  

运转是不会报错的,原因是在履行build时当时组件的dirty状况(对应的element中)为true,只要build履行完结后才会被置为false。而setState履行的时候会先判别当时dirty的值,假如为true则会直接回来,因而就不会报错。

上面只讨论了在build阶段调用setState会导致过错,实践上在整个构建、布局、制作阶段都不能同步调用setState,由于在这些阶段调用setState都有或许恳求新的frame,都或许会导致循环调用,因而假如要在这些阶段更新使用状况时,都不能直接调用setState。

安全更新

在build阶段不能调用setState,实践上在组件的布局和制作阶段也不能直接再同步恳求从头布局或重绘,能够经过以下办法优化:

// 在build、布局、制作阶段安全更新
void update(VoidCallback fn) {
  SchedulerBinding.instance.addPostFrameCallback((_) {
    setState(fn);
  });
}

留意:update函数只应在frame履行persistentCallbacks时履行,其他阶段直接调用setState即可。由于idle状况会是一个特例,假如在idle状况下调用update的话,需求手动调用scheduleFrame()恳求新的frame,不然postFrameCallbacks鄙人一个frame(其他组件恳求的frame)到来之前都不会被履行,因而优化成以下办法:

void update(VoidCallback fn) {
  final schedulerPhase = SchedulerBinding.instance.schedulerPhase;
  if (schedulerPhase == SchedulerPhase.persistentCallbacks) {
    SchedulerBinding.instance.addPostFrameCallback((_) {
      setState(fn);
    });
  } else {
    setState(fn);
  }
}

至此,封装了一个能够安全更新状况的update函数。

之前自绘组件中就是这样:

SchedulerBinding.instance.addPostFrameCallback((_) {
   ...
   markNeedsPaint();
 });

没有直接调用markNeedsPaints(),原因就是如此。

总结

Flutter 启动流程和渲染管线