三、Flutter UI 更新流程

1,979 阅读3分钟

一、Flutter 之图像绘制原理

二、Widget、Element、RenderObject

四、build 流程分析

五、layout 流程分析

六、Paint 绘制(1)

七、Paint 绘制(2)

八、composite 流程分析

九、Flutter 小实践

第一章提及到在 Flutter 中,是基于 Vsync 垂直信号的机制来协调图像数据的生成,那在 flutter 中,是怎么触发监听 vsync 信号的呢

1、Vsync 注册监听

setState 方法 是我们在写UI 组件时常用的方法,用于UI界面数据的更新

(1)调用 setState 方法

@protected
void setState(VoidCallback fn) {
  _element.markNeedsBuild();
}

(2)markNeedsBuild

void markNeedsBuild() {
    if (dirty)
      return;
    _dirty = true;  // 将元素标记为"脏元素"
    owner.scheduleBuildFor(this);
}

将元素标记为"脏元素"( flutter 在更新触发重新渲染时,只会将标脏了的元素重新绘制渲)同时调用 BuildOwner 的 scheduleBuildFor 方法

(3)scheduleBuildFor

void scheduleBuildFor(Element element) {
  if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
    _scheduledFlushDirtyElements = true;
    onBuildScheduled();
  }
  _dirtyElements.add(element);
  element._inDirtyList = true;
}

这个方法中,将该元素添加到脏元素集合中,同时调用 onBuildScheduled 方法

(4) onBuildScheduled

mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    buildOwner.onBuildScheduled = _handleBuildScheduled;
  }

onBuildScheduled 方法是在 flutter 初始化时进行绑定的一个方法

(5) _handleBuildScheduled

void _handleBuildScheduled() {
  ensureVisualUpdate();
}

(6) ensureVisualUpdate判断当前调度所处的状态,如果是 idle(空闲)或 postFrameCallbacks 时调用 scheduleFrame

void ensureVisualUpdate() {
  switch (schedulerPhase) {
    case SchedulerPhase.idle:
    case SchedulerPhase.postFrameCallbacks:
      scheduleFrame();
      return;
    case SchedulerPhase.transientCallbacks:
    case SchedulerPhase.midFrameMicrotasks:
    case SchedulerPhase.persistentCallbacks:
      return;
  }
}

(7) scheduleFrame

void scheduleFrame() {
  window.scheduleFrame();
}

(8)window.scheduleFrame

void scheduleFrame() native 'Window_scheduleFrame';

scheduleFrame 是底部 dart engine 底层的一个方法,这个方法用于 注册 vsync 信号的监听。由此可见,setState 方法的主要原理是,将当前元素标脏,同时触发 vsync 信号,以便在下次 vsync 信号回调时,完成这些脏元素的更新。

2、Vsync 信号回调

(1) Window

Window Flutter Framework连接宿主操作系统的接口, 在 Window 类 中定义了Vsync 信号的回调处理

VoidCallback get onDrawFrame => _onDrawFrame;

(2) SchedulerBinding

window.onDrawFrame = _handleDrawFrame;

onDrawFrame 方法在 SchedulerBinding初始化时进行了重新指向

(3) _handleDrawFrame

void _handleDrawFrame() {
  handleDrawFrame();
}

(4) handleDrawFrame

void handleDrawFrame() {
    _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);
}

在这个方法中,主要是执行一些回调集合,其中

persistentCallbacks:用于存放一些持久的回调,如下代码所示SchedulerBinding.instance.addPersitentFrameCallback(),这个回调中处理了布局与绘制工作,即 调用执行 build/layout/paint 流水线工作的地方,

postFrameCallbacks:在Frame结束时只会被调用一次,调用后会被系统移除,可由 SchedulerBinding.instance.addPostFrameCallback() 注册

(5)persistentCallbacks --> RendererBinding

void initInstances() {
  super.initInstances();
  addPersistentFrameCallback(_handlePersistentFrameCallback);
}

(6)_handlePersistentFrameCallback

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
}

(7)WidgetsBinding 中的 drawFrame

void drawFrame() {
  try {

    if (renderViewElement != null)
      buildOwner.buildScope(renderViewElement);
    super.drawFrame();
    buildOwner.finalizeTree();
  }
}

(8) super.drawFrame()(RendererBinding)

void drawFrame() {
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  renderView.compositeFrame(); // this sends the bits to the GPU
  pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}

mixin widgetBinding 以 RenderingBinding 为基础,因此会覆盖 RendererBinding 的drawFrame 函数,该方法执行的操作,其实就是 build -> layout -> paint -> composite

3、首帧渲染

前面分析了 setState 方法的工作原理,该方法主要是更新数据时调用,但 app 启动过程中,并没有触发 setState 方法,整个 app 首帧是怎么渲染的呢?

(1) runApp

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

ensureInitialized 主要时实现 各种 Binding 的初始化,例如 SchedulerBinding, RendererBinding, WidgetsBinding 等, attachRootWidget 方法是完成整个 widget UI 树的构建挂载

(2)scheduleWarmUpFrame

void scheduleWarmUpFrame() {
    handleBeginFrame(null);
    handleDrawFrame();
    if (hadScheduledFrame)
      scheduleFrame();
}

在完成UI 树的挂载之后,调用了 scheduleWarmUpFrame, 这个方法主要是调用了 handleBeginFrame 和 handleDrawFrame, 其中 handleBeginFrame 主要是 处理 transientCallbacks, 这个方法一般存放动画回调。可以通过SchedulerBinding.instance.scheduleFrameCallback 添加回调, handleDrawFrame 方法则是调用 drawFrame 方法完成首帧的绘制

(3) handleBeginFrame

void handleBeginFrame(Duration rawTimeStamp) {
 
 
    _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);
    });
}

由此可见,首帧的绘制并没有等待 Vsync 信号的回调,而是直接绘制。