对比图
webview 渲染流程
WebKit 的渲染层来自以前 macOS 的 Layer Rendering 架构,而 iOS 也是基于这一套架构。
从本质上来看,WebKit 和 iOS 原生渲染差别不大
。
但是为什么原生渲染会比webView渲染效果好很多呢?
-
1、初始化webView过程耗时
-
2、
WebView的渲染进程是独立的,每一帧的更新都要通过IPC调用GPU进程,会造成频繁的IPC进程通信,从而造成性能消耗
。并且,两个进程无法共享纹理资源,GPU无法直接使用context光栅化,而必须要等待WebView通过IPC把context传给GPU再光栅化
。因此GPU自身的性能发挥也会受影响手势
、动画
这两个会比较消耗性能- 手势检测带来的像素级动画会极大的降低帧率
- IPC(Inter-Process Communication,进程间通信)。进程间通信是指两个进程的数据之间产生交互
-
3、无限列表:目前为止,web端的无限列表和原生列表完全不在一个层次,流畅度和体验都特别差。一些号称能够解决这个问题的第三方js库,都有性能问题,特别是在安卓机上面,更是糟糕。无线端web对于无限列表几乎无解(从和原生列表的性能差距角度考虑)。
-
4、路由切换:web端路由切换体验也无法和原生相比。原生端可以轻松做到在路由切换的动画过程中完成下个页面的渲染,只有很小的帧率损失
无限列表
- 元素节点限制:一个太大的 WXML 节点树会增加内存的使用,样式重排时间也会更长,影响体验。微信小程序官方建议一个页面 WXML 节点数量应少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个。这种现象的本质原因是每次 setData 都需要创建新的虚拟树和旧树 Diff 操作耗时都比较高。
- 图片性能和内存的影响:长列表一般都会有大量的图片,会导致内存占用急剧上升。内存增长如果超过了限制,也会导致小程序出现白屏或黑屏,甚至整个小程序发生闪退。
- setData 频率以及数据量的影响:长列表的情况下,会有很多列表单元项,如果每个 item 内部都触发 setData,会造成 setData 的频率急速上升,并且在向每一个 item 注入数据的时候,会造成数据量传输过大,这也是一种性能的开销。而且,如果在首次渲染过程中,加载大量的数据,就会造成首次 setData 的时候耗时高。
原生性能为什么比较好
- iOS 处理长列表的主要方案是使用 UITableView 或 UICollectionView。这些控件使用可重用的单元格来显示数据,而不是为每个单元格创建一个新视图,从而大大减少了内存占用和开销,并提高了性能。另外也可以采用分页加载、延迟加载、滚动预加载等技术来进一步优化并提高用户体验。
- 安卓的RecyclerView 是一种高度可定制的列表视图,用于在有限的屏幕空间中显示大量数据。它可以通过重用现有的视图来显著提高性能
路由切换
在单页面应用中,可以通过 router 控制路由的状态,如果要改变路由跳转到新页面,本质上还是要通过路由跳转,先找到对应的路由组件,再卸载掉之前组件,然后渲染新的组件
小程序渲染流程
程序采用双线程架构,分为逻辑层和渲染层。
-
首先也是 Native 打开一个 WebView 页面,渲染层加载 WXML 和 WXSS 编译后的文件
- View 视图端通过小程序的框架将用户采用 WXML 和 WXSS 描述的UI信息处理成 H5 元素,最终交给 WebView 去渲染;
-
同时逻辑层用于逻辑处理,比如触发网络请求、setData 更新等等。接下来是请求资源,请求到数据之后,数据先通过逻辑层传递给 Native,然后通过 Native 把数据传递给渲染层 WebView,再进行渲染。
- App Service 端运行用户编写的 JavaScript 逻辑,并且可以调用具有微信开放能力的 JSAPI。逻辑和视图分离,通过事件和数据彼此之间建立联系
同层渲染
同层渲染:让原生组件和前端元素渲染在同一个层级上
为什么会有同层渲染:一些比较消耗性能的操作:地图、视频
-
在 iOS 上的 webview 容器是 WKWebview,它有这么个特性:如果 WKWebview 中的某个元素支持滚动,那么 iOS 会开启一个 UIScrollView(或是一个继承于 UIScrollVIew 的 WKChildScrollView)
- 前端通过一些手段让某个元素可滚动,并给这个容器一个唯一 id
- 通过 jsbridge 告诉端容器 id,同时告诉端我们需要什么类型的原生控件
- 端找到这个容器,并往里面的 UIScrollView 绘制原生控件
-
与 iOS 有明确改造方向不同,安卓 webview 的自定义性更强,方案选择也更自由
- 方案1:直接让 webview 控件上升至最高层级,将原生控件绘制在 webview 容器的下方,在 webview 上需要原生控件的地方渲染成透明
- 方案2:embed标签:可以由前端控制生成,同时端上也能对其进行操作,并且还符合 web 标准表现
小程序渲染方式
1、为什么有webview以外的渲染方式
- 1、字体一致性体验问题。微信小程序使用 WebView 渲染,与原生客户端的是两套不同的视图渲染体系,在 Android 平台上出现了无法跟随系统字体保持一致的问题,体验上会有较为明显的割裂感
- 2、大量的图片和视频混排的场景下,会出现一些掉帧现象,在 Android 中低端机上较为明显。如下图所示,在图片滑动等连续过程中,会偶尔出现 LAG 的情况。并且受目前小程序框架所限,视频、图片的全屏显示效果也不够理想
2、为了解决上面问题,小程序首先提出了使用RN-like
当 WXML/WXSS 发生改变,也就是 UI 页面发生改变的时候,小程序前端公共库(WXA Framework)会进行一些内部运算,以操作指令的形式将变化结果提交给 C++,具体的布局计算、CSS 样式更新和 DOM 结构变化都是由 C++ 这一层实现的。C++ 计算完成后,再决定是调用 WebView 渲染组件,还是调用原生视图渲染组件
在使用RN-like时,性能的确提高了不少,但是也迎来了新的问题
需要在各个平台去做适配是一个巨大工作量的事情,而且后续的维护成本也将无法预测。
基于 Web 的渲染满足不了性能和体验的要求,基于原生渲染又会带来高维护成本问题,我们需要一个跨平台的渲染方案来解决。在研究各种可能的方案的时候,Flutter 再次走进了我们的视野。
3、Flutter使用
汇总 Flutter 渲染解决的问题,基本上看是能够满足我们在性能和体验上的诉求的:
- 字体不一致问题:通过自定义 Flutter Engine 实现跟随系统原生视图字体;
- 视频、地图等同层渲染:Flutter 官方提供了一种机制,通过 Texture Widgets 的方式将 Native 平台渲染的 Texture 同步到 Flutter 的渲染体系中来,保证同一时刻界面上仅存在一种视图体系;
- 文本输入框:Flutter 官方提供了较为完整的输入框控件;
- 性能提升:相比 WebView 在低端机上有可见的性能指标提升;
- 减少重复资源投入,多平台维护:基本上只需要维护 Dart 和 C++ 代码,平台相关代码可以最小化。
因为稳定性原因, Flutter 渲染目前仅仅使用在微信内部
微信小程序是一个独立的生态和产品,使用 WebView 渲染具有极大的灵活性和前端兼容性,不会放弃 WebView 渲染。目前我们的尝试仅限于微信客户端内部部分场景使用,对微信小程序的外部开发者不会有任何影响。
React Navive
优点
- 1、开发效率高,一套代码可以在 Android、iOS 上运行;
- 2、更新部署方便,无须依赖应用市场发版,迭代更新速度快;
- 3、具备动态更新能力,特别是国内 App,以 Android 为例,受限于 Google Play,无法使用 Android App bundle,而插件化框架又存在稳定性问题。而业务快速迭代、Bug 响应速度都对动态更新能力有强烈的需求。
- 4、前端开发者可以很容易的开发app
缺点
- 1、由于 React Native 的链路比较长、涉及客户端、前端、后端,且 React Native 框架输出的日志不够多,排查问题比较困难
- 2、性能不佳。H5 渲染链路长;React Native 依托于 JS bridge 交互
- 3、兼容性差。Android、iOS 各版本都存在各种兼容性问题,特别是 Android 碎片化严重;
- 4、动态化能力受限。相比纯原生的插件化,跨端框架动态更新的业务如果涉及 Native 部分的组件更新,需要依赖 App 发版。
Flutter
可以看出Flutter渲染路径也是非常短的,而且是自有skia渲染引擎,速度也是非常快的
优点
-
Dart 运行时和编译器支持 Flutter 的两个关键特性的组合:
-
基于 JIT 的快速开发周期:Flutter 在开发阶段采用 JIT 模式,这样就避免了每次改动都要进行编译,极大地节省了开发时间;
-
基于 AOT 的发布包: Flutter 在发布时可以通过 AOT 生成高效的机器码以保证应用性能。而 JavaScript 则不具有这个能力。
-
-
2、渲染速度快
缺点
需要掌握新的语言