前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter中如何监听帧渲染相关事件?

Flutter中如何监听帧渲染相关事件?

作者头像
BennuCTech
发布2023-08-28 18:22:46
3720
发布2023-08-28 18:22:46
举报
文章被收录于专栏:BennuCTechBennuCTech

前言

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

代码语言:javascript
复制
@override
void initState() {
  ...

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

  });
}

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

PostFrameCallback和PersistentFrameCallback

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

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

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

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

源码解析

下面通过源码来看看具体是怎么实现的。两个函数源码如下:

代码语言:javascript
复制
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中,代码如下:

代码语言:javascript
复制
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函数中可以得到答案,代码如下:

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

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

代码语言:javascript
复制
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}

drawFrame代码如下:

代码语言:javascript
复制
@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是在它之后执行的,这时候帧渲染已经执行完成,所以是帧结束事件。

PersistentFrameCallback时机

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

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

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

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

代码语言:javascript
复制
BindingBase() {
  developer.Timeline.startSync('Framework initialization');

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

  ...
}

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

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

代码语言:javascript
复制
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对象。

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

代码语言:javascript
复制
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BennuCTech 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档