跨端技术概述|青训营笔记

132 阅读7分钟

这是我参与「第五届青训营 」笔记创作活动的第10天

跨端技术概要

跨端背景

随着业务的发展,产生了越来越多的业务场景,同时随着技术的发展,产生了越来越多的端,PC端(Windos、Mac),移动端(安卓、iOS)、web端、IOT设备(车载设备、手表等)

常见的痛点:

  1. 各端功能几乎一致,各端需要单独配置研发人员
  2. 开发、维护成本高
  3. 安卓、iOS发版周期长(需要在应用商店中审核和检测)
  4. 安卓、iOS各端出现的bug不一致

跨端技术方案目标

  1. 研发效率高

    • 学习成本低
    • 多端一致性高(便于维护)
  2. 用户体验好

    • 稳定性好
    • 性能体验好
  3. 动态化

    • 支持动态化下发,满足日益增长的业务需求

跨端技术方案介绍

hybrid方案

基于 WebView(简单通俗可理解为一个浏览器) 渲染,通过JS Bridge 把一部分系统能力开放给 JS 调用

image.png

image.png

WebView容器的工作原理是:

基于Web技术来实现界面和功能,通过将原生的接口封装、暴露给JavaScript调用JavaScript编写的页面可以运行在系统自带的WebView中。

优势:对于前端开发者比较友好,可以很快地实现页面跨端,同时保留调用原生的能力,通过搭建桥接层和原生能力打通。

缺陷:跨端的能力受限于桥接层,当调用之前没有的原生能力时,就需要增加桥。另外,浏览器内核的渲染独立于系统组件,无法保证原生体验,渲染的效果会差不少

原生渲染方案

使用JS开发,通过中间层桥接后使用原生组件来渲染UI界面

在 Android 开发中是使用 KotlinJava 来编写视图;在 iOS 开发中是使用 SwiftObjective-C 来编写视图。

在 React Native 中,则使用 React 组件通过 JavaScript 来调用这些视图。在运行时,React Native 为这些组件创建相应的 Android 和 iOS 视图。由于 React Native 组件就是对原生视图的封装,因此使用 React Native 编写的应用外观、感觉和性能与其他任何原生应用一样。 我们将这些平台支持的组件称为原生组件。

image.png

JSI

JSI是Javascript Interface的缩写,一个用C++写成的轻量级框架,它作用就是通过JSI,JS对象可以直接获得C++对象(Host Objects)引用,并调用对应方法。

有了JSI,JS和Native就可以直接通信了,调用过程如下:JS->JSI->C++->ObjectC/Java

JSI是整个架构的核心和基石,所有的一切都是建立在它上面。

JSI 将支持其他 JS 引擎;

JSI 允许线程之间的同步相互执行,不需要 JSON 序列号等耗费性能的操作;

JSI 是用 C++ 编写,以后如果针对电视、手表等其他系统,也可以很方便地移植

自此三个线程通信再也不需要通过Bridge,可以直接知道对方的存在,让同步通信成为现实。具体的用法可以看 官方例子。

另外一个好处就是有了JSI,JS引擎不再局限于JSC,可以自由的替换为V8,Hermes,进一步提高JS解析执行的速度。

Fabric

Fabric 是新的渲染系统,它将取代当前的 UI Manager。

UI Manager

当 App 运行时,React 会执行你的代码并在 JS 中创建一个 ReactElementTree ,基于这棵树渲染器会在 C++ 中创建一个 ReactShadowTreeUI Manager 会使用 Shadow Tree 来计算 UI 元素的位置,而一旦 Layout 完成,Shadow Tree 就会被转换为由 Native Elements 组成的 HostViewTree(例如:RN 里的 会变成 Android 中的 ViewGroup 和 iOS 中的 UIView)。

而之前线程之间的通信都发生在 Bridge 上,这就意味着需要在传输和数据复制上耗费时间。通过JSON格式来传递消息,每次都要经历序列化和反序列化。

而得益于前面的 JSI, JS 可以直接调用 Native 方法,其实就包括了 UI 方法,所以 JS 和 UI 线程可以同步执行从而提高列表、跳转、手势处理等的性能。

Turbo Modules

之前的架构中 JS 使用的所有 Native Modules(例如蓝牙、地理位置、文件存储等)都必须在应用程序打开之前进行初始化,这意味着即使用户不需要某些模块,但是它仍然必须在启动时进行初始化。

Turbo Modules 基本上是对这些旧的 Native 模块的增强,正如在前面介绍的那样,现在 JS 将能够持有这些模块的引用,所以** JS 代码可以仅在需要时才加载对应模块,这样可以将显着缩短 RN 应用的启动时间。**

image.png

React Native 的思路:

最大化地复用前端的生态和 Native 的生态,和 WebView 容器的最大区别在于 View 的渲染体系。React Native 抛弃了低效的浏览器内核渲染,转而使用自己的 DSL 生成中间格式,然后映射到对应的平台,渲染成平台的组件。

相对 WebView 容器,体验会有一定的提升。不过,渲染时需要 JavaScript 和原生之间通信,在有些场景可能会导致卡顿且在样式上存在缺失,部分效果不能实现

另外就是,渲染还是在Native层,要求开发人员对Native有一定的熟悉度。

自渲染方案

利用Skia重新实现渲染管线,不依赖原生组件

image.png

image.png

image.png

小程序方案

使用小程序DSL+JS开发,通过中间层桥接调用原生能力,使用webview来渲染UI界面

image.png

首先,我们来简单了解下小程序的运行环境。小程序的运行环境分成渲染层和逻辑层,其中 ttml 模板和 ttss 样式工作在渲染层,js 脚本工作在逻辑层。

小程序的渲染层和逻辑层分别由2个线程管理

  • 渲染层的界面使用了WebView 进行渲染
  • 逻辑层采用 JSC 线程运行 JS 脚本。

一个小程序存在多个界面,所以渲染层存在多个 WebView 线程,这两个线程的通信会经由客户端(下文中也会采用 Native 来代指客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型如图所示

image.png

跨端技术对比

image.png

小程序性能优化

  1. 留住用户
  2. 提高转化率
  3. 提高用户体验

性能指标

为了能够衡量小程序性能,我们需要一系列指标来描述小程序启动过程中的关键阶段。

以懂车帝小程序为例,从小程序启动到完成我们分为以下几个关键环节

  • 小程序启动后会显示一个 loading view 在这个阶段会初始化小程序环境
  • 初始化完成后开始加载小程序,然后开始第二个阶段 首次绘制,这个时机开始渲染小程序的首帧
  • 第三个阶段是【最大内容绘制】,在这时已经展示页面中的最大元素,也是比较接近用户视觉感知效果
  • 最后呢是主要元素加载完成,并且达到了可交互的状态 通过以上启动阶段,可以将用户体验数字化表达

当然,启动环节耗时并不能完全真实反映用户体验,我们也借助实际用户行为,异常监控,作为辅助指标 包含:取消率、白屏率、LCP 到达率

  • 取消率是指用户在加载过程中点击返回或右上角关闭的占比,用于描述小程序的启动性能,用户取消率越低,启动性能越好。
  • 白屏率是指从启动到退出白屏的 pv占比,出现白屏表示页面渲染失败,我们目标是尽可能降低白屏率
  • LCP 到达率,如果较多用户在 LCP 到达前离开,也表示小程序可能出现异常,或用户体验较差

借助启动阶段指标及辅助指标,我们可以较为准确的描绘出小程序的体验感知

优化措施

image.png