Flutter核心原理(Flutter启动流程和渲染管线 )

应用启动

Flutter的入口在"lib/main.dart"的main()函数中,它是Dart应用程序的起点。

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

查看runApp()方法

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

app是第一个组件,WidgetsFlutterBinding是绑定widget和Flutter引擎的桥梁。

WidgetsFlutterBinding

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对象的一些事件。WidgetsFlutterBinding.ensureInitialized()负责初始化一个WidgetsBinding的全局单例,然后调用scheduleAttachRootWidget方法将根Widget添加到RenderView。

attachRootWidget

void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
      attachRootWidget(rootWidget);
    });
  }


void attachRootWidget(Widget rootWidget) {
    final bool isBootstrapFrame = renderViewElement == null;
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
    if (isBootstrapFrame) {
      SchedulerBinding.instance.ensureVisualUpdate();
    }
  }

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 将不会响应各种事件,这可以保证在绘制过程中不会再触发新的重绘。

渲染管线

Frame

在 Flutter 中,frame 并不是每次屏幕刷新都会触发的,而是通过调用 window.scheduleFrame() 来主动请求进行渲染。Flutter 引擎会在合适的时机调用 onBeginFrame 和 onDrawFrame 回调,最终执行 drawFrame 进行渲染操作。因此,我们在 Flutter 中提到的 frame 指的是一次 drawFrame 的调用。

SchedulerPhase

Flutter 应用的执行过程可以分为 idleframe 两种状态。在 idle 状态下,应用没有进行帧处理,如果应用状态发生变化需要刷新界面,则需要通过 scheduleFrame() 方法请求新的帧。当新的帧到来时,应用进入 frame 状态,此时会按照特定顺序执行四个任务队列:transientCallbacks、midFrameMicrotasks、persistentCallbacks 和 postFrameCallbacks。当这四个任务队列执行完毕后,当前帧的处理结束。

  1. idle:空闲状态,并没有 frame 在处理。这种状态代表页面未发生变化,并不需要重新渲染。
  2. transientCallbacks:执行”临时“回调任务,”临时“回调任务只能被执行一次,执行后会被移出”临时“任务队列。典型的代表就是动画回调会在该阶段执行。
  3. midFrameMicrotasks:在执行临时任务时可能会产生一些新的微任务,比如在执行第一个临时任务时创建了一个Future,且这个 Future 在所有临时任务执行完毕前就已经 resolve 了。
  4. persistentCallbacks: 执行一些持久的任务(每一个frame都要执行的任务),比如渲染管线(构建、布局、绘制)。
  5. postFrameCallbacks 在当前 frame 在结束之前将会执行 postFrameCallbacks,通常进行一些清理工作和请求新的 frame。

渲染管线(rendering pipeline)

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

际上关键的代码就两行:先重新构建(build),然后再调用父类的 drawFrame 方法,我们将父类的 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
    ...
  }
}

可以看到主要做了5件事:

  1. 重新构建widget树。

  2. 更新布局。

  3. 更新“层合成”信息。

  4. 重绘。

  5. 上屏:将绘制的产物显示在屏幕上

setState 执行流

执行setState调用以后

  1. 首先调用当前 element 的 markNeedsBuild 方法,将当前 element标记为 dirty 。
  2. 接着调用 scheduleBuildFor,将当前 element 添加到pipelineOwner的 dirtyElements 列表。
  3. 最后请求一个新的 frame,随后会绘制新的 frame:onBuildScheduled->ensureVisualUpdate->scheduleFrame() 。当新的 frame 到来时执行渲染管线

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;
  }
}

安全更新

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

总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值