揭秘 Hummer —— 为何选择 Hummer ?

3,226 阅读16分钟

文章转载至:揭秘 Hummer —— 为何选择 Hummer ?

揭秘 Hummer —— 为何选择 Hummer ?

前言

在今年一月份,我们开源了在滴滴内部得到广泛应用的轻量级跨端技术框架——Hummer。为了响应广大社区小伙伴的期待,我们就Hummer的整体表现做了多维度的对比测试,方便大家对Hummer有一个更深入的了解。

在此之前,我们先来简单介绍一下目前行业内几款相对成熟的跨端框架:

  1. ReactNative。 ReactNative 是 Facebook 在 2015 年正式开源的一款基于 React 生态的移动端跨端开发框架,目标是在移动端开发中做到:“Learn once, write anywhere”,目前 GitHub 上的 star 数是 94k,Facebook 官方一直在对其进行重点建设和维护,社区活跃度较高。
  2. Weex。 Weex 是阿里在 2016 年开源的一款基于 Vue 生态的移动端跨端开发框架,目标是在移动端开发中做到:“Write Once, Run Everywhere”,在 2016 年年底,阿里将其捐赠给了 Apache 基金会,后来因为种种原因几乎停止了维护,开源社区也逐渐冷清,目前 GitHub 上的 star 数是 14k。
  3. Flutter。 Flutter 是 Google 在 2017 年的 I/O 大会上正式对外发布的一款全新的移动端跨端开发框架,其最显著的特点是自带了 Skia 渲染引擎和渲染控件以替代两端系统控件,做到了很好的平台一致性和性能。目前 GitHub 上的 star 数已有 117k,是 Google 官方重点维护的一个跨端开发项目,社区十分活跃。

而我们 Hummer 在建立之初,就确立了自己的定位和方向。我们致力于打造一款具有更小的包体积更高的综合性能、以及更健全的样式支持的跨端开发框架,正如 Hummer 的名字“蜂鸟”一样,拥有小巧轻盈的体态、迅猛强健的翅膀、以及色彩艳丽的外表。

更小的包体积

Hummer 起源于滴滴内部的聚合收银SDK,其核心业务场景和滴滴严格的包体积标准,都要求我们必须将Hummer的包体积打磨至极致。

我们也做了深入的研究和探索,包括裁剪 JavaScriptCore 引擎、调研其他小体积的 JS 引擎等等,最终我们选择了 JavaScriptCore 作为 iOS 端的 JS 引擎,QuickJS 作为 Android 端的 JS 引擎。

JavaScriptCore 是 Apple 开源并用于 WebKit 的内嵌 JS 引擎,想必大家都非常了解。在这里我们主要介绍一下 QuickJS。

QuickJS 是 Bellard 大神于 2019 年发布的一款面向嵌入式的 JS 引擎,寥寥几个 C 文件,就实现了非常完善的功能,支持 ES2020 规范,语法支持度甚至比 V8 还要高。可能大家会关注它的性能和稳定性表现如何?根据 QuickJS 官网放出的基准测试和 Test262 测试结果显示,其性能和稳定性并不差,在各类 JS 引擎中基本属于中上水平。经过综合评估,我们选择 QuickJS 作为 Hummer 在安卓端的默认 JS 引擎。

而说到 Android 端,很多人会觉得和 JavaScriptCore 对标的不是 V8 引擎么?V8 引擎确实存在于每一个内置了 Chromium 的 WebView 的 Android 系统中,但是 V8 和 WebView 捆绑得太紧密,不像 iOS 中的 JavaScriptCore 封装成了系统库可以被所有 App 调用。这直接导致,要使用 V8 还是得开发者自己通过源码封装一遍,会严重影响 App 的包体积,所以没有进入我们的考虑范围。

除此之外,我们为了极致的包体积体验,精简了对 JS 引擎特性的依赖,实现了一套抽象 JS 引擎接口,抹平了各类 JS 引擎的实现差异。经过我们的极致优化之后,Hummer 甚至可以与其他跨端框架共享 JS 引擎,完美应对各类复杂业务和技术场景,而不用过于担心包体积带来的额外成本和压力。

以下是一个纯净的 App 在接入各家跨端框架之后,在单架构(Android: armeabi-v7a、iOS: arm64) 模式下,生成的 Release 包总大小对比情况:

测试版本

  • Hummer(Android): 'com.didi.hummer:hummer:0.3.18'
  • Hummer(iOS): 0.2.3
  • ReactNative: 'com.facebook.react:react-native:0.63.4'
  • Weex: 'com.taobao.android:weex_sdk:0.26.0'
  • Flutter: 1.22.6

测试代码

github.com/OrangeLab/h…

测试结果

平台NativeHummerReactNativeWeexFlutter
Android2.5M4.2M8.5M6.9M5.2M
iOS0.097M0.697M1.2M0.997M5.9M

更高的综合性能

Hummer 作为一款基于 JS 引擎的跨端方案,经常会被关注的另一个重点是它的综合性能表现。我们的合作伙伴在选型时,通常会问的第一个问题就是,Hummer 跟原生 App 或者 WebView 中的 H5 相比性能如何?和目前业界已经存在的其他跨端框架相比,性能优势又在哪里?

为了更好的衡量Hummer的综合性能表现,我们在用户的主观感受之外,建立了一套量化指标体系。

在移动端,一般我们会以页面渲染时间、CPU、内存、帧率这几个指标,来整体衡量一款 App 的性能表现。下面我们就分别针对这四个指标,使用四个不同场景的测试用例,分别对各家跨端框架做了一次较为全面的性能对比测试。四个测试用例如下:

  • 用例1:长列表基准测试(Scroller)。 列表中500行视图,每一行视图中包含5个子视图,测试快速滑动整个列表时的性能。
  • 用例2:长列表基准测试(List)。 列表中1000行视图,每一行视图中包含5个子视图,测试快速滑动整个列表时的性能。
  • 用例3:动画基准测试。 500个视图,每个视图分别做5种动画中的其中1种,测试所有动画同时执行时的性能。
  • 用例4:拖拽基准测试。 测试在屏幕范围内拖拽一个视图时的性能。

在长列表场景的测试中,我们测试了两种类型的长列表,一种是以 Scroller 为代表的无复用式长列表视图,有多少数据源就会创建多少视图,另一种是以 List 为代表的可复用长列表视图,不管数据源有多少,永远只会创建屏幕可见范围内的视图数量,并在列表滚动过程中复用这些视图,以此来提高性能和减少内存占用。

动画和拖拽,都属于富交互的一种,对性能要求极高,他们的性能优劣,更能直接影响用户的使用体验,所以我们用两个用例来分别测试动画和拖拽的性能表现。

我们针对这四个用例,在 Android 和 iOS 设备上,都分别做了以上四个指标的性能测试,其中 Android 和 iOS 由于系统不同,测试方式也略有不同,下面先介绍一下我们在这两个系统上,分别是如何测试这四个指标的:

Android 端:

  1. 页面渲染时间
    Android 端可以通过 am 命令直接启动 App 中的某个页面,再通过 logcat 过滤出带有 “Displayed” 字符串的日志,就可以看到一个页面的启动耗时了,也就是页面的渲染时间。但是 Hummer、ReactNative 和 Weex 这三个跨端框架的 App,都是通过远程加载 JS 的方式显示页面的,这会对页面渲染时间的统计造成影响,所以这里全部改成了加载本地 JS 的方式来启动 App。其中 ReactNative 和 Weex 由于采用的是渐进式页面渲染方式,在页面启动并渲染出第一帧之后(即“Displayed”日志打印出来之后),实际页面中的元素还没有真正渲染完毕,所以对于他们的页面渲染时间统计,需要结合 “Displayed”日志页面渲染完成回调来一起计算页面渲染时间。其中 ReactNative 的页面渲染完成回调是 JS 侧的 componentDidMount 方法,Weex 的页面渲染完成回调是原生侧的 onRenderSuccess 方法。
  2. CPU 和内存
    Android 端可以直接通过 AndroidStudio 中内置的 profile 工具来检测 CPU 和 内存的使用情况,但是只能对 debug 版本的应用才能检测,而我们为了使测试数据尽量准确,都是使用 release 版本的 App 进行测试的,所以我们最终选择了使用腾讯的 PrefDog(性能狗)工具来检测 CPU 和内存数据。
  3. 帧率
    利用 PrefDog 也是可以检测出帧率的,但是我们发现对于某些场景,尤其是动画相关的场景,PrefDog 测试的结果并不准确,所以我们最终是通过 Android 系统自带的 dumpsys gfxinfo 命令来检测帧率的。在 Android 系统的“开发者选项”中,开启 “dumpsys gfxinfo” 开关,就可以在命令行中,通过输入 adb shell dumpsys gfxinfo 命令,来打印出最近 120 帧的耗时数据。再通过这个公式:fps = 实际帧数/(实际帧数+额外同步脉冲数) * 60,来近似计算出实际帧率,其中“额外同步脉冲数”是指在这 120 帧中,那些一帧超过 16.67ms 的总帧数。

iOS 端:

  1. 页面渲染时间
    iOS 的页面渲染时间没有区分首次和非首次,是因为 iOS 端即使杀掉应用再启动,也不一定是冷启动,所以这里不再区分首次和非首次了。iOS 使用 instruments 中的 App Launch 并配合日志来分析启动时长,具体公式如下:启动时长 = premain耗时 + 渲染时间,其中:渲染时间 = 渲染结束时间戳 - appdelegate.didFinishLaunching开始时间戳。由于 ReactNative 和 Weex 采用的是渐进式页面渲染方式,在页面启动并渲染出第一帧之后,实际页面中的元素还没有真正渲染完毕,所以对于他们的页面渲染时间统计,需要结合启动时长页面渲染完成回调来一起计算页面渲染时间。其中 ReactNative 的页面渲染完成回调是 JS 侧的 componentDidMount 方法,Weex 的页面渲染完成回调是原生侧的 onRenderSuccess 方法。
  2. CPU 和内存
    iOS 端可以通过 instruments 工具来统计 CPU 和内存数据,但是 instruments 对于导出的统计数据并不友好,很难计算一段时间内的平均值。所以我们最终也选择了使用 PrefDog 工具来检测 CPU 和内存数据。
  3. 卡顿率
    iOS 端我们也没有使用 PrefDog 来检测帧率,原因同上面 Android 端,iOS 端使用 instruments 中的 animation hitches 工具来分析卡顿。
    根据 WWDC 的中的定义:
    卡顿帧:屏幕上晚于预计时间出现的帧;
    卡顿率:卡顿时间/总帧数时间;
    卡顿时间:将图像帧延迟显示在屏幕上的时间总和;
    卡顿指标(hitch rate)可以被分为三档:
    • <5 ms/s,滑动流畅;
    • 5-10 ms/s,每隔几秒会有图像帧被丢弃,轻微卡顿;
    • > 10 ms/s,图像帧被频繁丢弃,严重卡顿;

具体的测试条件和测试结果如下:

测试机型

Android

  • 系统: Android 10
  • 型号: vivo X27 Pro - V1836A
  • vivo ROM: Funtouch OS_10 PD1836_A_6.20.1

iOS

  • 系统: iOS 14.4
  • 型号: iPhoneX

测试版本

  • Hummer(Android): 'com.didi.hummer:hummer:0.3.18'
  • Hummer(iOS): 0.2.3
  • Tenon: 1.2.1
  • Weex: 'com.taobao.android:weex_sdk:0.26.0'
  • ReactNative: 'com.facebook.react:react-native:0.63.4'
  • Flutter: 1.22.6

用例代码

github.com/OrangeLab/h…

用例页面

首页用例1用例2用例3用例4
首页用例1用例2用例3用例4

(以Native项目用例页面为例)

测试结果

无论是 Android 端还是 iOS 端,Hummer 和 Tenon 的整体表现,基本都很接近原生性能,足以见得 Hummer 的强大之处。

Android

用例1 (Scroller)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(首次, ms)7411835261716642834862
页面渲染时间(非首次, ms)5901560218215082579568
CPU(%)7.5310.7210.6911.710.119.94
内存(M)114.89133.45137.94167.8178.5210.17
帧率(fps)585051474931
用例2 (List)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(首次, ms)3343875595453477535
页面渲染时间(非首次, ms)1702153353683238215
CPU(%)4.074.856.8514.435.0810.44
内存(M)58.1464.6270.48173.89224.08152.38
帧率(fps)606060586059
用例3 (动画)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(首次, ms)34564110447781881538
页面渲染时间(非首次, ms)1953857306361394224
CPU(%)14.2715.4815.333.3117.2218.58
内存(M)86.988.97102.53118.75102.56172.28
帧率(fps)303030312930
用例4 (拖拽)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(首次, ms)253310342425396514
页面渲染时间(非首次, ms)168175183287185213
CPU(%)3.035.1967.515.966.94
内存(M)55.5563.1666.5102.1766.57142.21
帧率(fps)606059606060

iOS

用例1 (Scroller)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(ms)17271970224923581694806
CPU(%)1.73225.453.188.18
内存(M)116149156.27177.27144125.18
卡顿率(ms/s)1.6671.6671.6681.6781.6680
用例2 (List)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(ms)4695456849754141608
CPU(%)3.365.645.8218.459.918.27
内存(M)9.3621.1823.5588.3691.5591.82
卡顿率(ms/s)004.167.593.4192.902
用例3 (动画)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(ms)773905113113531535655
CPU(%)1.5521.9130.556.738.45
内存(M)61317.272656.7381.73
卡顿率(ms/s)000000
用例4 (拖拽)NativeHummerTenonReactNativeWeexFlutter
页面渲染时间(ms)539563578922545708
CPU(%)25.556.187.557.185
内存(M)51012141654
卡顿率(ms/s)000000

更健全的样式支持

如果仅仅是面向客户端研发,Hummer 已经具备了极好的两端一致性。但是对前端样式的全面支持,可以最大化地借助当下庞大且完善的前端生态,快速拓展 Hummer 的应用场景,提升历史兼容性。

相较于 ReactNative 尽量保持 Native 特性的设计原则,针对某些特性提供两端不同的 API。 Hummer 坚持了尽量保持两端一致性的设计原则,统一采用和 Web 生态一致的样式,在保证两端的一致性的同时,实现对前端生态的高度兼容。

目前 Hummer 整体采用 Flexbox 布局方案,除此之外对于其它一些常用的 CSS 样式都做了很好的两端对齐,下面举一些比较典型的 CSS 样式做对比:

功能描述CSS样式HummerReactNativeWeex
盒模型box-sizingborder-boxborder-boxborder-box
布局方式positionrelative
absolute
fixed
relative
absolute
relative
absolute
fixed
背景颜色background-color支持支持支持
背景图片background-image支持支持支持
普通边框&圆角border-style
border-color
border-width
border-radius
支持支持支持
单边边框&圆角border-left-width
border-top-left-radius
支持只支持View组件圆弧过渡不平滑
阴影shadow支持Android 和 iOS 分别是两套 API 和 UI 效果Android 不支持
堆叠顺序z-index支持支持不支持
内容溢出overflow支持支持Android 不支持

相较于 RN 和 Weex,Hummer 在设计之初就考虑到了样式优先级的重要性,在这方面也做了大量尝试,目前正在内部测试阶段,在未来的版本中将对外发布。

除了常用的 CSS 样式对比之外,还有一些比较常用的属性对比,比如:

功能描述HummerReactNativeWeex
富文本支持支持支持
GIF图支持Android 默认不支持Android 默认不支持
无障碍功能支持支持部分支持

更丰富的动画

在动画的实现方面,Hummer 通过抽象的动画 API,使开发者可以实现各种复杂的动画效果,并且具备很高的两端一致性。

下面是我们对比各个跨端框架在动画方面的表现:

功能描述属性HummerReactNativeWeex
动画 API基础动画支持支持支持
帧动画支持不支持不支持
动画属性duration支持支持支持
delay支持支持支持
easing支持支持支持
repeatCount支持支持不支持
repeatModeAndroid 支持
iOS 开发中
不支持不支持
CSS样式动画transition支持不支持支持

其他方面

Hummer 除了在包体积性能样式动画方面有不小的优势之外,在其他方面也有不俗的表现,下面我们对前端生态的支持程度框架整体的特性方面也都做了简单的对比,各位小伙伴可以从多维度更加直观地感受到 Hummer 的对比表现。

前端生态支持

Hummer 对于前端框架的支持是比较友好的,目前已支持到 Vue3.0,对 React16+ 的支持也正在全力开发中。

TenonReactNativeWeex
Vue3.0支持不支持不支持
React16+支持中支持不支持

框架整体特性

HummerReactNativeWeexFlutter
框架复杂度很轻较重较轻很重
上手难度容易较高一般较高
适用场景整体App或单页面整体App单页面整体App
社区成熟度刚起步,正在建设中成熟,Facebook重点维护捐献给Apache,已不再维护成熟,Google重点维护

总结

综上所述,Hummer 虽然是一套相对年轻的跨端技术方案,但是其整体表现已具备很大的竞争力。各位感兴趣的小伙伴都可以在自己项目中进行小规模的尝试,评估效果。在使用过程中如有遇到任何问题,都可以通过我们的官方 QQ 交流群及时给我们反馈,同时也欢迎大家在 GitHub 上积极给我们提 issues 和 PRs,我们会第一时间跟进和处理。

联系我们

Hummer GitHub 地址: github.com/didi/Hummer
Benchmark GitHub 地址: github.com/OrangeLab/h…
QQ 交流群: 851327307
微信公众号: 滴滴OrangeLab

团队成员

张丹枫: Hummer 负责人,专注于 Hummer 框架的整体技术建设和社区维护工作。
樊远东: Hummer 核心成员,主要负责 Hummer 框架的整体架构设计。
史广远: Hummer 核心成员,主要负责 Hummer 框架的 iOS 端研发工作。
唐佳诚: Hummer 核心成员,主要负责 Hummer 框架的 iOS 端研发工作。
段丽康: Hummer 核心成员,主要负责 Tenon 框架的设计和研发工作。
曹恩泽: Hummer 核心成员,主要负责 Tenon 框架的设计和研发工作。

延伸阅读

《滴滴开源轻量级跨端开发框架:Hummer》