这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
课程笔记
跨端
跨端背景
随着业务的发展,产生了越来越多的业务场景,同时随着技术的发展,产生了越来越多的端,PC端(Windows、Mac)、移动端(安卓、iOS),web端、loT设备(车载设备、手表)等。
就传统开发来说,我们开发一个应用需要每个端都配备一个相应的开发人员,这样会带来许多开发的麻烦:
- 各端功能几乎一致,各端需要单独配置研发人员
- 开发、维护成本高
- 安卓、iOS发版周期长
- 有时单端出现BUG会导致多端不对齐
所以这时我们就需要实现跨端技术使得我们的一个产品在不同的客户端上有一致的表现。
跨端技术方案目标
基于跨端背景的分析我们可以得出实现跨端技术的方案目标:
-
研发效率高
- 学习成本低
- 多端一致性高
-
用户体验好
- 稳定性高
- 性能体验好
-
动态化
- 支持动态化下发,满足日益增长的业务需求
跨端技术方案介绍
业界主流的跨端技术方案主要有hybrid方案、原生渲染方案、自渲染方案、和小程序方案四种。
hybrid方案
hybrid方案基于WebView渲染,通过JS Bridge把一部分系统能力开放给JS调用。
- WebView简单来说就是浏览器即当打开一个url时对页面进行渲染的部分。
- JS Bridge可以让我们使用JavaScript来调用一些终端系统的操作。
WebView容器的工作原理是基于Web技术来实现界面和功能,通过将原生的接口封装、暴露给JavaScript调用,JavaScript编写的页面可以运行在系统自带的WebView中。
这样做的优势是,对于前端开发者比较友好,可以很快地实现页面跨端,同时保留调用原生的能力,通过搭建桥接层和原生能力打通。但这种设计,跨端的能力受限于桥接层,当调用之前没有的原生能力时,就需要增加桥。
另外,浏览器内核的渲染独立于系统组件,无法保证原生体验,渲染的效果会差不少。
原生渲染方案
使用JS开发,通过中间层桥接后使用原生组件来渲染UI界面。
即逻辑使用JS来开发,视图使用原生组件来渲染。与hybrid的区别就在于hybrid的逻辑也是使用JS开发,但其视图使用WebView来开发。
目前业内比较成熟的原生渲染方案是FaceBook开发的React Native。
React Native
React Native是一个由Facebook于2015年9月发布的一款开源的JavaScript框架,它可以让开发者使用JavaScript和 React来开发跨平台的移动应用。
React Native 原理
React Native 的思路是最大化地复用前端的生态和 Native 的生态,和 WebView 容器的最大区别在于 View 的渲染体系。React Native 抛弃了低效的浏览器内核渲染,转而使用自己的 DSL 生成中间格式,然后映射到对应的平台,渲染成平台的组件。相对 WebView 容器,体验会有一定的提升。不过,渲染时需要 JavaScript 和原生之间通信,在有些场景可能会导致卡顿。另外就是,渲染还是在Native层,要求开发人员对Native有一定的熟悉度。
React Native 主要由JSI、Fabric、TurboModules 组成。
-
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++ 编写,以后如果针对电视、手表等其他系统,也可以很方便地移植; -
Fabric
Fabric 是新的渲染系统,它将取代当前的 UI Manager。
-
UI Manager:
当 App 运行时,React 会执行你的代码并在 JS 中创建一个 ReactElementTree ,基于这棵树渲染器会在 C++ 中创建一个 ReactShadowTree。UI Manager 会使用 Shadow Tree 来计算 UI 元素的位置,而一旦 Layout 完成,Shadow Tree 就会被转换为由 Native Elements 组成的 HostViewTree(例如:RN 里的 <View/> 会变成 Android 中的 ViewGroup 和 iOS 中的 UIView)。
而之前线程之间的通信都发生在 Bridge 上,这就意味着需要在传输和数据复制上耗费时间。通过JSON格式来传递消息,每次都要经历序列化和反序列化。 而得益于前面的 JSI, JS 可以直接调用 Native 方法,其实就包括了 UI 方法,所以 JS 和 UI 线程可以同步执行从而提高列表、跳转、手势处理等的性能。
-
-
Turbo Modules
在之前的架构中 JS 使用的所有 Native Modules(例如蓝牙、地理位置、文件存储等)都必须在应用程序打开之前进行初始化,这意味着即使用户不需要某些模块,但是它仍然必须在启动时进行初始化。
Turbo Modules 基本上是对这些旧的 Native 模块的增强,正如在前面介绍的那样,现在 JS 将能够持有这些模块的引用,所以 JS 代码可以仅在需要时才加载对应模块,这样可以将显着缩短 RN 应用的启动时间。
自渲染方案
利用 Skia 重新实现渲染管线,不依赖原生组件。主流的自渲染方案就是Google提出的Flutter。
Flutter
Flutter是Google在2018年世界移动大会公布的开源应用开发框架,仅通过一套代码库,就能构建精美的、原生平台编译的多平台应用。
Flutter与上述Recat Native、WebView容器本质上都是不同的,它没有使用WebView、JavaScript解释器或者系统平台自带的原生控件,而是有一套自己专属的Widget,底层渲染使用自身的高性能C/C++ 引擎自绘。
对于底层操作系统而言,Flutter 应用程序的包装方式与其他原生应用相同。在每一个平台上,会包含一个特定的嵌入层,从而提供一个程序入口,程序由此可以与底层操作系统进行协调,访问诸如 surface 渲染、辅助功能和输入等服务,并且管理事件循环队列。
Flutter 代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。 Flutter 本身包含了各个常见平台的嵌入层,同时也存在一些其他的嵌入层。
Flutter 引擎毫无疑问是 Flutter 的核心,它主要使用 C++ 编写,并提供了 Flutter 应用所需的原语。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter 核心 API 的底层实现,包括图形(通过 Skia)、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链。
引擎将底层 C++ 代码包装成 Dart 代码,通过 dart:ui 暴露给 Flutter 框架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本渲染的子系统的类。
通常,开发者可以通过 Flutter 框架层 与 Flutter 交互,该框架提供了以 Dart 语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:
-
基础的 foundational 类及一些基层之上的构建块服务,如 animation、 painting 和 gestures,它们可以提供上层常用的抽象。
-
渲染层 用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。
-
widget 层 是一种组合的抽象。每一个渲染层中的渲染对象,都在 widgets 层中有一个对应的类。此外,widgets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。
-
Material 和 Cupertino 库提供了全面的 widgets 层的原语组合,这套组合分别实现了 Material 和 iOS 设计规范。
Flutter 框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,使用 Dart 和 Flutter 的核心库实现,其中包括平台插件,例如 camera 和 webview;与平台无关的功能,例如 characters、 http 和 animations。还有一些软件包来自于更为宽泛的生态系统中,例如 应用内支付、 Apple 认证 和 Lottie 动画。
小程序方案
使用小程序DSL +JS开发,通过中间层桥接后调用原生能力,使用webview来渲染U界面。
字节小程序是一种全新的连接用户与服务的方式,它可以在宿主(抖音、头条等App)内被便捷地获取和传播,同时具有出色的使用体验。
小程序的运行环境分成渲染层和逻辑层,其中 ttml 模板和 ttss 样式工作在渲染层,js 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由2个线程管理:
渲染层的界面使用了WebView 进行渲染;逻辑层采用 JSC 线程运行 JS 脚本。一个小程序存在多个界面,所以渲染层存在多个 WebView 线程,这两个线程的通信会经由客户端(下文中也会采用 Native 来代指客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型如图所示。
跨端技术方案对比
| 技术方案 | 视图层 | 逻辑层 | 优点 | 缺点 |
|---|---|---|---|---|
| hybrid方案 | webview | webiew JS thread | 1.开发成本低 2.CSS全集 3.一致性好 | 1.性能中等 |
| 原生渲染方案 | 原生组件 | JS Engine | 1.性能好 | 1.CSS子集 2.一致性一般 |
| 自渲染方案 | Skia | Dart VM | 1.性能最好 2.一致性好 | 1.CSS子集 2. Dart生态一般 3.开发成本较高 |
| 小程序方案 | webview +原生 | JS Engine组件 | 1.开发成本低 2.CSS全集 3.一致性好 | 1.性能较好 |
总结
通过本次课程,大致对跨端技术有了一定的了解,从hybrid方案到小程序的介绍,自己跨端技术有了大致的了解。
随着端越来越多,跨端的需求会越来越强烈,跨端技术方案也会不断迭代,核心目标仍然是「write once, run anywhere」,未来自渲染可能会出现更多机会。