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

相关推荐
fundroid34 分钟前
2025 跨平台技术如何选:KMP 与 Flutter 的核心差异
flutter·kotlin·kmp
耳東陈3 小时前
Flutter开箱即用一站式解决方案-新增企业级日志
flutter
顾林海3 小时前
Flutter 图片组件全面解析:从基础加载到高级应用
android·前端·flutter
眼镜会飞3 小时前
Flutter window和Mac中webview2使用Cef替代
windows·flutter·mac
淡写成灰3 小时前
Flutter自定义带有Badger组件组
flutter
好的佩奇4 小时前
Dart 之任务
android·flutter·dart
豪冷啊17 小时前
Flutter Invalid constant value.
flutter
中杯可乐多加冰18 小时前
告别信息焦虑,用这个国产AI知识库工具做知识管理,大脑终于解放了
人工智能·掘金·金石计划
顾林海21 小时前
Flutter容器组件深度解析
android·前端·flutter
xq952721 小时前
mac os flutter 配置环境变量
flutter