当Hummer升级到2.5.x后,我们发现官方的PlatformView功能仍然不太完善,还存在不少问题,例如,崩溃、黑/白屏、内存泄漏等。另外,PlatformView的性能也被社区吐槽得厉害,特别是Android端。
于是,在今年1月初的时候我们成立了一个优化小专项,希望对PlatformView的已知问题进行一轮集中处理,并探索性能优化方案,快速解决业务痛点。
两个月过去了,PlatformView优化专项已处于收尾阶段,工作基本告一段落。这里向大家同步一下工作进展和阶段成果,以及后续的规划。
阶段成果
立项时明确了两个目标:一是集中解决PlatformView的已知问题;二是探索PlatformView的性能优化方案。
已知问题处理
我们发现并解决了十多个platformview相关的体验问题,涉及各个场景下的黑/白屏、资源泄漏、崩溃,以及死锁等。到目前为止,已知问题已基本扫清。
在此过程中,我们也积极与Flutter社区进行沟通和交流,将我们的一些修复贡献给官方。以下是与PlatformView相关的PR和issue:
PR
Fix compatibility issues with SurfaceTexture on Android Q(Merged)
Using invalidateChildInParent for API levels lower than 26(Merged)
Minor refactor: move flutterViewConvertedToImageView to FlutterView from PlatformViewsController(官方打算弃用HybridComposition,觉得没有必要合并该优化)
Remove the unused FlutterImageView from FlutterView to avoid leaks(Merged)
Check weak pointer to avoid potential use-after-free crash(Merged)
Add synchronization mechanism to avoid deadlock caused by thread(官方觉得全局锁的方案不太优雅,晚点再重新搞个方案)
[android] Fix black and blank screen issues on the platform view page(Merged)
[Android] Add support for disabling dynamic thread merging.(其实,我只是想修复关闭合并线程开关的一些代码问题)
issue
[android][platform_views] Views are not properly updated on Android Q
[shared engine, platform_view, Hybrid Composition] PlatformView flashes white screen
[platform_view][android][shared engine] FlutterImageView leak in hybrid composition
[IOS] Platform-views Deadlock caused by thread merging
[shared engine][android] App crashes when opening two platform view pages one after another in hybrid composition mode
[shared engine][android] The flutter page embedding a platform view using hybrid composition gets a black screen when going back from another
[shared engine][android] The platform view using hybrid composition is invisible when it is opened for the second time.
性能优化探索
性能问题
目前,Android上官方提供了两种PlatformView实现方案:Virtual displays和Hybrid composition。
Virtual displays
Virtual displays方案需要先将Native View绘制到虚显,然后Flutter通过从虚显输出中获取纹理并将其与自己内部的widget树进行合成,最后作为Flutter在 Android 上更大的纹理输出的一部分进行渲染。该方案除了存在性能问题外,更重要的是有很多难以解决的功能性问题,例如,事件/文本输入/可访问性。为了解决这些问题,Flutter官方推出了Hybrid composition,并寄予众望。
Hybrid composition
Hybrid composition是通过ImageReader获取Flutter UI的输出,并将其转为FlutterImageView,然后与Native View进行合成。该方案的确能够解决Virtual displays方案中的一些缺陷,特别是可访问性和键盘相关的问题。
但是,Hybrid composition自身也存在一些性能和稳定性问题:一方面,Raster线程与Platform线程合并带来了潜在的性能损失,以及死锁风险(#issues/94524)。另一方面,在将Flutter UI转为FlutterImageView的过程中,由于Android Q以下的版本不支持直接通过HardwareBuffer创建Bitmap,需要执行copyPixelsFromBuffer函数进行每一帧的内存拷贝,所以在某些场景下性能非常差,这也是社区集中吐槽的点。
下图是Flutter社区一哥们抓的火焰图,借来用用。从图中我们能清楚地看到拷贝内存操作的耗时。
挖洞模式
为了解决上述性能问题,Hummer在早些时候实现了挖洞模式,其核心原理归纳起来有两点:
将Embedded View放到FlutterView下方,并将FlutterView中对应于Embedded View的位置透明化处理(就像是挖了个洞),Embedded View与Flutter的渲染互不干扰,但从用户视觉上看是一个整体
在Raster Thread中计算Embedded View位置、大小、透明度等等的信息,并在Platform Thread中的合适时机根据Raster Thread中的计算结果来更新Embedded View,以此做到线程拆分,并做到了Embedded View与Flutter其他元素的显示同步,不会出现滚动黑边等问题。
挖洞模式确实有很好的性能,但是它自身的局限性也很明显,不支持半透明,矩阵变换,蒙版,各种过滤器特效等。所以,针对挖洞模式不能覆盖的场景,我们还需要继续探索~~
优化思路
Virtual displays方案已逐渐被官方废弃,不需要考虑,我们开始了两个方向的尝试:
对Hybrid composition方案进一步挖掘,例如,不合并线程,避免不必要的FlutterImageView转换;
寻求全新的解决方案——移植Roger大神在我们U4浏览器内核实现的「Embed Surface方案」;
尝试不合并线程
前面分析了,Hybrid composition的性能问题来源于线程合并和内存拷贝,所以我们快速尝试了不合并线程和不转换background类型的FlutterImageView(这里的同步问题暂未处理),以期望能对快速地对性能摸个底,看看优化空间是否足够大,值不值得继续投入优化。
移植Embed Surface
这里有个小插曲,放完春节回来,我们在评估移植U4的「Embed Surface方案」过程中,发现Flutter官方的blasten同学也恰巧在做类似的事情(#pull/31198),并且代码已完成的差不多了。于是决定与他一起完善,这很大程度上节省了我们移植代码的时间。
代码移植完后,一轮测试下来,除了发现一个Android Q上显示白屏(#issues/98722)外,其他表现良好,页面切换也不存在Hybrid Composition的闪黑/白问题,比预期来得顺利。
关于Android Q的显示问题,我们给官方提出了修复方案(#pull/31698),得到了blasten同学的认可,并快速合入了主线。另外一个优化点(#pull/31533),也很快被合入了主线。
测试数据
除了官方支持的VirtualDisplay和HybridComposition外,hummer还提供了挖洞模式(PenetratedDisplay),再加上EmbedSurface和不合并线程的HybridComposition,目前有五种PlatformView方案。针对这几种方案我们设计了对比测试案例。
场景一:listview滑动
在该测试案例(源码:https://github.com/0xZOne/flutter_boost/blob/task/master/example/lib/case/platform_view_perf.dart)中有个listview,Flutter UI和PlatformView作为item,交替出现。测试时,滑动该列表,效果如下面视频所示:
第一组数据
表1(Pixel2,Android 9)
表2(Pixel2,Android 10)
小结:
EmbedSurface与挖洞模式帧率接近,性能最好,大约60帧;
在Android 9上,HybridComposition大约50帧,比EmbedSurface低了10帧左右;
不合并线程的HybridComposition的表现也不如EmbedSurface方案好;
该场景VirtualDisplay性能比较差,不到30帧;
第二组数据
表3 (Mi 8,Android 9)
表4(Pixel3,Android 11)
挖洞模式性能最好,接近60帧;
在Android 9上,HybridComposition大约50帧,比EmbedSurface低了6、7帧左右;
不合并线程的HybridComposition的波动比较大;
该场景VirtualDisplay性能比较差,保持在40帧以下;
场景二:复杂动画
如视频所示,在该场景(源码:https://github.com/0xZOne/flutter_boost/blob/task/master/example/lib/case/native_view_demo.dart)中,中间的「蓝色大嘴巴」是Flutter动画,最上面「跳动的小球」是Native View实现的,而下边「游动的鱼」是嵌入了一个WebView。
第一组数据
表5 (Pixel2,Android 9)
表6(Pixel2,Android 10)
小结:
EmbeddedSurface/VirtualDisplay/挖洞帧率接近,大约60帧,几乎无抖动;
HybridComposition:在Android9上的性能比较糟糕,只有30帧,抖动非常厉害;
Android10上基本可以到60帧,但是也存在抖动;
不合并线程的HybridComposition在Android9上也可以达到56帧,但抖动也比较厉害;
第二组数据
表7(Mi 6,Android 9)
表8(Pixel3,Android 11)
小结:
EmbeddedSurface/VirtualDisplay/挖洞帧率接近,大约60帧,有轻微抖动;
在Android9上,HybridComposition帧率只有25,出现严重抖动和卡顿;
不合并线程的HybridComposition在Android9上接近59帧,存在抖动和轻微卡顿;
测试结论
通过了解实现原理和上面的测试数据,我们可以得出如下结论:
「挖洞模式」的性能仍然是最好的。在挖洞模式能满足的场景下,请考虑优先使用挖洞;
Embed Surface除了仅支持Android6.0及以上外,没有明显短板,其帧率在大多数场景下可以与挖洞模式持平;
从测试数据看,不合并线程的HybridComposition的帧率波动比较大,并不比Embed Surface有优势。另外,因为不能完全避免AndroidQ以下的内存拷贝,同时还需要处理部分场景下的同步问题,所以我们不会继续优化该方案。
另外,官方上了Embed Surface方案后,会直接删除VirtualDisplay实现,估计也会逐渐弃用Hybrid Composition,Android上不再需要合并线程了。
后续工作
因目前Android端的PlatformView的问题比较突出,所以该轮优化主要集中在Android端,后续除了持续优化Android端外,也会更多地关注iOS端,为大家提供更好的开发体验,满足更多的业务场景。
写在最后
UC 内核团队,专注渲染引擎 & 虚拟机技术十数年。作为阿里巴巴集团经济体共建 Flutter 的重要参与方,积极拥抱社区,力求给业务带来最大化的价值提升。Hummer 是我们深度定制优化的 Flutter 引擎,融合了团队在 Web 引擎上的多年技术沉淀。欢迎从事相关技术研究或基于 Flutter 构建应用的同学提出宝贵的意见或建议。
U4内核致力于打造性能最好、最安全的web平台,让web无所不能;
Hummer引擎旨在全面对标native性能,提升native业务开发效率及体验。
关注请长按二维码
喜欢请分享到朋友圈