前言
如题所示,本文主要聊一聊跨平台技术的发展与演进。那么,请各位先思考一下,什么是跨平台? 为什么要跨平台? 怎么跨平台?怎么更好更优的跨? 依次按照这个逻辑进行一一讲述。
什么是跨平台?
众所周知,移动端app 已经占据了我们生活大半的时间,并且当前移动端领域开发主要由 iOS 和 Android 这两大平台占据。
早期移动端研发人员使用原生的方式来开发 App,要求针对 iOS 和 Android 这两个平台分别进行开发,这种研发模式付出的成本和时间都是巨大的,造成的负担和隐患对于中小型企业是不可接受的,开发效率与上线周期严重影响了互联网业务的快速迭代发展。因此,“一套代码,多端运行”的跨平台理念也应运而生。
为什么要跨平台?
如上所述,由于互联网快速发展业务场景,移动端原生app开发模式的“代价高、迭代慢”等不可接受因素,,导致了跨平台开发的概念顺势渐渐的走进了人们的视野。“一套代码,多端运行”的理念逐渐洗礼了早期移动端纯原生开发模式。
怎么跨平台?
对于怎么进行跨平台的问题,业内各种跨平台技术方案如雨后春笋,比如 web-app、小程序、React-Native、Weex、Flutter等等。
那么,既然有 web-app的 hybrid 技术方案可以实现跨平台,为什么还要出现一系列其他技术方案呢?
我们先看看web-app 的 hybrid 方案的实现原理。
从架构图可以看出,这种技术方案其实就是 h5 应用套了一个 app 外壳 ,但是他可以利用 js-bridge 通信技术来利用 native 的部分能力。 无论在IOS 还是 安卓上,你都可以认为就是 app 内部运行了一个浏览器 (webview),每次跳转页面都是 tab 间的跳转。
但是,这种技术架构的瓶颈是很明显的。首先,页面的加载/渲染过程其实完全由 webview 来决定,任何技术优化怎么都脱离不了webview;另外,页面中操作想利用 native 能力时与原生通信只能通过 js-bridge 来完成。重度依赖 "webview" "js-bridge"。
一个完整 H5 页面的展示要webview的初始化,页面 url 解析与页面静态资源的加载、解析和渲染等过程,并且js进程和渲染进程的互斥也造成整个渲染流程变得复杂,时间和性能消耗要比原生开发的 app 增加 N 个数量级,与原生开发的应用有肉眼可见的性能/体验边界。
当然了,hybrid 体系下为了弥补性能/体验与原生的差距,也衍生出一些类似操近路的技术优化方案。比如,离线包方案、资源预加载+缓冲方案、接口预请求+缓存方案、页面预渲染方案等等,这些方案不是本文的重点,这里不展开讲述。
怎么更好的跨平台?
上面讲述了基于 hybrid 体系的 web-app 跨平台方案。但是由于重度依赖 "webview"、 "js-bridge",因此导致肉眼可见的性能问题。
那么,能否摆脱对 webview 依赖,对较重的 webview 进行功能裁剪,在仅保留必要的 Web 标准和渲染能力的基础上,对开发体验和原生优异的渲染性能做一个平衡呢?
业界的 React Native 和 Weex 给出了答案。
下面,接着讲述 React Native 等方案的跨平台技术演进之路。
为了解决浏览器(webview) 与 js-bridge 的瓶颈, 业界出现了诸如 React Native 、Weex 此类的跨端方案。下面主要聊一聊 React Native。
React Native 简介
RN 提出了一种新的 “一套代码 多端使用” 的跨平台方案,那么他是如何摆脱掉 webview 的性能问题的呢?下面是 RN 的整体设计方案。
如图所示,UI逻辑与统一的组件与布局两部分组成了控制中心
,通过它指定需要在屏幕上显示的组件以及该组件在屏幕上显示的位置,同时它还负责处理用户与组件之间的交互。中间转换系统
一般会涉及到跨语言调用和对平台相关组件以及组件的属性和方法的封装。最底层的 Native UI系统
负责具体的渲染操作。
上述流程中最核心的概念是控制,RN 不是要实现一个跨平台的 UI系统,而是要实现的是一个借助各平台既有的 UI系统的跨平台UI控制系统。RN 就是这么做的,它的控制中心由 JavaScript 语言实现,然后通过由 NativeModule、C++ Bridge 等组成的中间转换系统,将布局指令翻译成平台相关的组件与布局语法。简而言之,RN 是通过 JS 来控制 native 的渲染,请注意,RN 的 JS 层代码是控制
native 渲染而不是负责渲染。
下面看一下 RN 与 hybrid 方案对比:
RN | Hybrid | |
---|---|---|
运行环境 | js 引擎(JSC) | 依托浏览器(webview) |
渲染 | native渲染 接近原生 | 依赖浏览器 渲染性能较差 |
通信 | 通过 JSC中间层与 Native交互, 无平台差异 | Native通过操作JS上下文与浏览器交互,有平台差异 |
native扩展 | 通过它的NativeModule机制 支持复杂类型参数 | JS的上下文只支持原始数据类型(primitive) |
RN中的JS代码是直接运行在JS引擎上的,故不存在系统浏览器组件的兼容性问题。对于UI的渲染,JS代码只负责指定需要渲染的组件以及各组件之间的相对位置,而实际的渲染工作由native侧完成,能达到接近原生的渲染效果。
RN的native扩展通过它的NativeModule机制来实现,可以支持Map、Array等复杂类型参数的传递。RN中与native相关的组件、包括官方提供的,都是通过NativeModule来实现的。
最后,RN中利用C++实现了一个桥,通过这个桥,支持JS与原生端的双向通信交互,它在Android上的表现是Java与JS代码的相互调用,在iOS上则是OC与JS代码的相互调用。
React Native 的架构
RN 整体架构可以分为三大部分,JS域、Native域以及负责两个域之间通信的 C++ Bridge 。具体如下图所示:
JS域为单线程,使用的编程语言是JavaScript,JS代码运行在JavaScriptCore上。JS域主要负责实现APP的业务逻辑、并指定需要渲染的组件以及组件的布局。
Native域是一个多线程的环境,它有负责UI渲染的主UI线程,以及其他后台任务线程。负责运行JS代码的线程是native域中多条后台线程中的一条。 JS本身没有线程,线程由宿主环境提供。native域的主要作用是提供宿主环境,并负责UI渲染与交互。
C++ Bridge主要负责JS域与Native域的通信,而通信则是指JS与Java、OC等语言之间的相互调用。
对于性能问题,现代JS代码的执行速度已经非常快了,性能问题一般不会出在JS域。Android与iOS对其各自的性能都有保证,性能问题一般也不会出现在native域。因此,性能瓶颈往往会出现在 C++ Bridge 层
。
在JS域和Native域中运行的是不同的语言,因此在不同域中定义的变量是不能相互访问的,所有跨语言的通信都需要通过C++ Bridge来完成。而在通信过程中,数据的序列化与反序列化是非常耗时的,一旦通信任务量过大会导致性能问题,比如快速滑动列表白屏现象(虽然 React 在JS 侧已经基于 v-dom diff 的批量更新策略做了些优化)。
因此,设计一个高性能的 RN 应用,必须将 bridge 上传递的数据量保持在最低限度。
React Native 的线程
RN 的 Android 端主要有三个线程:
- 负责页面的交互 UI渲染的 main_ui 线程,这个可以看作是主线程;
- 负责执行 JS 代码的 mqt_js 线程,并负责 JS 和原生代码的交互;
- 与 NativeModule 相关的 mqt_native_modules 线程,负责计算实际布局与 v-dom 在 native 的映射。
其中每个线程都有与其绑定的消息队列。
mqt_js 线程中运行的是JS代码,它位于JS域。而 main_ui 与 mqt_native_module s线程中运行的是native代码,位于native域。
native侧线程(main_ui和mqt_native_modules线程)通过调用 jniCallJSFunction 和 jniCallJSCallback 方法来执行JS侧的代码,从而实现与 mqt_js 线程交互。
mqt_js线程通过调用NativeModule暴露给JS侧的方法与mqt_native_modules线程交互。
而main_ui与mqt_native_modules可通过Handler直接交互。
React Native 新架构
为了解决 bridge 层通信(数据的序列化/反序列化,队列任务拥堵)和渲染任务同步的瓶颈, RN 设计了新的架构。
优化后的新架构:JSI 架起 JS 和Native之间的桥梁,通过在C++层实现,不需要序列化成JSON与双向传递等一系列操作,实现了Native和 JS 间的直接同步通讯。 Fabric 将UI操作作为函数公开给 JavaScript,Shadow Tree(决定在屏幕上真正显示的内容)在两个领域之间共享,允许两端直接交互,大大优化了通信性能。
最后
经过上面的介绍,对于 React Native 此类跨端方案的实现原理想必已经有所了解了。 那么,是不是这种技术就是跨端技术的终极方案了呢?是不是已经非常的完美了呢?
显然不是的,另一种创新性跨端方案(谷歌 Flutter 方案)的腾空出世,为跨端界又指引了一个新的方向。 那么 flutter 解决了什么问题呢? 下一篇见~