背景
论坛上很多小伙伴关心为什么闲鱼选择了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] https://github.com/CarGuo/GSYGithubAppFlutter
[REACT NATIVE] https://github.com/CarGuo/GSYGithubApp
场景
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&流畅度: https://testerhome.com/topics/4775
- 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;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
if (result != KERN_SUCCESS)
return 0;
return (vm_size_t)vmInfo.phys_footprint;
原文链接
本文为云栖社区原创内容,未经允许不得转载。