详细解读Flutter中帧渲染的监听机制

前言

有时候我们需要在页面渲染完成后做一些操作,那么flutter中如何监听渲染完成,用addPostFrameCallback即可,如下:

dart 复制代码
@override
void initState() {
  ...

  WidgetsBinding.instance.addPostFrameCallback((timeStamp){
    ...

  });
}

我们在initState中添加监听,这样当渲染完成就会调用,但是注意只能调用一次!也就是说如何重新渲染不会再次调用,如果需要则必须重新添加。

addPostFrameCallback和addPersistentFrameCallback

我们注意到WidgetsBinding(实际上是SchedulerBindingWidgetsBindingmixin它)还有另外一个函数:addPersistentFrameCallback,那么这个函数有什么作用,它与addPostFrameCallback有什么区别?

通过官方文档我们可以了解到:

addPostFrameCallback是在一帧结束的时候回调,而且是一次性的(这个后面细说);而addPersistentFrameCallback对应的是"begin frame"事件,也就是说是理论上是帧开始(这个后面细说),并且与addPostFrameCallback不同,它是持续,一旦注册就会一直接受事件。

另外注意,这两个函数都是全局的且不可注销的,所以使用的时候一定要注意,addPostFrameCallback虽然是一次性的,但是也要注意不可注销导致的一些问题。

源码解析

两个函数源码如下:

dart 复制代码
final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];

void addPersistentFrameCallback(FrameCallback callback) {
  _persistentCallbacks.add(callback);
}

final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];

void addPostFrameCallback(FrameCallback callback) {
  _postFrameCallbacks.add(callback);
}

可以看到是分别将callback放到不同的列表中,那么在哪里执行?

SchedulerBindinghandleDrawFrame中,代码如下:

dart 复制代码
void handleDrawFrame() {
  assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
  Timeline.finishSync(); // end the "Animate" phase
  try {
    // PERSISTENT FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    for (final FrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);

    // POST-FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.postFrameCallbacks;
    final List<FrameCallback> localPostFrameCallbacks =
        List<FrameCallback>.from(_postFrameCallbacks);
    _postFrameCallbacks.clear();
    for (final FrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);
  } finally {
    _schedulerPhase = SchedulerPhase.idle;
    Timeline.finishSync(); // end the Frame
    assert(() {
      if (debugPrintEndFrameBanner)
        debugPrint('▀' * _debugBanner!.length);
      _debugBanner = null;
      return true;
    }());
    _currentFrameTimeStamp = null;
  }
}

这里可以看到先执行_persistentCallbacks中的回调,然后在执行_postFrameCallbacks,而且注意在执行_postFrameCallbacks时是先将其中的元素放入另外一个列表中,然后清空了_postFrameCallbacks,所以这就是addPostFrameCallback只执行一次的原因。

通过代码可以看到两个其实是先后执行的,那么为什么分吧对应帧开始和结束?

RendererBinding(同样WidgetsBindingmixin它)的initInstances函数中可以得到答案,代码如下:

dart 复制代码
@override
void initInstances() {
  super.initInstances();
  _instance = this;
 ...
  addPersistentFrameCallback(_handlePersistentFrameCallback);
  initMouseTracker();
  if (kIsWeb) {
    addPostFrameCallback(_handleWebFirstFrame);
  }
}

这里通过addPersistentFrameCallback添加了一个callback,这个callback如下:

dart 复制代码
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

drawFrame代码如下:

dart 复制代码
@protected
void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); // this sends the bits to the GPU
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

可以看到这里通过pipeline来进行渲染,所以addPersistentFrameCallback实际上是包含帧渲染的,所以在官方文档中的说法是

概念上,addPersistentFrameCallback对应的是"begin frame"事件

addPostFrameCallback是在它之后执行的,这时候帧渲染已经执行完成,所以是帧结束事件。

initInstances

但是为什么很多文章将addPersistentFrameCallback也定性为帧的结束事件?这要从RendererBindinginitInstances的执行时机探究。

RendererBinding中没有找到调用initInstances的代码,不过这个函数是mixin进来的,如下:

dart 复制代码
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    ...

这样我们去被mixin的类中查找即可,最终在BindingBase的构造函数中找到了:

dart 复制代码
BindingBase() {
  developer.Timeline.startSync('Framework initialization');

  assert(!_debugInitialized);
  initInstances();
  assert(_debugInitialized);

  ...
}

所以initInstances是在创建的时候执行的,那么什么时候创建RendererBinding

上面我们知道WidgetsBinding又mixinRendererBinding,而最终WidgetsFlutterBinding又mixin了WidgetsBinding(同样也mixin了RendererBinding),WidgetsFlutterBinding就是最终的实现类,他的代码如下:

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

它只有一个函数ensureInitialized,其中新建了WidgetsFlutterBinding对象。

而这个函数则在应用一开始就被执行了,如下:

dart 复制代码
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

我们知道runApp函数是应用的入口,所以RendererBindinginitInstances在应用一开始就执行了,所以我们在代码中通过addPersistentFrameCallback添加的callback一定会在drawFrame之后执行,所以我们后添加的这些callback实际上也是在帧渲染结束后,这也是很多文章将addPersistentFrameCallback也定性为帧的结束事件,只能说是有这个效果,但是不够严谨。

相关推荐
旭日猎鹰5 小时前
Flutter踩坑记录(三)-- 更改入口执行文件
flutter
旭日猎鹰5 小时前
Flutter踩坑记录(一)debug运行生成的项目,不能手动点击运行
flutter
️ 邪神5 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】自定义View
flutter·ios·鸿蒙·reactnative·anroid
比格丽巴格丽抱17 小时前
flutter项目苹果编译运行打包上线
flutter·ios
SoaringHeart17 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
AiFlutter21 小时前
Flutter通过 Coap发送组播
flutter
嘟嘟叽2 天前
初学 flutter 环境变量配置
flutter
iFlyCai2 天前
深入理解Flutter生命周期函数之StatefulWidget(一)
flutter·生命周期·dart·statefulwidget
sunly_2 天前
Flutter:photo_view图片预览功能
android·javascript·flutter
Summer不秃2 天前
Flutter中sqflite的使用案例
flutter