前言
有时候我们需要在页面渲染完成后做一些操作,那么flutter中如何监听渲染完成,用addPostFrameCallback即可,如下:
dart
@override
void initState() {
...
WidgetsBinding.instance.addPostFrameCallback((timeStamp){
...
});
}
我们在initState中添加监听,这样当渲染完成就会调用,但是注意只能调用一次!也就是说如何重新渲染不会再次调用,如果需要则必须重新添加。
addPostFrameCallback和addPersistentFrameCallback
我们注意到WidgetsBinding
(实际上是SchedulerBinding
,WidgetsBinding
mixin它)还有另外一个函数: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放到不同的列表中,那么在哪里执行?
在SchedulerBinding
的handleDrawFrame
中,代码如下:
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
(同样WidgetsBinding
mixin它)的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
也定性为帧的结束事件?这要从RendererBinding
的initInstances
的执行时机探究。
在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
函数是应用的入口,所以RendererBinding
的initInstances
在应用一开始就执行了,所以我们在代码中通过addPersistentFrameCallback
添加的callback一定会在drawFrame
之后执行,所以我们后添加的这些callback实际上也是在帧渲染结束后,这也是很多文章将addPersistentFrameCallback
也定性为帧的结束事件,只能说是有这个效果,但是不够严谨。