本文主要介绍Flutter
中创建一个Widget
到屏幕上渲染出Widget内容
的路程.
拾用本文您将获得:
Widget
是什么Element
是什么RenderObject
是什么
附加Buff
:
Widget
直接渲染成图片- 文本
String
的绘制 - 图片
ui.Image
的绘制
这一切都要从runApp
方法开始说起, 如果你还不知道runApp
是什么, 建议从
https://docs.flutter.dev/ui 开始阅读…
runApp
runApp
方法就是进入Flutter
世界的入口, 方法参数也是唯一的参数就是一个Widget
void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
_runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}
那么这个名为app
的Widget
是怎样到界面上的呢? 开始吧…
Element
要想将Widget
渲染上屏, 首先就需要将Widget
变换成Element
.
所以在runApp
方法的执行链上, 很容易就能跟踪到下面的代码:
void attachToBuildOwner(RootWidget widget) {
final bool isBootstrapFrame = rootElement == null;
_readyToProduceFrames = true;
// 请注意这里
_rootElement = widget.attach(buildOwner!, rootElement as RootElement?);
if (isBootstrapFrame) {
SchedulerBinding.instance.ensureVisualUpdate();
}
}
关键代码widget.attach(buildOwner!, rootElement as RootElement?)
/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(/* parent */ null, /* slot */ null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
上述代码,就是将一个Widget
变换成Element
的关键代码. 请注意上面的代码, 因为这玩意在另一个类中原封不动的也出现过.
那就是RenderObjectToWidgetAdapter
如下:
/// 代码来自[RenderObjectToWidgetAdapter.attachToRenderTree]
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
Widget
变换成Element
同样也是将Widget
渲染成图片的关键步骤.
到这一步WidgetsBinding.rootElement
就赋值完成了, 接下来就是等待屏幕信号刷新帧,开始渲染了…
上述attachToBuildOwner
方法中, 有一行代码SchedulerBinding.instance.ensureVisualUpdate()
就是用来安排刷新帧的, 触发之后, 等待屏幕信号即可.
/// 代码来自[SchedulerBinding.ensureVisualUpdate]
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
/// 代码来自[SchedulerBinding.scheduleFrame]
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled) {
return;
}
assert(() {
if (debugPrintScheduleFrameStacks) {
debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
}
return true;
}());
ensureFrameCallbacksRegistered();
platformDispatcher.scheduleFrame();
_hasScheduledFrame = true;
}
void ensureFrameCallbacksRegistered() {
platformDispatcher.onBeginFrame ??= _handleBeginFrame;
platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}
PlatformDispatcher.onBeginFrame
调用链路SchedulerBinding.ensureVisualUpdate
->SchedulerBinding.scheduleFrame
->SchedulerBinding._handleBeginFrame
->SchedulerBinding.handleBeginFrame
->SchedulerBinding._invokeFrameCallback
平时通过SchedulerBinding.scheduleFrameCallback
方法安排的帧回调就是在SchedulerBinding._invokeFrameCallback
方法中执行的.
PlatformDispatcher.onDrawFrame
调用链路SchedulerBinding.ensureVisualUpdate
->SchedulerBinding.scheduleFrame
->SchedulerBinding._handleDrawFrame
->SchedulerBinding.handleDrawFrame
->SchedulerBinding._invokeFrameCallback
平时通过SchedulerBinding.addPersistentFrameCallback
和SchedulerBinding.addPostFrameCallback
方法安排的帧回调就是在这里进行处理的.
之后Flutter
通过无限循环执行PlatformDispatcher.onBeginFrame
和PlatformDispatcher.onDrawFrame
方法渲染出精美的软件界面.
读到这里, 你是否注意到和WidgetsBinding.rootElement
似乎一毛钱关系都没有呢?
不慌, 它在这里…
WidgetsFlutterBinding.ensureInitialized
还记得runApp
方法吗?
void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
_runWidget(binding.wrapWithDefaultView(app), binding, 'runApp');
}
这方法头一句就是WidgetsFlutterBinding.ensureInitialized
, 来看看它的神秘面纱.
/// 代码来自[WidgetsFlutterBinding.ensureInitialized]
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding._instance == null) {
WidgetsFlutterBinding();
}
return WidgetsBinding.instance;
}
聪明的你, 应该看出来了, 就是创建了一个单例WidgetsFlutterBinding
对象. 您可千万不要被它的表象所迷惑, 这玩意可是一个巨兽…
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding
当您创建WidgetsFlutterBinding
对象后会执行父类的构造方法BindingBase
BindingBase() {
if (!kReleaseMode) {
FlutterTimeline.startSync('Framework initialization');
}
assert(() {
_debugConstructed = true;
return true;
}());
assert(_debugInitializedType == null, 'Binding is already initialized to $_debugInitializedType');
//注意这里
initInstances();
assert(_debugInitializedType != null);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
if (!kReleaseMode) {
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
FlutterTimeline.finishSync();
}
}
会触发BindingBase.initInstances
方法, 此方法会依次执行:
GestureBinding.initInstances
主要用来执行屏幕手势回调处理
void initInstances() {
super.initInstances();
_instance = this;
platformDispatcher.onPointerDataPacket = _handlePointerDataPacket;
}
SchedulerBinding.initInstances
在开发阶段用来计算帧率时间等
void initInstances() {
super.initInstances();
_instance = this;
if (!kReleaseMode) {
addTimingsCallback((List<FrameTiming> timings) {
timings.forEach(_profileFramePostEvent);
});
}
}
ServicesBinding.initInstances
主要用于平台服务支持等
void initInstances() {
super.initInstances();
_instance = this;
_defaultBinaryMessenger = createBinaryMessenger();
_restorationManager = createRestorationManager();
_initKeyboard();
initLicenses();
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
SystemChannels.accessibility.setMessageHandler((dynamic message) => _handleAccessibilityMessage(message as Object));
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
SystemChannels.platform.setMethodCallHandler(_handlePlatformMessage);
platformDispatcher.onViewFocusChange = handleViewFocusChanged;
TextInput.ensureInitialized();
readInitialLifecycleStateFromNativeWindow();
initializationComplete();
}
PaintingBinding.initInstances
没想到吧,Flutter
原生就有图片缓存池
void initInstances() {
super.initInstances();
_instance = this;
_imageCache = createImageCache();
shaderWarmUp?.execute();
}
SemanticsBinding.initInstances
平台一些语义,无障碍服务等
void initInstances() {
super.initInstances();
_instance = this;
_accessibilityFeatures = platformDispatcher.accessibilityFeatures;
platformDispatcher
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsActionEvent = _handleSemanticsActionEvent
..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
_handleSemanticsEnabledChanged();
}
RendererBinding.initInstances
Flutter
引擎渲染调度核心
void initInstances() {
super.initInstances();
_instance = this;
_rootPipelineOwner = createRootPipelineOwner();
platformDispatcher
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged;
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
if (kIsWeb) {
addPostFrameCallback(_handleWebFirstFrame, debugLabel: 'RendererBinding.webFirstFrame');
}
rootPipelineOwner.attach(_manifold);
}
注意, 注意, 注意, 这个addPersistentFrameCallback(_handlePersistentFrameCallback)
方法就是无限循环渲染的关键. addPersistentFrameCallback
方法, 在前面已经介绍过了, 是不是忘记了? 往上翻一翻~~
WidgetsBinding.initInstances
一些和Widget
有关的操作
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
// Initialization of [_buildOwner] has to be done after
// [super.initInstances] is called, as it requires [ServicesBinding] to
// properly setup the [defaultBinaryMessenger] instance.
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
platformDispatcher.onLocaleChanged = handleLocaleChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
SystemChannels.backGesture.setMethodCallHandler(
_handleBackGestureInvocation,
);
assert(() {
FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
return true;
}());
platformMenuDelegate = DefaultPlatformMenuDelegate();
}
去掉杂念, 让我们关注 _handlePersistentFrameCallback方法:
/// 代码来自[RendererBinding._handlePersistentFrameCallback]
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
/// 代码来自[RendererBinding.drawFrame]
void drawFrame() {
rootPipelineOwner.flushLayout();
rootPipelineOwner.flushCompositingBits();
rootPipelineOwner.flushPaint();
if (sendFramesToEngine) {
for (final RenderView renderView in renderViews) {
renderView.compositeFrame(); // this sends the bits to the GPU
}
rootPipelineOwner.flushSemantics(); // this sends the semantics to the OS.
_firstFrameSent = true;
}
}
注意到drawFrame
方法了吗? 此方法在WidgetsBinding.drawFrame
被重写了:
void drawFrame() {
assert(!debugBuildingDirtyElements);
assert(() {
debugBuildingDirtyElements = true;
return true;
}());
TimingsCallback? firstFrameCallback;
bool debugFrameWasSentToEngine = false;
if (_needToReportFirstFrame) {
assert(!_firstFrameCompleter.isCompleted);
firstFrameCallback = (List<FrameTiming> timings) {
assert(debugFrameWasSentToEngine);
if (!kReleaseMode) {
// Change the current user tag back to the default tag. At this point,
// the user tag should be set to "AppStartUp" (originally set in the
// engine), so we need to change it back to the default tag to mark
// the end of app start up for CPU profiles.
developer.UserTag.defaultTag.makeCurrent();
developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
}
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
firstFrameCallback = null;
_firstFrameCompleter.complete();
};
// Callback is only invoked when FlutterView.render is called. When
// sendFramesToEngine is set to false during the frame, it will not be
// called and we need to remove the callback (see below).
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback!);
}
try {
//注意此处
if (rootElement != null) {
buildOwner!.buildScope(rootElement!);
}
super.drawFrame();
assert(() {
debugFrameWasSentToEngine = sendFramesToEngine;
return true;
}());
buildOwner!.finalizeTree();
} finally {
assert(() {
debugBuildingDirtyElements = false;
return true;
}());
}
if (!kReleaseMode) {
if (_needToReportFirstFrame && sendFramesToEngine) {
developer.Timeline.instantSync('Widgets built first useful frame');
}
}
_needToReportFirstFrame = false;
if (firstFrameCallback != null && !sendFramesToEngine) {
// This frame is deferred and not the first frame sent to the engine that
// should be reported.
_needToReportFirstFrame = true;
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback!);
}
}
注意到buildOwner!.buildScope(rootElement!);
了吗?
Element
被丢到BuildOwner
对象中, 然后在super.drawFrame
方法中调用了PipelineOwner.flushPaint
完成一帧渲染的所有流程.
此流程在将Widget
直接渲染成图片如出一辙,
直接将Widget
渲染成图片代码如下:
Future<ui.Image> buildImage(Widget widget) async {
final repaintBoundary = RenderRepaintBoundary();
final view = ui.PlatformDispatcher.instance.implicitView ??
RendererBinding.instance.renderView.flutterView;
//渲染树根
final renderView = RenderView(
view: view,
child: RenderPositionedBox(
alignment: Alignment.center,
child: repaintBoundary,
),
configuration: ViewConfiguration.fromView(view),
);
//管道
final pipelineOwner = PipelineOwner()..rootNode = renderView;
renderView.prepareInitialFrame();
//管道对象
final buildOwner = BuildOwner(focusManager: FocusManager());
//根元素
final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
container: repaintBoundary,
child: Directionality(
textDirection: TextDirection.ltr,
child: widget,
)).attachToRenderTree(buildOwner);
//构建树
buildOwner
..buildScope(rootElement)
..finalizeTree();
//渲染树
pipelineOwner
..flushLayout()
..flushCompositingBits()
..flushPaint();
final image = await repaintBoundary.toImage();
return image;
}
总结
runApp
方法的参数app
是怎么到界面上的?
- 使用
RootWidget
包裹app
Widget
- 然后使用
RootWidget.attach
方法将Widget
转变成RootElement
Element
被丢到BuildOwner
对象中- 然后调用
PipelineOwner.flushPaint
完成一帧渲染
到这里, 我们还有一个东西没有介绍RenderObject
, 它要来了…
RenderObject
首先, 并不是所有的Element
都有RenderObject
,
而Element
又是由Widget
变换来的, 所以并不是所有的Widget
都需要RenderObject
.
这你纠正一点, 在Element
中renderObject
是一个get
方法, 所以Element
能获取到renderObject
, 但不一定有值…
/// 代码来自[Element.renderObject]
RenderObject? get renderObject {
Element? current = this;
while (current != null) {
if (current._lifecycleState == _ElementLifecycle.defunct) {
break;
} else if (current is RenderObjectElement) {
return current.renderObject;
} else {
current = current.renderObjectAttachingChild;
}
}
return null;
}
在Flutter
系统里面, 只有RenderObjectElement
才有renderObject
, RenderObjectWidget
会默认创建RenderObjectElement
, 所以…
/// 代码来自[RenderObjectElement.renderObject]
RenderObject get renderObject {
assert(_renderObject != null, '$runtimeType unmounted');
return _renderObject!;
}
RenderObject? _renderObject; //注意这里
这里顺便说一下, 平时使用的
Text
小部件, 文本InlineSpan
是通过渲染对象RenderObject
->RenderParagraph
使用TextPainter
绘制出来的.
平时使用的Image
小部件, 图片ui.Image
是通过渲染对象RenderObject
>RenderImage
使用paintImage
方法绘制出来的
Element
会经历->Element.mount
->Element.update
->Element.unmount
可能不全, 但最核心的就这几个生命周期的回调.
RootElement
根元素的mount
方法在RootWidget.attach
中的BuildOwner.buildScope
方法中调用, 代码在之前已经提到过, 这里再来一次, 不劳烦翻页了.
/// 代码来自[RootWidget.attach]
RootElement attach(BuildOwner owner, [ RootElement? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
//注意这里
element!.mount(/* parent */ null, /* slot */ null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}
而之后子元素Element
的mount
方法就会在inflateWidget
方法中调用了:
Element inflateWidget(Widget newWidget, Object? newSlot) {
...
final Element newChild = newWidget.createElement();
...
//注意这里
newChild.mount(this, newSlot);
...
return newChild;
} finally {
if (isTimelineTracked) {
FlutterTimeline.finishSync();
}
}
}
方法调用链:RootElement.mount
->RootElement._rebuild
->Element.updateChild
->Element.inflateWidget
->Widget.createElement
->Element.mount
火车就这样开起来了…
RenderObjectElement._renderObject
对象就是在RenderObjectElement.mount
方法中调用RenderObjectWidget.createRenderObject
方法赋值的.
/// 代码来自[RenderObjectElement.mount]
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
//注意此处
_renderObject = (widget as RenderObjectWidget).createRenderObject(this);
assert(!_renderObject!.debugDisposed!);
assert(() {
_debugDoingBuild = false;
return true;
}());
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(slot == newSlot);
attachRenderObject(newSlot);
super.performRebuild(); // clears the "dirty" flag
}
然后RenderObject.paint
方法会被调用, 用来绘制, 里面有熟悉的canvas
对象, 这对于Android
开发的同学, 再熟悉不过了把?
/// 代码来自[RenderObject.paint]
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
}
RenderObject
对象中有:
performLayout
方法, 用来实现布局(类似Android
中的onMeasure
和onLayout
)paint
方法, 用来实现自绘(类似Android
中的onDraw
)handleEvent
方法, 用来处理手势事件(类似Android
中的onTouchEvent
)
我将在之后的文章中介绍Flutter
中的自定义控件:
- 自定义自绘
Widget
(类似于自定义Android
中的View
) - 自定义布局
Widget
(类似于自定义Android
中的ViewGroup
)
总结
Widget
是什么?
用来变换成Element
的配置对象.
Element
是什么?
用来创建最终的RenderObject
对象.
RenderObject
是什么?
使用Canvas
绘制的, 界面上能看到的都是绘制出来的. 其余类其实都是控制在什么地方绘制用的.
至此文章就结束了! 感谢读者的宝贵时间~
群内有各(pian)种(ni)各(jin)样(qun)
的大佬,等你来撩.
联系作者
点此QQ对话 该死的空格
点此快速加群