• flutter如何监听页面是否渲染完成?
  • 发布于 2个月前
  • 724 热度
    0 评论
  • 亦東風
  • 0 粉丝 38 篇博客
  •   
前言
有时候我们需要在页面渲染完成后做一些操作,那么flutter中如何监听渲染完成,用addPostFrameCallback即可,如下:
@override
void initState() {
  ...

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

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

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

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

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

源码解析
下面通过源码来看看具体是怎么实现的。两个函数源码如下:
final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
// 堆代码 duidaima.com
void addPersistentFrameCallback(FrameCallback callback) {
  _persistentCallbacks.add(callback);
}

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

void addPostFrameCallback(FrameCallback callback) {
  _postFrameCallbacks.add(callback);
}
可以看到是分别将callback放到不同的列表中,那么在哪里执行?
在SchedulerBinding的handleDrawFrame中,代码如下:
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函数中可以得到答案,代码如下:
@override
void initInstances() {
  super.initInstances();
  _instance = this;
 ...
  addPersistentFrameCallback(_handlePersistentFrameCallback);
  initMouseTracker();
  if (kIsWeb) {
    addPostFrameCallback(_handleWebFirstFrame);
  }
}
这里通过addPersistentFrameCallback添加了一个callback,这个callback如下:
void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();
  _scheduleMouseTrackerUpdate();
}
drawFrame代码如下:
@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也定性为帧的结束事件?这要从RendererBinding的initInstances的执行时机探究。在RendererBinding中没有找到调用initInstances的代码,不过这个函数是mixin进来的,如下:
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    ...
这样我们去被mixin的类中查找即可,最终在BindingBase的构造函数中找到了:
BindingBase() {
  developer.Timeline.startSync('Framework initialization');

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

  ...
}
所以initInstances是在创建的时候执行的,那么什么时候创建RendererBinding?上面我们知道WidgetsBinding又mixinRendererBinding,而最终WidgetsFlutterBinding又mixin了WidgetsBinding(同样也mixin了RendererBinding),WidgetsFlutterBinding就是最终的实现类,他的代码如下:
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对象。
而这个函数则在应用一开始就被执行了,如下:
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
我们知道runApp函数是应用的入口,所以RendererBinding的initInstances在应用一开始就执行了,所以我们在代码中通过addPersistentFrameCallback添加的callback一定会在drawFrame之后执行,所以我们后添加的这些callback实际上也是在帧渲染结束后,这也是很多文章将addPersistentFrameCallback也定性为帧的结束事件,只能说是有这个效果,但是不够严谨。

用户评论