Flutter的绘制流程

1. 动画与微任务阶段:

主要是处理动画及执行一系列微任务。具体是在SchedulerBinding中的handleBeginFrame函数中实现。

void handleBeginFrame(Duration rawTimeStamp) {
    ...
    try {
      // TRANSIENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.transientCallbacks;
      //切换为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 {
      //切换为midFrameMicrotasks阶段
      _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
    }
  }

2. 构建阶段(build):

在该阶段主要是重新构建标记为“脏”的Widget节点及将需要更新的RenderObject对象标记为“脏”。

当handleBeginFrame函数执行完毕后,就会执行handleDrawFrame函数,该函数在SchedulerBinding对象初始化时会与Window相关联,所以除第一次需要主动调用外,其他时候皆是通过Window来调用该函数。

  void handleDrawFrame() {
    assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
    Timeline.finishSync(); // end the "Animate" phase
    try {
      //持久帧回调,该回调会一直存在,不会移除
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
      //当前帧绘制完成回调
      _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();
      });
      _currentFrameTimeStamp = null;
    }
  }

这里重点关注持久帧回调,该回调也是UI绘制的关键函数,是在RendererBinding对象初始化时注册的。主要函数:persistentCallbacks

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    ...
    //注册持久帧回调
    addPersistentFrameCallback(_handlePersistentFrameCallback);
  }
  void _handlePersistentFrameCallback(Duration timeStamp) {
    //绘制帧
    drawFrame();
  }
  //绘制帧
  void drawFrame() {
    //对Widget进行测量、布局
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    //对Widget进行绘制
    pipelineOwner.flushPaint();
    //发送数据给GPU
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
  }
}

在WidgetsBinding中重写了drawFrame函数。在该函数中会创建新的Widget对象替换旧的Widget对象并将不需要的Element节点从树中移除。

  @override
  void drawFrame() {
    ...
    try {
      //如果根结点存在,就重新构建Widget
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      //调用RendererBinding中的drawFrame函数
      super.drawFrame();
      //移除不再使用的Element节点
      buildOwner.finalizeTree();
    } finally {...}
    ...
  }

2.1. 重新build Widget对象:
  void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
    try {
      //“脏”节点列表需要重新排序
      _scheduledFlushDirtyElements = true;
      ...
      //将标记为“脏”的Element节点根据深度进行排序
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      //标记为“脏”的Element节点数量
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      //遍历“脏”节点
      while (index < dirtyCount) {
        try {
          //重新构建Widget,及是否复用当前Element
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
        //当_dirtyElements集合中的“脏”节点还未处理完毕时,又添加了新的“脏”节点
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          //根据“脏”节点的深度进行排序
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          //如果当前节点的深度比新加入的“脏”节点深度要深,则需要将处理坐标指向新加入的“脏”节点
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            index -= 1;
          }
        }
      }
    } finally {
      //清除_dirtyElements中所有节点的“脏”状态
      for (Element element in _dirtyElements) {
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      Timeline.finishSync();
    }
  }

_dirtyElements是一个集合,存储了所有标记为“脏”的节点。在对其中的“脏”节点进行处理时,需要首先对集合中的“脏”节点进行排序,其排序规则如下。

● 如果“脏”节点的深度不同,则按照深度进行升序排序

● 如果“脏”节点的深度相同,则会将“脏”节点放在集合的右侧,“干净”节点则在在集合的左侧。

在排序完成后,就要遍历该集合,对其中的“脏”节点进行处理。在这里调用的是rebuild函数,通过该函数,会重新创建“脏”节点下的所有Widget对象,并根据新的Widget对象来判断是否需要重用Element对象。一般只要不是增删Widget,Element对象都会被重用,从而也就会重用RenderObject对象。由于Widget是一个非常轻量级的数据结构,所以在UI更新时做到了把性能损耗降到最低。

如果_dirtyElements中的“脏”节点还未处理完毕,就又新增了“脏”节点,那么这时候就会重新排序,保证_dirtyElements集合的左侧永远是“干净”节点,右侧永远是“脏”节点。

由于rebuild

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>