Flutter platform view 避坑指南,你应该知道的技术细节之原理篇
上文使用篇介绍了如何使用原生的view来创建一个flutter widget。
但是,细心的同学应该能够注意到,官方文档UiKitView里面有下面这一段话,大体意思是嵌入原生的view是一个比较昂贵的操作(指性能不咋地)。
Embedding iOS views is an expensive operation and should be avoided when a Flutter equivalent is possible.
那么我们就来分析一下,flutter到底是如何去实现这个嵌套native view功能的,其性能具体又如何呢?
platform view的渲染过程
Flutter的渲染循环
首先我们来看看flutter的渲染循环具体有哪些步骤,借用一下官方的这个图:
首先,每帧都是由vsync信号触发(在iOS上实际上是由CADisplayLink来触发的,具体可看VsyncWaiterIOS.mm的代码)
然后,在UI Thread由dart层处理3种树(widget、Element、RenderObject)的生成与更新
真正的渲染工作是在engine层处理的,其接受dart层传递的layerTree,然后通过GPU渲染出来。注意这里layerTree和RenderObject还有区别,并不是每一个renderObject节点都有对应的layer节点。
Engine层渲染layerTree
因为在dart侧生成layerTree的过程中,platform view widget的处理逻辑和普通的widget是一致的,最终处理都是生成一个layerTree。
所以我们这里主要关注engine层渲染layerTree的过程,platform view实际上会是layerTree上的一些叶子节点。
各种layer子类
通过xcode的代码跳转提示,可以看到layerTree的叶子节点多种多样,那么和我们platform view相关的就是PlatformViewLayer
了。当遍历到这种layer的时候,flutter就知道这里需要渲染一个native view了。
查看PlatformViewLayer相关代码,可以看到其具体逻辑都是由FlutterPlatformViewsController
处理的。
FlutterPlatformViewsController的关键逻辑
onCreate
1 | void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult& result) { |
onCreate是由dart层触发,这块代码看过上篇使用篇的同学应该比较熟悉。关键点在于:
- 调用PlatformViewFactory来创建具体的native view,从使用篇mapview的demo里面,我们可以知道我们可以通过FlutterPlatformView协议的view方法拿到具体的native view实例
- 除了FlutterPlatformView之外,flutter还创建了一个
FlutterTouchInterceptingView
,实际上这个view是作为我们native view的父view存在的,作用是拦截或者传递native view的一些手势事件。
SubmitFrame
1 | bool FlutterPlatformViewsController::SubmitFrame(GrContext* gr_context, |
几个关键变量先解释一下:
- flutter_view 我们所有的flutter UI在iOS侧都是由
FlutterViewController
承载,这个flutter_view
就是FlutterViewController
的根view - composition_order_ 这里面是根据layerTree的结构,构造的一个viewId的数组。可以理解数组里面靠前的viewID会更靠近底层一些。
- platform_view_root 这里可以简单理解为是factory创建的native view对象。(实际上flutter可能会在这个view上包一些
ChildClippingView
) - overlay 这个overlay是解决native view与flutter其他widget遮挡关系的关键,后面我们详细介绍。
然后有2个关键逻辑:
- platform_view_root是直接作为subview添加到flutter_view上面的,这说明真正的platform_view是由原生来渲染的。并没有什么先渲染到纹理,然后再转给flutter当做图片渲染的逻辑。这部分纯渲染的性能是足够可靠的。
- 每一个platform_view_root后面都跟了一个overlay,这个overlay才是提供给flutter侧做widget渲染的skia环境。在platform_view上层的flutter widget,可能就会被渲染到这个platform_view上面的overlay view上;而platform_view底层的flutter widge,可能会被渲染到更底层的overlay view或者flutter_view(根view)的skia环境上。
大部分性能问题,应该就出在这个overlay view上面。
flutter Demo的层级结构查看
上面代码分析了一大堆,我们直接看下xcode view的层级结构:
这里层级结构比较简单,由于我们嵌入了一个QMapView(这个是native view的类型)。flutter自动添加了一个FlutterOverlayView,然后其他所有的flutter widget都被绘制到这个overlay view上面去了,由于直接是绘制到gl layer上的,所以xcode是看不到flutter widget的层级的。
Ps. 如果所有的view都是由flutter widget绘制的,并没有插入platform view的话,我们会发现所有的UI是直接绘制到FlutterView上的。这里就不贴图了,有兴趣的小伙伴可以自己去看一下。
多个platform view嵌套的场景
整个widget的结构和对应的UI层级结构是这样的:
Stack
– Container(red)
– Button(定位)
– mapview0
– Container(yellow)
– mapview1
ListView
对应右边的UI层级结构图,我们可以看到Stack最底层的Red container,被绘制到FlutterView上了,其会被其他的mapview遮挡。
位于2个mapview中间的yellow container,是绘制到第mapview0的overlay上面的,这样黄色的container widget就能遮挡住mapview0,而被mapview1遮挡住。
总结
通过查看代码,我们可以了解到,Platform View是直接通过渲染一个native view的方式实现的,这部分并不是性能问题的根源。
为了解决platform view和普通flutter view的遮挡关系,每个platform view实例的引入,flutter都会对应引入一个全屏的、可以用skia渲染组件的overlay view。这个overlay view就是影响flutter app性能的关键点。
具体分析我们可以查看性能篇
- 本文链接:http://www.luoyibu.cn/posts/63773/
- 版权声明:本博客所有文章除特别声明外,均采用CC BY-SA 4.0许可协议。转载请注明出处!