个推 2019-03-21 18:03:54 浏览 44 评论 0
iOS
Android
架构
- JavaScript
- native
- react
线程
c++
布局
用户体验
高性能
摘要: 作者: 个推 iOS 工程师 伊泽瑞尔 一, 背景 目前, 移动开发技术主要分为原生开发和跨平台开发两种. 其中, 原生应用是指在某个特定的移动平台上, 使用平台所支持的开发工具和语言, 直接调用系统提供的 API 所开发的应用.
作者: 个推 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
目前, 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 所带来的问题是相同的, 都会带来比较可观的性能开销.
四, 为什么 Flutter 会选择 Dart 语言?
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 框架结构
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 会慢慢成熟起来, 成为主流的开发语言之一.
来源: https://yq.aliyun.com/articles/694719