前言
2018 年最热跨平台技术 Flutter 凌空出世,通过自绘 UI 组件,构建高质量跨平台组件库,解决了此类框架难以解决的双端一致性, Bridge 通信效率等问题。并提供丰富的 Widget 组件,渲染性与 Native UI 相媲美,掀起了大家对下一代跨平台技术探索的热情。同时对国内闲鱼、GCanvas、支付宝、Weex 等都投入了不少研究,通过 Flutter 打造自己的渲染引擎,支持 APP 内业务、小程序等业务。下一代跨平台技术非 Flutter 莫属吗?基于 Flutter 引擎有哪些误区?有木有性能媲美 Flutter 的跨平台渲染技术?本文通过阐述跨平台 UI 渲染引擎的历史,并且实际研究探索构建下一代跨平台 UI 渲染引擎。
1、第一类 WebView 跨平台技术
第一代跨平台技术主要以 Webview 容器,代表有 PhoneGap/Cordova 。优点:功能丰富,标准强,历史悠久,有强大的前端生态支持;是目前最成功的跨平台渲染容器。支付宝及微信更是以此为载体,打造小程序内核。第一代渲染引擎主要缺点在于性能方面及高级组件方面,流畅性始终与 Native 无法媲美。
为何会这样,我们以 Blink 为例从三个方面来看此原因。
1.1 WebView 的基础架构及线程模型
Blink基础架构示意图
Android 平台 WebView 采用多进程架构,主要分为 Browser 进程, Render 进程和 GPU 进程。 Browser 进程负责用户输入, Touch 事件处理、平台相关的对接等功能。 Render 进程 Main Thread 负责 JS 的执行, CSS 解析, Layout Paint ,输出 DisplayList 供 CC 使用。 Work Threads 进行图片的编解码。 Compositor Threads 负责 Layer 的合成,和 Tile 分片;将分片输出成 Bitmap 或者 GL 指令,通过 IPC 输出到 GPU Process 。 GPU Process 的 GPUThread 线程负具体指令的绘制,将绘制指令渲染输出到显示器上。
1.2 WebView渲染流程及线程模型
WebView 的渲染一般从载构建成 DOM Tree 开始算起。下图是 Blink 发起一个样式变更,到最终渲染到屏幕上渲染流程。图片来自于 A Pixel Life In Blink 。
一个像素从更新发起到显示的具体流程
下面是 WebView 渲染具体的具体执行的线程模型:
WebView 的 JS 执行, DOM 构建, RenderObject 的构建, Layout Paint 都在主线程执行。 Compositor Thread 负责 Layer 合成, Worker Thread 进行图片解码及 GPU 光栅化。 GPU Thread 进行最终的指令合成和渲染显示。
上图 Render 进程是与 GPU 进程 GPU Thread 的交互图,WebView 每一帧的更新都需 IPC 调用更新到 GPU Process ,这种 IPC 模型相对线程通信还是比较昂贵的。
1.3 HTML5 作为开放的技术标准,历史悠久,包袱多
HTML5 标准在 Android/iOS 引擎实现不统一, Android 平台 Chrome(blink) , iOS 平台 WKWebview(Webkit) 。标准的实现难度非常高,每个引擎代码行数都在 500-1000 万行;庞大的代码规模导致入门和改动成本比较高,引擎定制成本非常复杂。目前从国内来看, UC /阿里云有能力做内核级别的高级定制开发,其它团队难以进行大规模内核级别的高级定制。无线端 Native 平台成熟的 List Scroller Cell 等高性能组件,无法在 WebView 内核级别做有效的支持。以小程序内嵌 NativeView 所需的同层渲染技术为例,在两个平台要做不同的技术实现。 HTML 规范从提出到落地时间非常长,一般 3-5 年后才能普及,业务难以等待。
1.4 WebView 渲染引擎设计的上的缺陷
- JS Execute,Layout, Paint 都在MainThread ,无法并行化。
- JS 的性能赶不上 Native Tookit 的 Java Dart Object-C 等编译型语言,执行复杂逻辑时会卡顿。
- 渲染流水线非常长,导致浏览器对合成器动画和非合成器动画区分对待,非合成器动画性能不佳。
- OpenGL 设计上是推荐单线程模型,一个 Context 同时只能运行一个线程使用。 GPU Thread 运行在单独 GPU 进程, Render 进程无法访问 GPU 进程的 - OpenGL Context ,两个进程无法 Texture 共享资源。 Render 进程只能输出 Bitmap/Command Buffer 通过 IPC 传递给 GPU 进程,无法直接在 GPU 进程的 Open GL Context 做直接光栅化,难以充分发挥现代 GPU 的性能。
- 光栅化是异步进行的,进行惯性滚动时,会出现白屏,这个是 Webview 始终无法避免的问题。
- 设备平台众多,需要兼容CPU渲染,无法进行 All In GPU 的设计。
2、第二类 Weex/React-Native 跨平台技术
第二类跨平台框架主要以 Weex/React-Native/ 鸟巢等为代表,这种技术最大化的复用前端的生态和 Native 的生态体系,把 Native View 的高性能组件积累输出给前端的技术体系。此方案和浏览器的最大区别在于 Script 的执行和 Native View 渲染体系。
2.1 Weex 的基础架构
Weex 对外通过 Rax 和 Vue 前端框架进行功能输出,前端框架下有一层 JS Framework 来实现 dom 的功能。 WeexCore 负责基础的 Flex Layout ,然后通过 Component 分别对接到 Android/iOS 的 Platform Native View 体系。
2.2 Weex 基础架构 JS 执行上相对 WebView 的优势
Weex 和 WebView 在 JS Execute Layout Paint 线程模型区别
Weex 体系中, JS 的执行在 JS Thread,Layout 执行在独立的 Layout Thread ,渲染执行在系统的 MainThread ,三个线程相互独立,并行执行。在 WebView 的体系中 JS 的执行、 Layout 、 Paint 都在 MainThread ,相互影响,在进行复杂任务时会导致界面卡顿。
2.3 Weex 体系渲染流水线及 OpenGL 设计上和 WebView 区别
Android Native 线程模型和 WebView 线程模型对比如下图:
两图对比可以看出:
- Android Native 采用更轻量级的渲染流水线,能更快更高效的的响应事件。
- RenderThread 直接操作 OpenGLContext ,进行 Direct GPU Raster ,充分发挥现代 GPU 的特性,提供高性能渲染和流畅的体验。
- 部分耗时操作,如 Bitmap 上传 Texture , TextureThread 上传到 Share Open GL Context 中, Texture 完成后通知主线程进行绘制,通过 Share Open GL Context 与主线程共享 Texture 等资源。 WebView 只能在 Render Process 内部进行 Texture 的共享, RenderProcess 无法与 GPU Process 共享 Texture 等资源。
- Android Native 有 RecycleView ViewPager 等高级组件,每个高级组件都做了性能的最佳实践;浏览器上的高级组件只能通过 JS 模拟实现,优化定制效率低。
- 浏览器流水线设计复杂,需要考虑到 PC 、手机、嵌入式设备等多种复杂的环境,不少设备上木有 GPU ,只能进行 CPU 渲染。无法像 Android Native 体系一样进行 All In GPU 的体系设计,全面发挥现代 GPU 的性能。
2.4 Weex 体系在跨平台及性能上的不足
Weex 体系充分将 Native 的 View 体系输出到前端体系中,在进行 Android/iOS Native View 的封装过程中,存在不少难以逾越的障碍。如:难以磨平的双端一致性问题、复杂样式能力难以实现、 Layout 动画需要执行两次(WeexCore Layout 和 Android Native 本身的 Layout )。组件的封装成本随着复杂度增加也越来越高,难以逾越 Native View 限制提供更细致的 W3C 标准能力。
3、第三类 Flutter 跨平台技术
2018 年 Flutter 横空出世,通过 Dart 语言构建一套跨平台的开发组件,所有组件基于 Skia 引擎自绘,在性能上和 Native 平台的 View 相媲美。引起大家广泛关注,充分验证了通过绘制构建组件做到 Native View 媲美的 UI 渲染引擎的可行性。
3.1 Flutter 的基础架构
Flutter基础架构
Flutter 基础架构主要分为三个部分:
Framework 层:包含 Animation Painting Gestures RenderIng Widgets 等模块;提供基础的 UI 组件。
Engine 层包括: Dart VM Manager , Frame Pipeline Rendering , TextLayout 等模块,主要负责 Skia 的渲染调度以及 Layer Tree 等合成。
Embedber 层分别对接 Android/iOS 平台层,进行事件对接, Surface 对接,以及 Native 平台接口调用的插件机制。
3.2 Flutter 的渲染流水线和 Android Native 对比
3.2.1 Flutter 的渲染流水线
Flutter 渲染流水线
Embedber 层收到 VSync 信号,把此信号传递给 Dart VM 中运行 Flutter Framework 。 Dart UI FrameWork 首先处理 Animation 差值,然后更新 Widget Tree ,接着更新 Element Tree ,最后更新 RenderObject Tree ,发起 Paint 流程。再由 SceneBuilder 输出 Layer Tree ,提交到 GPU 线程进行这一帧的阻塞式合成,合成完成后开始下一帧。
3.2.2 Android Native 渲染流水线
Android Native 渲染流水线
Android 系统 Native View Framework 收到 VSync 信号后,首先进行 Touch、Input 等事件处理,再进行 Animation 的更新处理,之后 View Tree 发起 Measure和 Layout 完成布局过程。通过 Draw 把本次更新的脏节点更新的 DispayList 绘制指令同步到 RenderThread 。 RenderThread 通过 DisplayList 对 RenderNode 更新合成,把指令转换成 OpenGL 绘制指令输出到 GPU , 整个流程和Flutter基本相同。
3.3 Flutter 和 Android 在渲染方面相似点和不同点
a、Flutter 和Android 共同点:
采用精简的渲染流水线,从事件到 GPU 更新整体渲染流程很相似
在 GPU 层面进行直接光栅化,充分利用现代 GPU 的高性能渲染性能。
都采用 OpenGL Share Context 进行设计,异步进行图片 Texture 上传,共享图片等 Texture 资源。
最新版本 Android Native 和 Flutter 底层共同采用 Skia 引擎进行合成绘制。
b、Flutter 和 Android 不同点:
Android Native 采用 Java 构建 UIFramework , Flutter 采用 Dart 。
Android Native 支持局部更新,在 Open GL 层面做了非常多的深层次的优化, Flutter 这块工作目前不足。
Android HW UI 是系统应用,可根据手机机型及 GPU 进行参数调优,深度定制;这是 Flutter 框架做不到的。
目前 Android 生态 UI 库比较全面,模块之间融合成本低。 Flutter 自成体系,和 Native View 融合上存在一定成本。
3.4 Flutter 相对 Weex Native 的优势与不足
Flutter 引擎基于 Skia 构建跨平台组件,解决了 Weex 难以解决的双端一致性等问题。上层采用 Dart 语言,没有利用到前端最强大的 JavaScript 生态;和 NativeView 的融合上也存在一些问题,难以复用 Native 多年来积累的强大组件。这些是它相对于 Weex 的不足。在性能方面, Flutter 和 Weex 解决方案,本质上基本相同,实际页面性能取决于最佳实践,目前实际情况看 Weex 的 NativeView 性能优势更强一些。
3.5 在 Flutter 渲染引擎上的探索与实践。
Weex 团队、GCanvas 团队、UC 团队、支付宝团队都在研究 Flutter Engine 。目前主要由 C++ 流和 JavaScript 流两种做法。这两种做法的共同点核心仍采用 Flutter Engine 的渲染流水线,去除 Dart VM ,引入 JavaScript 生态,把 Flutter 标准转换成 W3C 标准对外输出。下面简单介绍一下这两种做法:
Flutter体系向前端输出尝试探索
方案一、通过把 Flutter Widget 整个体系采用 TypeScript 重写,来实现 Flutter 的 JavaScript 的化,上层再基于 JavaScript 的 Widget 封装框架。
方案二、通过把 Flutter的 Framework 采用 C++ 重写,在 Widget 上层封装一层 Component 层,完成 Widget 到 W3C 标准层的转换,然后通过 JavaScript Binding 把 HTML 标准讲功能对外输出。
两种方案都难以把 Flutter 整个体系迁移过来,只能挑选核心的组件进行重写,都是非常不错的尝试。
3.6 Flutter 引擎走 HTML 子集输出的缺陷
Widget 标准对于前端不友好,因此不少团队开始尝试把 Widget 体系转换成前端标准子集进行功能输出。在完善的 Flutter Widget 的前提下,Flutter Widget 通过 Component 封装转换成前端 HTML5 标准进行输出。此方式相对 Weex 封装 Android/iOS 平台 Native View 做法,具有解决 Weex 面临的双端一致性的问题优势。 Weex 从 Native View 到 W3C 的标准转换很难去完美适配,Flutter 的 Widget 实际到 HTML5 标准转换过程中一样会存在。在深层次标准适配时,会出现难以解决的样式和布局能力扩展的问题。由于引擎本身绘制能力可扩展性,这些方面的缺陷相对 Weex 会弱一些。整体来看: Widget 的标准转换到 HTML5 标准只能做部分实现,难以完美适配;进行复杂样式组合时会碰到和标准不一致的现象,难以像下一代定制内核的UI渲染引擎一样高效。
4、前三类跨平台技术的总结
上面整体介绍了前三类的跨平台渲染引擎,下面从特点和技术方面来总结上面的跨平台渲染引擎。
4.1 跨平台渲染引擎的特点上对比
备注: WebView性能目前随着手机性能的提升而逐步提升,在某些高端机上简单页面的性能已经非常流畅,但在复杂页面尤其是有交互的页面和Weex还是有一定的性能差距。
4.2 跨平台渲染引擎的技术上进行对比
此表格概括了前三代跨平台渲染引擎的技术特点,下一代跨平台 UI 渲染引擎会是什么样子?
5、展望更标准轻量级的UI渲染引擎
Web 技术作为目前最广泛的的应用平台,各种技术 Weex/React-Native/QT 都采用 Web 技术作为最有效的输出手段。 Flutter Widget 标准只是 Flex 的子集,用 Flutter Widget 转换成 HTML 子集不一定是下一代渲染引擎的最佳实践。下一代渲染引擎应充分吸收上一代的优势,继续开拓进取;那下一代跨平台渲染引擎会有哪些特点呢?我个人认为下一代渲染引擎应具备以下的特点:
面向前端设计,支持最广泛的 JavaScript 语言及部分 CSS HTML 标准; JS 独立执行和 Layout 分离,并行执行。
面向 GPU 设计,轻量级高性能渲染流水线。
在内核级别支持 Web 标准,能完善的扩展 Web 所需要的特性,无需标准的中间转换层,如 Yoga 。
Web 标准之外,支持 List Scroller Slider 等 Native 高级组件,解决复杂页面性能低,内存占用大的问题。
内核支持多种手势,如 PanGesture ScrollGesture ScaleGesture 等,在内核层面组件直接处理这些手势。
5.1 下一代 UI 渲染引擎基础架构
底层布局和样式实现参考 Web 思想,在 Web 标准之外,构建一套高性能的 UI Framework ,然后向上对接 JS Engine 供上次的 Rax Vue 框架使用。
5.2 下一代渲染引擎渲染流水线及 Open GL 设计
下一代渲染引擎流水线及OpenGL设计
UI Framework 采用 Native Tookit 的轻量级渲染流水线设计,在 GPU 层面进行 Direct GPU Raster 光栅化。通过 OpenGL Share Context 高效共享纹理资源。渲染性能做到和 Native View 基本相当,打造一个轻量级高性能 UI 渲染引擎。
5.3 下一代渲染引擎和WebView的区别
1、面向 GPU 设计, Direct GPU Raster ;不考虑 CPU 端合成情况,充分发挥现代 GPU 的高性能特性。
2、轻量级渲染流水线,轻量级 Layer Tree 更高效的响应页面变化。
3、JavaScript 执行在独立线程中, Layout Paint 并行执行,繁重的 JavaScript 任务不影响 UI 流畅度。
4、通过 Open GL Share Context 进行 Bitmap 异步上传和 Texture 资源共享。
5、UI 渲染引擎本身不包含 Dom,JSBinding 等功能,专注高性能 UIKit 渲染引擎。
6、基础布局和样式实现参考Web思想,但在 Web 标准之外构建一套完整的高性能 UI Framework。
7、设计机制上杜绝 WebView 快速滑动过程的白屏,通过高性能UI组件解决 Web 性能及内存占用问题。
5.4 下一代渲染引擎实际性能
下一代渲染引擎性能到底如何,实际可行度高吗?目前Demo已经完成滚动组件开发,实测滚动流畅性和 Weex Native List 及 Flutter 列表相当。在某些复杂场景,甚至优于 Weex Native List 的流畅度。在样式能力方面,远超 Weex/Flutter 等目前解决方案。
6、最后总结
本文主要阐述目前主流的跨平台渲染引擎以及目前关于渲染引擎的探索,目前各种渲染引擎并不是一成不变的,都在蓬勃发展。如 Blink 的 Slimming Paint 项目采用和 Native View 相同的策略, Render Layer 只输出 Display List ,由 CC 根据策略是采用 Layer Compositor 还是 Direct GPU Raster 思路? Firefox 的 WebRender 渲染引擎,尝试把 JS 的执行, Layout、Paint 的执行并行化,并采用更高级的 GPU 绘制元素的方式。相信未来一定会涌出不少优秀的跨平台 UI 渲染引擎。