详细解读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也定性为帧的结束事件,只能说是有这个效果,但是不够严谨。

相关推荐
liulian09163 小时前
Flutter 跨平台路由与状态管理:go_router 与 Riverpod 的 OpenHarmony总结
flutter·华为·学习方法·harmonyos
liulian09164 小时前
Flutter for OpenHarmony 跨平台技术实战:flutter_animate 与 pull_to_refresh 库的鸿蒙化适配总结
flutter·华为·学习方法·harmonyos
IntMainJhy4 小时前
【flutter for open harmony】第三方库 Flutter 二维码生成的鸿蒙化适配与实战指南
数据库·flutter·华为·sqlite·harmonyos
jiejiejiejie_5 小时前
Flutter for OpenHarmony 底部选项卡与多语言适配小记:让 App 更贴心的两次小升级✨
flutter·华为·harmonyos
jiejiejiejie_6 小时前
Flutter for OpenHarmony 应用更新检测与萌系搜索功能实战小记✨
flutter·华为·harmonyos
IntMainJhy7 小时前
Flutter 三方库 Firebase Messaging 鸿蒙化适配与实战指南(权限检查+设备Token获取全覆盖)
flutter·华为·harmonyos
liulian09168 小时前
Flutter 依赖注入与设备信息库:get_it 与 device_info_plus 的 OpenHarmony 适配指南总结
flutter·华为·学习方法·harmonyos
里欧跑得慢8 小时前
微交互设计模式:提升用户体验的细节之美
前端·css·flutter·web
stringwu8 小时前
Flutter GetX 核心坑及架构选型与可替换性方案
前端·flutter
IntMainJhy8 小时前
【flutter for open harmony】第三方库Flutter 国际化多语言的鸿蒙化适配与实战指南
数据库·flutter·华为·sqlite·harmonyos