阿里妹导读:Flutter从本质上来讲还是一个UI框架,它解决的是一套代码在多端渲染的问题。在渲染管线的设计上更加精简,加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具有更好的性能体验。本文将从架构和源码的角度详细分析Flutter渲染机制的设计与实现。较长,同学们可收藏后再看。
文末福利:下载《AliFlutter 体系化建设和实践》电子书。
效率:解决在多应用、多平台、多容器上开发效率的问题,一码多端,业务快跑。
性能:解决的是业务的性能和体验问题。
WebView渲染:依赖WebView进行渲染,在功能和性能上有妥协,例如PhoneGap、Cordova、小程序(有的小程序底层也采用了ReactNative等渲染方案)等。
原生渲染:上层拥抱W3C,通过中间层把前端框架翻译为原生控件,例如ReactNative+React、Weex+Vue的组合,这种方案多了一层转译层,性能上有损耗。随着原生系统的升级,在兼容性上也会有问题。
自建渲染:自建渲染框架,底层使用Skia等图形库进行渲染,例如Flutter、Unity。
1)UI Thread
对应图中1-5,执行Dart VM中的Dart代码(包含应用程序和Flutter框架代码),主要负责Widget Tree、Element Tree、RenderObject Tree的构建,布局、以及绘制生成绘制指令,生成Layer Tree(保存绘制指令)等工作。
2)GPU Thread
对应图中6-7,执行Flutter引擎中图形相关代码(Skia),这个线程通过与GPU通信,获取Layer Tree并执行栅格化以及合成上屏等操作,将Layer Tree显示在屏幕上。
注:图层树(Layer Tree)是Flutter组织绘制指令的方式,类似于Android Rendering里的View DisplayList,都是组织绘制指令的一种方式。
void handleBeginFrame(Duration rawTimeStamp) {
...
try {
// TRANSIENT FRAME CALLBACKS
Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
_schedulerPhase = SchedulerPhase.transientCallbacks;
final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
...
} finally {
...
}
}
postFrameCallbacks用来通知监听者绘制已经完成。
pesistentCallbacks用来触发渲染。
_transientCallbacks:用于存放一些临时回调,目前是在 Ticker.scheduleTick() 中注册,用来驱动动画。
_persistentCallbacks:用来存放一些持久回调,不能在此回调中再请求新的绘制帧,持久回调一经注册就不嫩嫩移除, RenderBinding.initInstaces().addPersitentFrameCallback() 添加了一个持久回调,用来触发 drawFrame()。
_postFrameCallbacks:在Frame结束时会被调用一次,调用后会被移除,它主要是用来通知监听者这个Frame已经完成。
void drawFrame() {
...
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
assert(() {
debugBuildingDirtyElements = false;
return true;
}());
}
...
}
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;
}
}
树构建(应用启动时):我们上面提到的 runApp() 方法调用的 scheduleAttachRootWidget() 方法,它会构建Widgets Tree、Element Tree与RenderObject Tree三棵树。
树更新(帧绘制与更新时):这里不会重新构建三棵树,而是只会更新dirty区域的Element。
Widget Tree:为Element描述需要的配置,调用createElement方法创建Element,决定Element是否需要更新。Flutter通过查分算法比对Widget树前后的变化,来决定Element的State是否改变。
Element Tree:表示Widget Tree特定位置的一个实例,调用createRenderObject创建RenderObject,同时持有Widget和RenderObject,负责管理Widget的配置和RenderObjec的渲染。Element的状态由Flutter维护,开发人员只需要维护Widget即可。
RenderObject Tree:RenderObject绘制,测量和绘制节点,布局子节点,处理输入事件。
相关文档:Understanding constraints
相关源码:PipelineOwner.flushLayout()
RenderFlex:弹性布局,这是一种很常见的布局方式,它对应的是Widget组件Flex、Row和Column。关于这一块的布局算法代码注释里有描述,也可以直接看这篇文章的解释。
RenderStack:栈布局。
边界约束(Constraints):边界约束是父节点用来限制子节点的大小的一种方式,例如BoxConstraints、SliverConstraints等。
minWidth
maxWidth
minHeight
maxHeight
重新布局边界(RelayoutBoundary):为一个子节点设置重新布局边界,这样当它的大小发生变化时,不会导致父节点重新布局,这是个标志位,在标记dirty的markNeedsLayout()方法中会检查这个标记位来决定是否重新进行布局。
Dart层调用入口:painting.dart
C++层实现:canvas.cc
重新绘制边界(RepaintBoundary):为一个子节点设置重新绘制边界,这样当它需要重新绘制时,不会导致父节点重新绘制,这是个标志位,在标记dirty的markNeedsPaint()方法中会检查这个标记位来决定是否重新进行重绘。 事实上这种重绘边界的机制相对于把图层分层这个功能开放给了开发者,开发者可以自己决定自己的页面那一块在重绘时不参与重绘(例如滚动容器),以提升整体页面的性能。重新绘制边界会改变最终的图层树(Layer Tree)结构。
ClipRectLayer:矩形裁剪层,可以指定裁剪和矩形行为参数。共有4种裁剪行为,none、hardEdge、antiAlias、antiAliashWithSaveLayer。
ClipRRectLayer:圆角矩形裁剪层,行为同上。
ClipPathLayer:路径裁剪层,可以指定路径和行为裁剪参数,行为同上。
OpacityLayer:透明层,可以指定透明度和偏移(画布坐标系原点到调用者坐标系原点的偏移)参数。
ShaderMaskLayer:着色层,可以指定着色器矩阵和混合模式参数。
ColorFilterLayer:颜色过滤层,可以指定颜色和混合模式参数。
TransformLayer:变换图层,可以指定变换矩阵参数。
BackdropFilterLayer:背景过滤层,可以指定背景图参数。
PhysicalShapeLayer:物理性状层,可以指定颜色等八个参数。
Dart层调用入口:compositing.dart widow.dart
C++层实现:scene.cc scene_builder.cc
注:这个地方官方的说法叫Compositing,不过我觉得叫Compositing有歧义,因为它并不是在合成,而是把Layer Tree提交给GPU Thread,因而我觉得叫Submit更合适。
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineArgumentsIndicatingLandmarkEvent);
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
_window.render(scene);
scene.dispose();
assert(() {
if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
return true;
}());
} finally {
Timeline.finishSync();
}
}
创建SceneBuilder对象,并通过 SceneBuilder.addPicture() 将上文中生成的Picture添加到SceneBuilder对象对象中。
通过 SceneBuilder.build() 方法生成Scene对象,接着会通过window.render(scene)将包含绘制指令的Layer Tree提交给CPU线程进行光栅化和合成。
光栅化是把绘制指令转换成对应的像素数据,合成是把各图层栅格化后的数据进行相关的叠加和特性处理。这个流程称为Graphics Pipeline。
光栅化和合成在一个线程,或者通过线程同步等方式来保证光栅化和合成的的顺序。 直接光栅化:直接执行可见图层的DisplayList中可见区域的绘制指令进行光栅化,在目标Surface的像素缓冲区上生成像素的颜色值。 间接光栅化:为指定图层分配额外的像素缓冲区(例如Android提供View.setLayerType允许应用为指定View提供像素缓冲区,Flutter提供了Relayout Boundary机制来为特定图层分配额外缓冲区),该图层光栅化的过程中会先写入自身的像素缓冲区,渲染引擎再将这些图层的像素缓冲区通过合成输出到目标Surface的像素缓冲区。
图层会按照一定的规则粉尘同样大小的图块,光栅化以图块为单位进行,每个光栅化任务执行图块区域内的指令,将执行结果写入分块的像素缓冲区,光栅化和合成不在一个线程内执行,并且不是同步的。如果合成过程中,某个分块没有完成光栅化,那么它会保留空白或者绘制一个棋盘格图形。
LayerTree::Preroll():处理绘制前的一些准备工作。
LayerTree::Paint():嵌套调用不通Layer的绘制方法。
SkCanvas::Flush():将数据flush给GPU。
AndroidContextGL::SwapBuffers():交换帧缓存给显示器显示。
拥抱W3C生态
相对稳定性
前端的学习成本增加,小程序的DSL还算简单,Flutter的Widget体系学习起来就需要花上一点时间,这些对于团队来说都是成本。
业务代码重写,大量逻辑需要梳理,而且老业务并不一定都适合迁移到新容器上,比如小程序本来就是个很轻量的解决方案,但是我们在上面堆积了很多功能,造成了严重的体验问题。
统一API解决方案
统一性能解决方案
统一组件解决方案
统一配套设施解决方案
等等
欢迎加入本地生活终端技术部!
本地生活终端技术部隶属于阿里本地生活用户技术部,从事客户端技术研发工作,主要负责本地生活饿了么App 和 口碑App 的客户端架构、基础中间件、跨平台技术解决方案,以及账号、首页、全局购物车、收银台、订单列表、红包卡券、直播、短视频等平台化核心业务链路。目前团队规模50+人,我们依托阿里强大的终端技术底盘,以及本地生活的业务土壤,致力于打造最优秀的O2O技术团队。
招聘本地生活-客户端开发专家/高级技术专家-杭州/上海/北京,欢迎您的加盟!简历发送至 wushi@alibaba-inc.com
附录 相关平台 [1]Flutter pub.dev (https://pub.dev/flutter/packages) 相关文档 [1]Flutter 官方文档
(https://flutter.dev/docs/get-started/install/macos)[2]Flutter for Android developers
(https://flutter.dev/docs/get-started/flutter-for/android-devs)[3]Flutter Widget Doc
(https://flutter.dev/docs/reference/widgets)[4]Flutter API Doc(https://api.flutter.dev/) [5]Dart Doc (https://dart.dev/guides/language) 相关源码 [1]Dart Framework
(https://github.com/flutter/flutter/tree/master/packages)[2]Flutter Engine (https://github.com/flutter/engine) 相关资源 [1]Flutter Render Pipeline
(https://www.youtube.com/watch?v=UUfXWzp0-DU)[2]How Flutter renders Widgets
(https://www.youtube.com/watch?v=996ZgFRENMs)[3]深入了解Flutter的高性能图形渲染 video
(https://www.bilibili.com/video/av48772383)[4]深入了解Flutter的高性能图形渲染 ppt
(https://files.flutter-io.cn/events/gdd2018/Deep_Dive_into_Flutter_Graphics_Performance.pdf)