被遮挡路由会被刷新吗?
通过分析源码发现,被遮挡的路由是不会被刷新,这里的刷新是指不会被paint
我们知道state在调用了setState后实际是调用了_element!.markNeedsBuild()
分析markNeedsBuild源码发现,该方法会把当前element标记为dirty脏数据,
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
}
BuildOwner? get owner => _owner;
这里scheduleBuildFor就是将该element放到脏数据_dirtyElements列表里面进行缓存,
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled!();
}
_dirtyElements.add(element);
element._inDirtyList = true;
...
}
这里onBuildScheduled是一个回调函数
通过上图我们能够看出,BuildOwnder在构造函数可传入该回调函数。
通过定位能很容易找到这个参数是在WidgetsBinding传入
可以查看_handleBuildScheduled方法,这里继续调用
void _handleBuildScheduled() {
// If we're in the process of building dirty elements, then changes
// should not trigger a new frame.
...
ensureVisualUpdate();
}
/// Schedules a new frame using [scheduleFrame] if this object is not
/// currently producing a frame.
///
/// Calling this method ensures that [handleDrawFrame] will eventually be
/// called, unless it's already in progress.
///
/// This has no effect if [schedulerPhase] is
/// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
/// (because a frame is already being prepared in that case), or
/// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
/// rendered in that case). It will schedule a frame if the [schedulerPhase]
/// is [SchedulerPhase.idle] (in between frames) or
/// [SchedulerPhase.postFrameCallbacks] (after a frame).
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
这个方法判断当前schedulerPhase状态,只有在停止或者前面状态处理到postFrameCallBacks才
执行scheduleFrame方法
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
...
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
上面这个方法,重点查看window.scheduleFrame方法,这个方法是用来通知底层引擎上层需要渲染,需要底层引擎在Vsync驱动下调用Flutter上层回调,进入布局、绘图、渲染流程。
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
void scheduleFrame() => platformDispatcher.scheduleFrame();
/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
///
/// See also:
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
上面的方法,我们看到有native标记,可以理解为Android JNI接口类型,都是调用底层方法
这里省略具体底层引擎如何调用上层接口,下一篇再详细讲解这一块,
今天直接跳转到讲解逻辑里
当渲染流执行到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;
}
}
由于篇幅限制,这里只讲和主题相关的flushPaint点
/// Update the display lists for all render objects.
///
/// This function is one of the core stages of the rendering pipeline.
/// Painting occurs after layout and before the scene is recomposited so that
/// scene is composited with up-to-date display lists for every render object.
///
/// See [RendererBinding] for an example of how this function is used.
void flushPaint() {
if (!kReleaseMode) {
Timeline.startSync('Paint', arguments: timelineArgumentsIndicatingLandmarkEvent);
}
...
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layerHandle.layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layerHandle.layer!.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
assert(_nodesNeedingPaint.isEmpty);
} finally {
assert(() {
_debugDoingPaint = false;
return true;
}());
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
这个就是今天的重点方法
在for循环里,对node对象进行逻辑判断,判断当前node layer 是否处于attached状态,
当我们路由被遮挡后,当前路由里面的RenderObject 的layer 状态都会是false
也就不会进入到paint流程里面,减少性能消耗,这里被遮挡的组件渲染时只能执行_skippedPaintingOnLayer方法
// Called when flushPaint() tries to make us paint but our layer is detached.
// To make sure that our subtree is repainted when it's finally reattached,
// even in the case where some ancestor layer is itself never marked dirty, we
// have to mark our entire detached subtree as dirty and needing to be
// repainted. That way, we'll eventually be repainted.
void _skippedPaintingOnLayer() {
...
AbstractNode? node = parent;
while (node is RenderObject) {
if (node.isRepaintBoundary) {
if (node._layerHandle.layer == null)
break; // looks like the subtree here has never been painted. let it handle itself.
if (node._layerHandle.layer!.attached)
break; // it's the one that detached us, so it's the one that will decide to repaint us.
node._needsPaint = true;
}
node = node.parent;
}
}