如何评价 Google 的 Fuchsia、Android、iOS 跨平台应用框架 Flutter?

其中,Fuchsia 的官方应用框架就是 Flutter + Dart 。
关注者
2,009
被浏览
320,490

52 个回答

我在过去做过若干年的动态化技术的开发,也写过类似于 RN 的应用框架(但要早于 RN)。简单从几个角度对比下 Flutter & RN/Weex,顺便聊聊动态化技术:


当前几大动态化技术对比

  1. 性能和体验
    Flutter 由于渲染的基础(gdi)是自己实现的,所以实现跨平台、性能优化、摆脱平台约束方面的裕度更大。从实际体验来看, Flutter 的性能比 RN 要高不少。

    我尝试总结下性能差异的原因以及 Flutter 架构上为“未来”留下的裕度:

    运行时语言:从前端的思维来看,jsx 或 dart 都是一种声明式的写法,但 jsx 需要转译(工程上看起来美好的东西肯定是需要在别的方面消耗更多),dart 是直接语言层面支持了 node tree 的书写,且对象创建成本低,可直接编译成 native 代码(AOT),VM 效率更高。运行上应该是 dart 效率高很多。

    渲染机制:RN 是从前端思想出发的框架,其在表达复杂 UI 时需要依赖前端“盒子”的深层次叠加嵌套,在 RN 背景下,这每个“盒子”都是一个 native 的 view,这时候就相比 native 开发来说多了很多 view 对象,造成了性能降低。也就是说复杂 UI 需求下,RN 对 UI 的表达效率远低于 native 造成性能低下(Facebook 后来做了一个项目 litho 的亮点就在于打平布局层次,针对性优化我说的布局表达效率低下这一点)。Flutter 是基于 skia (gdi) 层面往上去做的,每个 node/布局是否一定需要是一个 layer 以及 render tree 怎么来划分和实现都有更多灵活性和性能优化的空间,所以能做到性能更优。

    组件的兼容性:
    Flutter 提供的 widget 都是基于 skia 来实现和精心定制的,与具体平台没关,所以能保持很高的跨 os 跨 os version 的兼容性。
  2. 跨平台
    RN 是一套概念/设计理念跨越两个平台,具体到实际平台上去还要去适配和桥接差异性(这其中有巨大的工程成本和性能牺牲,比如做动画,js 就绝对不能用,用了性能就差了)。Flutter 至少做到了一套代码(不涉及平台 api 层面的 UI 及纯事件响应可以完全一样)。
    Flutter 相对来说是做到了跨平台。RN 更适合称为:将一种设计理念延展到两个平台,不能称其为“一套代码,自动部署多平台”的跨平台方案。
  3. 对未来的适应性
    由于 Flutter 从更基础的层去抹平平台差异,Flutter 站在了更宽广、更可控的一个基础平台上去演变和发展。RN 永远需要 follow native 开发的这套约束,桥接和抹平差异乃至应用层去适配的成本、面对具体场景去优化性能所需要的成本都是居高不下的。RN (动态化当然是首要好处,这是这份回答的隐含前提)属于“大公司扛大旗,赚吆喝,小公司跟着复用下现有资源”。
  4. 动态化技术的未来
    我个人认为 RN、weex 这类设计都是走不远的,为什么,动态化技术最成熟的就是 H5/Web,真正产业化(标准化、研发支撑、上下游软件开发商、开发者生态)的技术栈还是 H5/Web。所以业界(软件公司、开发团队)对于动态化技术的期待就是 H5,H5 是要由浏览器来支撑的,RN 的能力不到浏览器的 5%(举例 index = 1000,绝对布局,各种动画和复杂布局能力很难用 native 来实现,还有各种布局模型、浮动元素、css rule,不一而足。举例:本人曾经跨 Android、iOS 对等的实现全规范 css 中的渐变色,开发测试花了 2 天,可能还有未知的 bug),RN、Weex 是无法满足这个期望的(面对一个复杂点儿的需求,RN、Weex 都需要 case by case 去 review,还可能需要写 native 代码去扩展才能实现;而 H5 保证了 99% 概率下都是可以实现的。不需要产品/业务将就“技术”)。

    对于工程而言,RN、Weex 这类方案最大的价值就是提供了一套让传统前端开发上手 native 开发的途径,同时创建了一个社区用于存放可供复用的组件库,让小公司、小团队能共享这部分“生态”,且能切实实现“命题作文”类的需求。
  5. 研发模式
    Dart 语言相关的 vm、debug 以及 hot reload 都是 Google 官方开发和支持的,是其老本行,Flutter 也定位于未来的跨平台的应用框架,更是 Fuchsia OS 的正统 app framework,以上是定位;其次本人跑过 Flutter app 的 demo,研发体验上还是很 “敏捷” 的,实现了现代化框架的 “所改即所得”。
  6. 结论:Flutter 从设计、体验、跨平台上都有亮点。值得关注和寄予期望。但目前成熟度有限,不适合商用;不做改造的前提下,不具备动态化能力(release 版本 dart 被编译后再分发)。

--- 2018/7/1 更新

现在闲鱼已经在做基于 flutter 的线上实践,可以参看:

GMTC-闲鱼Flutter实践效果访谈 mp.weixin.qq.com 图标


目前闲鱼的实践中是拿到了 flutter 在性能、开发效率方面的好处,但是降低包体积、动态化还没有实现。


畅想 Flutter 如何实现动态化

  1. 将 AOT compiler 的能力放到客户端,就跟 android art runtime 下第一次安装 apk 时 aot 编译一次一样,这样 flutter 程序可以以 script 的形式动态分发,同时运行时又能得到 machine code 的性能优势。
    挑战:AOT compiler 很大很重,移植到移动端运行有可能不可行。

2. framework 部分用拥有巨大生态的 JavaScript 等动态语言重写(主要是 widget/layer/renderobject 相关的布局/动画/渲染/语义(aka. 可访问性)计算、任务调度),复用现有 flutter engine(c++ 实现) 部分

挑战:js 写渲染逻辑/动画性能低下,支持灵活的任务调度能力差


flutter 的核心黑科技有哪些

  1. dart
    dart 语言对于 flutter 众多功能层面的特性做出了全方位、多角度的支撑,除了生态小不太容易推广外,dart 完美匹配移动端“轻”且“快”的技术要求。

2. 渲染机制
google 由于有 chromium 项目的积累,所以渲染这块是手到擒来。
代码一开篇就把 layer/renderObject/displayList/layout (源于 WebKit )这一套渲染给熟练的弄过来了。对了,这一套渲染还在 Android 系统上用着。


flutter 的核心不足

  1. dart 语言的生态小,精通成本比较高
  2. 嵌入外部 platform view 成本高
    这块(基于官方代码)目前在 iOS 端有支持,android 上还没有,但相关开发者博客上有提到实现了 webview//mapview 等 platform view 的嵌入。
    从 iOS 端的实现来看,每嵌入一层 platform view 会额外多一层 surface,内存代价比较高。

3. 平台 API

flutter 目前提供的开箱即用的功能只有 UI framework + dart 语言的能力,平台能力需要通过 platform channel 来扩展。当然这一点也是除了 RN 等其他同类解决方案的通病。

泻药,9月份的测评结果如下

————————————————————————

论坛上很多小伙伴关心为什么闲鱼选择了Flutter而不选择其他跨端方案?站在质量的角度,高性能是一个很重的因素,我们使用Flutter重写了宝贝详情页之后,对比了Flutter和Native详情页的性能表现,结论是中高端机型上Flutter和Native不相上下,在低端机型上,Flutter会比Native更加的流畅,其实闲鱼团队在使用Flutter做详情页过程中,没有更多地关注性能优化,为了更快地上线,也是优先功能的实现,不过测试结果出来之后,却出乎意料地优于原先的Native的实现(具体的测试结果,属于敏感数据,要走披露流程,伤不起…)

但是这样很显然不能敷衍过去,仔细想了想,确实Flutter的定位并不是要替代Native,他只想做一个极致的跨端解决方案,所以还是要回到跨端解决方案的赛道,给您从性能角度比一比,谁才是更好的跨端开发方案?

参赛选手

[Flutter]

Flutter is Google’s mobile app SDK for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.

[REACT NATIVE]

We're working on a large-scale rearchitecture of React Native to make it more flexible and integrate better with native infrastructure in hybrid JavaScript/native apps.

鸣锣开赛

怎么比

怎么比较确实伤脑筋,自己也写了一个Flutter 和 一个RN的App,但是实在太丑陋,担心大家关注点都到我的烂代码上了,所以在Github上找到了一个跨端开发高手Car Guo,用Flutter和RN分别实现的一个实际可用的App,Car Guo谦虚表示其实也写的比较粗糙,但是在我看来这个是具备真实使用场景的App(Github客户端App,提供丰富的功能,旨在更好的日常管理和维护个人Github),还是有代表性的 [Flutter] github.com/CarGuo/GSYGi [REACT NATIVE] github.com/CarGuo/GSYGi

场景

1、默认登录成功 2、“动态”页,点击搜索按钮,搜索关键字“Java”,正常速度浏览3页,等第4页加载完成后回退 3、点击“趋势”页Tab,浏览Feeds到页面底部,点击最底部的Item,进入Item后,浏览详情+浏览3页的动态后回退,到“我的”Tab页 4、查看“我的”Feeds到底部,点击右上角搜索按钮,搜索关键字“C”,浏览3页后,等第4页加载完成后场景结束

测试工具

  • iOS
  • 掌中测(iOS端):CPU,内存
  • Instruments:FPS
  • Android
  • 基于Adb的Shell脚本:CPU,内存,FPS

测试机型

  • iOS:iPhone 5c 9.0.1 / iPhone 6s 10.3.2
  • Android:Xiaomi 2s 5.0.2 / Sumsung S8 7.0

数据分析

iOS

iPhone 5c 9.0.1



iPhone 6s 10.3.2



测试结论

1、Flutter在低端和中端的iOS机型上,FPS的表现都优于RN 2、CPU的使用上Flutter在低端机上表现略差于RN,中端机型略优于RN 3、值得注意的是内存上的表现(上图红色箭头区域),Flutter在低端机型上的起始内存和RN几乎一致,在中端机型上会多30M左右的内存(分析为Dart VM的内存),可以想到这应该是Flutter针对低端和中端机型上内存策略是不一样的,可用内存少的机型,Dart VM的初始内存少,运行时进行分配(这样也可以理解为什么在低端机上带来了更多的CPU损耗),中端机器上预分配了更多的VM内存,这样在处理时会更加的游刃有余,减少CPU的介入,带来更流畅的体验. 可以看出,Flutter团队在针对不同机型上处理更加的细腻,目的就是为了带来稳定流畅的体验。

Android

Xiaomi 2s 5.0.2



Sumsung S8 7.0


- 注: MFS - Max Frame Space: 指的是去掉buffer之后的两帧的时间差

测试结论

1、Flutter在高低端机的CPU上的表现都优于RN,尤其在低端的小米2s上有着更优的表现 2、Android端在原来FPS基础上增加了流畅度的指标,FPS和流畅度的表现Flutter优于RN(计算规则见附参考文章) 3、Android端的内存也是值得关注的一点,在小米2s上起始内存Flutter明显比RN多40M,RN在测试过程中内存飞涨,Flutter相比之下会更稳定,内存上RN侧的代码是需要调优的,同一套代码Flutter在Android和iOS上并没有很大的差异,但是RN的却要在单端调优,Flutter在这项比拼上又更胜一筹。 比较奇怪的是三星S8上Flutter和RN的初始内存是一致的,猜测是RN也Android高端机型上也会预分配一些内存,具体细节还需要更进一步的研究。

升旗仪式

看了之前的数据,做为裁判的我会把金牌颁给Flutter,在测试过程中的体验和数据上来看Flutter都优于RN,并且开发这个App的是一位Android的开发同学,Flutter和RN对于他来说都是全新的技术栈,Car Guo同学更倾向性地让大家得到一致性的使用体验,性能方面并没有投入太多的时间进行调优,由此看出Flutter在跨端开发上在同样投入的情况下,可以获得更佳的性能,更好的用户体验。

一些思考

拿到了这些数据,也感受到Flutter带来福利,那Flutter为什么可以做到这么流畅呢?Flutter是如何优化了渲染,Dart VM的Runtime是怎么玩的?请大家继续关注后续解密文章,感兴趣的同学欢迎加入闲鱼,成为跨端解决方案的领军者。

参考

  • Android FPS&流畅度: testerhome.com/topics/4
  • Android 内存获取方式: dumpsys meminfo packageName
  • Android CPU 通过busybox 执行 top命令获取
  • iOS CPU获取方式:累计每个线程中的CPU利用率
for (j = 0; j < thread_count; j++)
ATCPUDO *cpuDO = [[ATCPUDO alloc] init];
char name[256];
pthread_t pt = pthread_from_mach_thread_np(thread_list[j]);
if (pt) {
name[0] = '\0';
__unused int rc = pthread_getname_np(pt, name, sizeof name);
cpuDO.threadid = thread_list[j];
cpuDO.identify = [NSString stringWithFormat:@"%s",name];
thread_info_count = THREAD_INFO_MAX;
kr = thread_info(thread_list[j], THREAD_BASIC_INFO,(thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS) {
return nil;
basic_info_th = (thread_basic_info_t)thinfo;
if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds;
tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
cpuDO.usage = basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
if (container) {
[container addObject:cpuDO];
} // for each thread
  • iOS 内存获取方式:测试过程中使用的是phys_footprint,是最准确的物理内存,很多开源软件用的是resident_size(这个值代表的是常驻内存,并不能很好地表现出真实内存变化,这可以另开文章细谈)
if ([[UIDevice currentDevice].systemVersion intValue] < 10) {
kern_return_t kr;
mach_msg_type_number_t info_count;
task_vm_info_data_t vm_info;
info_count = TASK_VM_INFO_COUNT;
kr = task_info(mach_task_self(), TASK_VM_INFO_PURGEABLE, (task_info_t)&vm_info,&info_count);
if (kr == KERN_SUCCESS) {
return (vm_size_t)(vm_info.internal + vm_info.compressed - vm_info.purgeable_volatile_pmap);
return 0;
task_vm_info_data_t vmInfo;