作者:个推 iOS 工程师 伊泽瑞尔
目前,移动开发技术主要分为原生开发和跨平台开发两种。其中,原生应用是指在某个特定的移动平台上,使用平台所支持的开发工具和语言,直接调用系统提供的 API 所开发的应用。 原生开发的主要优势体现在: 1.可以快速访问本平台的全部功能,比如摄像头、GPS 等; 2.原生应用的速度快、性能高,而且可以实现比较复杂的动画和绘制效果,用户体验较好。 原生开发的缺点也很明显,主要体现在: 1.开发成本较高,不同的平台必须维护不同的代码,人力成本也会随之增加; 2.有新的功能需要更新时,只能进行版本升级。 随着移动互联网的高速发展,在很多的业务场景下,传统的纯原生开发已经不能满足日益增长的业务需求,主要表现在以下两个方面: 1.应用动态化的需求增大。当需求发生变化,或者是需要增加新的功能时,传统的纯原生应用开发只能通过版本的升级来更新内容,然而应用的上架和审核都需要一定的时间。因此,开发人员迫切地希望进行应用内容的更新时,可以不更新版本,提升工作效率。 2.业务需求变化快,开发成本变高。原生开发一般需要技术团队对 iOS、Android 两个开发平台进行维护。当版本更新迭代时,开发和测试的成本都会增加。 针对上述两个问题,跨平台框架应运而生。
针对上文提到的原生开发所面临的问题,目前在 IT 界已经诞生了很多跨平台框架,主要分为三类: 1.H5+原生(Cordova、Ionic、微信小程序); 2.JavaScript 开发+原生渲染(React Native、Weex、快应用); 3.自绘 UI+原生(Flutter)。 在本文中,我们将对 React Native、Weex 和 Flutter 进行对比。 1.React Native React Native 是 Facebook 于 2015 年 4 月开源的跨平台移动应用开发框架,是 Facebook 开源的 JS 框架 React 在原生移动应用平台的衍生物。React Native 使用了 react 的设计模式,但是其 UI 渲染、动画效果、网络请求等均是由原生来实现的。开发者编写 JS 代码,通过 React Native 的中间层转化为原生控件,并进行操作。也就是说通过 JS 代码来调用原生的组件,从而实现相应的功能。 React Native 实现跨平台的功能,主要由 Java、C++和 Javascript 三层所构成的。其中,C++实现的动态链接库(.so ),作为中间适配层桥接,实现了 JS 端与原生端的双向通信交互。React Native 会把应用的 JS 代码编译成一个 JS 文件,React Native 整体框架目标就是为了解释并运行这个 JS 脚本文件,如果是 JS 扩展的 API,则直接通过 bridge 调用 native ;如果是 UI 界面,则映射到 virtual DOM 这个虚拟的 JS 数据结构中,通过 bridge 传递到 native,然后根据数据设置各个对应的真实 native 的 View。 2.Weex 在 Weex 设计之初,开发者就考虑到,使其能够在三端( iOS、安卓和 H5 )上均能得到展现。在最上面的 DSL,阿里一般称之为 Weex 文件(.we ),通过 Transform 转换为 js-bundle,再部署到服务器,这样服务端就完成了。在客户端,第一层是 JS-Framework,最后是 RenderRengine。 如上图所示,Weex 的输入是 Virtual DOM,输出是 native 或 H5 view,还原为内存中的树型数据结构,再创建 view,把事件绑定在 view 上,设置 view 的基本属性。Weex Render 会分三个线程,不同的线程负责不同的事情,让 JS 线程优先保障流畅性。
表面上,Weex 是一种客户端技术,但实际上,它串联起了从本地开发、云端部署到分发的整个链路。开发者可以在本地像编写 Web 页面一样先编写一个 APP 界面,然后通过命令行工具将之编译为一段 JavaScript 代码,生成一个 Weex 的 JS bundle。与此同时,开发者可以将生成的 JS bundle 部署至云端,之后通过网络请求或者预下发的方式加载至用户的移动应用客户端。 在移动应用客户端,Weex SDK 会准备一个 JavaScript 执行环境,在用户打开一个 Weex 页面时,在该环境中执行相应的 JS bundle,并将执行过程中产生的各种命令发送到 native 端,进行界面渲染、数据存储、网络通信、调用设备及用户交互响应等。如果用户希望使用浏览器访问这个界面,那么他可以在浏览器中打开一个相同的 Web 页面,这个页面和移动应用使用相同的页面源代码,但被编译成适合 Web 展示的 JS Bundle,通过浏览器里的 javaScript 引擎及 Weex SDK 运行起来的。 3.Flutter Flutter 是 Google 推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart 语言进行 APP 开发,只需要一套代码就可以同时构建 Android 和 iOS 应用,并且可以达到与原生应用一样的性能。Flutter 还提供了丰富的组件、接口,开发者可以高效地为 Flutter 添加 native 扩展。此外,Flutter 还使用了 Native 引擎渲染视图,为用户提供了良好的体验。 Flutter 与用于构建移动应用程序的其它多数框架不同,因为 Flutter 既不使用 WebView,也不使用操作系统的原生控件。相反,Flutter 使用自己的高性能渲染引擎来绘制 widget。这样不仅可以保证在 Android 和 iOS 的 UI 一致性,而且也可以避免对原生控件依赖而带来的限制和高昂的维护成本。 同时,Flutter 使用 Skia 作为 2D 引擎渲染,Skia 是 Google 的一个 2D 图形处理函数库,在字型、坐标转换以及点阵图等方面都有高效而且简洁的表现。Skia 是跨平台的,并提供了非常友好的 API。由于 Android 系统已经内置了 Skia,所以 Flutter 在打包 APK 时,不需要再将 Skia 打包到 APK 中,但是 iOS 系统并未内置 Skia,所以在构建 API 时,必须将 Skia 一起打包。
目前,Flutter 程序主要有两种运行方式:静态编译与动态解释。静态编译的程序在执行前,会被全部翻译为机器码,通常将这种类型称为 AOT,即 “提前编译”。解释执行则是一句句地边翻译边运行,通常将这种类型称为 JIT,即“即时编译”。 AOT 程序的典型代表是用 C/C++开发的应用,它们必须在执行前编译成机器码。而 JIT 的代表则非常多,如 JavaScript、python 等。事实上,所有脚本语言都支持 JIT 模式。但需要注意的是,JIT 和 AOT 指的是程序运行方式,和编程语言并非是强关联的,有些语言既可以以 JIT 方式运行,也可以以 AOT 方式运行,如 Java、Python,它们可以在第一次执行时编译成中间字节码,然后在之后的执行中,直接执行字节码。 Flutter 的高性能主要靠两点来保证,首先,Flutter APP 采用 Dart 语言进行开发。当 Dart 在 JIT 模式下时,其运行速度与 JavaScript 基本持平。此外 Dart 支持 还 AOT,当 Dart 在 AOT 模式下事,其运行速度远超 JavaScript。速度的提升对高帧率下的视图数据计算很有帮助。 其次,Flutter 使用自己的渲染引擎来绘制 UI,布局数据等由 Dart 语言直接控制,所以在布局过程中不需要像 RN 那样要在 JavaScript 和 Native 之间通信,在一些滑动和拖动的场景下具有明显优势。由于滑动和拖动往往会引起布局的变化,所以 JavaScript 需要不停地与 Native 之间同步布局信息,这和在浏览器中要 JavaScript 频繁操作 DOM 所带来的问题是相同的,都会带来比较可观的性能开销。
**1.开发效率高。**Dart 运行时和编译器支持 Flutter 的两个关键特性的组合,分别是基于 JIT 的快速开发周期和基于 AOT 的发布包。基于 JIT 的快速开发周期:Flutter 在开发阶段,采用 JIT 模式,这样就避免了每次改动都需要进行编译,极大地节省了开发时间。基于 AOT 的发布包,Flutter 在发布时可以通过 AOT 生成高效的 ARM 代码,以保证应用性能。而 JavaScript 则不具备这个能力。 **2.高性能。**为了实现流畅、高保真的的 UI 体验,Flutter 必须在每个动画帧中都运行大量的代码。这意味着需要一种既能支持高性能,又能保证不丢帧的周期性暂停的语言,而 Dart 支持 AOT,在这一点上比 JavaScript 更有优势。 **3.快速分配内存。**Flutter 框架使用函数式流,这使得它在很大程度上依赖于底层的内存分配器。 **4.类型安全。**由于 Dart 是类型安全的语言,支持静态类型检测,所以可以在编译前就发现一些类型的错误,并排除潜在问题。这对于前端开发者来说更具有吸引力。而 JavaScript 是一个弱类型语言,这也是为什么在诸多前端社区中,会有众多为 JavaScript 代码添加静态类型检测的扩展语言和工具。
Flutter Framework 是一个完全由 Dart 语言构建的 SDK,它实现了一整套自底而上的基础库。 1.底部两层(Foundation 和 Animation、Painting、Gestures)是 Flutter 引擎暴露的底层 UI 库,提供动画、手势及绘制能力。 2.Rendering 层是一个抽象的布局层,它依赖于 dart UI 层。Rendering 层会构建一个 UI 树,当 UI 树有变化时,它会随即计算出有变化的部分,然后更新 UI 树,最终将 UI 树绘制到屏幕上。这个过程类似于 React 中的虚拟 DOM。Rendering 层可以说是 Flutter UI 框架最核心的部分,它除了确定每个 UI 元素的位置、大小之外,还要进行坐标变换和绘制(调用底层 dart:ui)。 3.Widgets 层是 Flutter 提供的一套基础组件库,在基础组件库之上,Flutter 还提供了 Material 和 Cupertino 两种视觉风格的组件库。 Flutter Engine:这是一个完全由 C++实现的 SDK,其中包括了 Skia 引擎、Dart 运行时和文字排版引擎等。在代码调用 dart:ui 库时,调用最终会走到 Engine 层,然后实现真正的绘制逻辑。 React Native、Weex 和 Flutter 进行对比结果如下所示:
从 Flutter 的设计理念来看,其整体架构都是具有革命性的,相比于其他架构,它实现了真正意义上的跨平台。它能够让各平台的体验一致,并且让用户体验达到更优。现如今,Flutter 的各种 UI 库和组件都在不断增加,与之相关的各种生态系统和社区也在不断完善,它对新的操作系统的适配性将会越来越强。相信在不久的将来,Flutter 会慢慢成熟起来,成为主流的开发语言之一。