一套代码,多端运行” 口号的跨平台开发方案
React Native 希望 在性能、展示、交互能力和迭代交付效率之间做到平衡。
它在 Web 容器方案的基础上, 优化了加载、解析和渲染这三大过程,
以相对简单的方式支持了构建移动端页面必要的 Web 标准,
保证了便捷的前端开发体验;
并且在保留基本渲染能力的基础上,用原生自带的 UI 组件实现代替了核心的渲染引擎,
从而保证了良好的渲染性能。
缺点:
但是,由于 React Native 的技术方案所限,使用原生控件承载界面渲染,在牺牲了部分 Web 标准灵活性的同时,
固然解决了不少性能问题,但也引入了新的问题:除开通过 JavaScript 虚拟机进行原生接口的调用,而带来的通信低效不谈,
由于框架本身不负责渲染,而是由原生代理,因此我们还需要面对大量平台相关的逻辑。
而随着系统版本和 API 的变化,我们还需要处理不同平台的原生控件渲染能力上的差异,
修复各类怪异的 Bug,甚至还需要在原生系统上打各类补丁。
要用好 React Native,除了掌握这个框架外,开发者还必须同时熟悉 iOS 和 Android 系统
Flutter,则完全不同于 React Native
提供了一整套从底层渲染逻辑到上层开发语言的完整解决方案:
1\. 视图渲染完全闭环在其框架内部,
不依赖于底层操作系统提供的任何组件,
从根本上保证了视图渲染在 Android 和 iOS 上的高度一致性;
2\. Flutter 的开发语言 Dart,是 Google 专门为(大)前端开发量身打造的专属语言,
借助于先进的工具链和编译器,成为了少数同时支持 JIT 和 AOT 的语言之一,
开发期调试效率高,发布期运行速度快、执行性能好,在代码执行效率上可以媲美原生 App。
对比:
而这与 React Native 所用的只能解释执行的 JavaScript,又拉开了性能差距。
正是因为 Flutter 在开发阶段使用了 JIT 编译模式,使得通过热重载(Hot Reload)
这样的技术去进一步提升调试效率成为可能。
简单来说,热重载就是在无需重新编译代码、重启应用程序、丢失程序执行状态的情况下,
就能实时加载修改后的代码,查看改动效果。
体验 热重载:
将其根节点修改为 StatelessWidget:点击 Run 图标,然后试着修改一下代码,
保存后仅需几百毫秒就可以看到最新的显示效果。
热重载 局限性:
并不是所有的代码改动都可以通过热重载来更新。对 hello_world 示例而言,
由于 Flutter 并不会在热重载后重新执行 main 函数,而只会根据原来的根节点重新创建控件树,
因此我们刚才做了一些改造之后才支持热重载。
如果热重载不起作用的时候,我们也不需要进行漫长的重新编译加载等待,
只要点击位于工程面板左下角的热重启(Hot Restart)按钮就可以以秒级的速度进行代码重编译以及程序重启了,
而它与热重载的区别只是因为重启丢失了当前程序的运行状态而已,对实际调试也没什么影响。
Dart 语言
2011年谷歌推出,
目的:
如同 Kotlin 和 Swift 的出现,分别是为了解决 Java 和 Objective-C 在编写应用程序的一些实际问题一样,
Dart 的诞生正是要解决 JavaScript 存在的、在语言本质上无法改进的缺陷。
那么,JavaScript 到底有哪些问题和缺陷呢?
JavaScript 实际上是两类编程语言风格的混合产物:(简化的)函数式编程风格,与(简化的)面向对象编程风格。
语言设计时间短,考虑不周,导致 使用 JavaScript 开发的程序混乱不堪
JavaScript 的发展
原本 JavaScript 只能在浏览器中运行,但 Node.js 的出现让它开始有能力运行在服务端,
很快手机应用与桌面应用也成为了 JavaScript 的宿主容器,
一些明星项目比如 React、React Native、Vue、Electron、NW(node-webkit)也迅速发展
JavaScript 成为了前后端通吃的全栈语言,
如同 Atwood 定律描述的:凡是能用 JavaScript 写出来的系统,最终都会用 JavaScript 写出来
Dart 核心特性
JIT 与 AOT借助于先进的工具链和编译器,Dart 是少数同时支持
JIT(Just In Time,即时编译)
和 AOT(Ahead of Time,运行前编译)的语言之一。
语言在运行之前通常都需要编译,JIT 和 AOT 则是最常见的两种编译模式。
JIT 在运行时即时编译,
在开发周期中使用,可以动态下发和执行代码,开发测试效率高,
但 运行速度 和 执行性能 则会因为运行时即时编译受到影响。
AOT 即提前编译,
可以生成被直接执行的二进制代码,运行速度快、执行性能表现好,但每次执行前都需要提前编译,开发测试效率低。
AOT 的典型代表是 C/C++,它们必须在执行前编译成机器码;
而 JIT 的代表,则包括了如 JavaScript、Python 等几乎所有的脚本语言。
总结
在开发期使用 JIT 编译,可以缩短产品的开发周期。
Flutter 最受欢迎的功能之一热重载,正是基于此特性。
而在发布期使用 AOT,就不需要像 React Native 那样在跨平台 JavaScript 代码
和原生 Android、iOS 代码之间建立低效的方法调用映射关系。
所以说,Dart 具有运行速度快、执行性能好的特点。
Dart 内存分配与垃圾回收
Dart VM 的内存分配策略比较简单,创建对象时只需要在堆上移动指针,
内存增长始终是线性的,省去了查找可用内存的过程。
在 Dart 中,并发是通过 Isolate 实现的。
Isolate 是类似于线程但不共享内存,独立运行的 worker。
这样的机制,就可以让 Dart 实现无锁的快速分配。
Dart 的垃圾回收,则是采用了多生代算法。
新生代在回收内存时采用“半空间”机制,触发垃圾回收时,Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,
然后整体释放当前空间的所有内存。
回收过程中,Dart 只需要操作少量的“活跃”对象,没有引用的大量“死亡”对象则被忽略,
这样的回收机制很适合 Flutter 框架中大量 Widget 销毁重建的场景。
线程优势:
其他原因的并发多线程
支持并发执行线程的高级语言(比如,C++、Java、Objective-C),大都以抢占式的方式切换线程,
即:每个线程都会被分配一个固定的时间片来执行,超过了时间片后线程上下文将被抢占后切换。
如果这时正在更新线程间的共享资源,抢占后就可能导致数据不同步的问题。
解决这一问题的典型方法是,使用锁来保护共享资源,但锁本身又可能会带来性能损耗,
甚至出现死锁等更严重的问题。
Dart 是单线程模型的优势就体现出来了,因为它天然不存在资源竞争和状态同步的问题。
这就意味着,一旦某个函数开始执行,就将执行到这个函数结束,而不会被其他 Dart 代码打断。
Dart 中并没有线程,只有 Isolate(隔离区)。
Isolates 之间不会共享内存,就像几个运行在不同进程中的 worker,
通过事件循环(Event Looper)在 事件队列(Event Queue)上传递消息通信。
在 Dart 社区目前最顶级的产品就是 Flutter 和 Fuchsia 了,
我觉得 Dart 是否能够成功,目前来看主要取决于 Flutter 和 Fuchsia 能否成功。
而,Flutter 是构建 Fuchsia 的 UI 开发框架,因此这个问题也变成了 Flutter 能否成功。
//////////////
跨平台开发方案的三个时代
根据实现方式的不同,业内常见的观点是将主流的跨平台方案划分为三个时代。
Web 容器时代:
基于 Web 相关技术通过浏览器组件来实现界面及功能,
典型的框架包括 Cordova(PhoneGap)、Ionic 和微信小程序。
泛 Web 容器时代:
采用类 Web 标准进行开发,但在运行时把绘制和渲染交由原生系统接管的技术,
代表框架有 React Native、Weex 和快应用,广义的还包括天猫的 Virtual View 等。
自绘引擎时代:
自带渲染引擎,客户端仅提供一块画布即可获得从业务逻辑到功能呈现的多端高度一致的渲染体验。
Flutter,是为数不多的代表。