docs/api/performance.md
经常有业务咨询 Hippy 有哪些优化手段,以及业务性能问题应该怎么分析,有哪些监控工具,本篇文档则系统的介绍了 Hippy 冷启动的性能优化的最佳实践。
介绍一下Hippy应用冷启动有哪些阶段
1. Hippy 运行环境启动 - 终端耗时 (启动)
2. 加载 Bundle的 JS 文件 - JS 引擎耗时 (自执行)
3. 应用实例的执行 - JS 引擎耗时(JS运行)
a. 业务代码逻辑执行
b. 业务 IO(网络、JS API)等待
c. React/Vue 框架 DIFF 耗时
d. Hippy JS SDK计算耗时
4. 上屏显示 - 终端耗时 (渲染)
a. Hippy DOM 构建、排版耗时
b. Hippy 渲染上屏耗时
详细介绍参考:Hippy冷启动核心逻辑和数据梳理
我们把启动阶段分为 4个阶段:Hippy 启动、Bundle加载、应用实例执行、渲染上屏。
不同规模的 Hippy 业务,这些阶段耗时不尽相同,大家可以先测试各阶段耗时占比,再针对性的来分析。
Hippy 团队过去有一些经验可供参考:
1. 一般耗时大头是阶段 3 (一半耗时占比超过 50%)
业务可重点排查 业务 JS 执行耗时、**IO(网络、JS API)等待耗时。**此外,节点数过多、CSS 属性比较复杂,也会影响 Hippy SDK的计算耗时
2. JS Bundle包过大,会影响阶段 2 的耗时
3. 节点数过多,节点层级比较深,渲染样式比较复杂,会影响阶段 3 的耗时
在 Hippy 3.0,我们设计了用于性能分析的 Performance API:
Performance API 冷启动打点指标:
| 指标 | 对应 Key |
|---|---|
| Hippy 引擎加载开始 | hippyNativeInitStart |
| JS 引擎加载开始 | hippyJsEngineInitStart |
| JS 引擎加载结束 | hippyJsEngineInitEnd |
| Hippy 引擎加载结束 | hippyNativeInitEnd |
| JS Bundle 自执行耗时 | bundleInfo[] |
| 业务入口执行开始 | hippyRunApplicationStart |
| 业务入口执行结束 | hippyRunApplicationEnd |
| 首帧绘制开始 | hippyFirstFrameStart |
| 首帧绘制结束 | hippyFirstFrameEnd |
| 启动耗时 | duration |
监控方式:
1. Hippy 3.x版本:可以接入最新 aegis-sdk (腾讯内部咨询 端框架小助手),已经基于 performance api,可以直接获取打点数据
2. Hippy 2.x 版本可以基于业务代码打点,获取启动阶段的近似耗时数据。可参考:https://km.woa.com/articles/show/557810?kmref=search&from_page=1&no=1
1. Perfdog
a. 可查看 FPS, CPU, GPU, 内存等变化情况
2. 安卓系统自带工具:
a. GPU 渲染模式分析: 通过在 Android 设备的设置 APP 的开发者选项里打开 “ GPU 渲染模式分析” 选项,选择 ” 在屏幕上显示为条形图 “ 。
b. 过度绘制:Android 设备的设置 APP 的开发者选项里打开 “ 调试 GPU 过度绘制 ”
c. 查看界面边界:系统设置 – 开发者选项 – 绘图 --显示布局边界
3. iOS系统 simulator 模拟器
a. Debug 下自带分析工具 Color Blended Layers 图层绘制 Color Off-screen Rendered 离屏渲染
4. Profile工具
a. Android Studio Profiler
b. Xcode Instruments
c. Hippy Devtools (也在进一步完善中,欢迎大家提需求),如下示例:
如何优化:
定位到耗时点,接下来就可以针对性分析。
先简单介绍常用的优化:
1. 预加载:大幅降低 1-2 阶段耗时
2. 预渲染/SSR/Native-Vue:大幅降低 1-3阶段耗时
3. 数据预拉取:降低 3阶段 IO 等待耗时
4. JSI、动态加载、节点数优化:降低 3 阶段耗时
5. 节点缓存/渲染优化/图片缓存:降低 4 阶段耗时
6. 骨架屏优化/Loading:优化交互体验
...
接下来详细介绍这些优化点:
通过提前执行1+2阶段逻辑优化首屏耗时。
这两个阶段耗时主要被封装在HippyBridge中,因此可以通过提前初始化HippyBridge达成目的。
iOS 代码示例:
HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil bundleURL:url moduleProvider:^NSArray<id<HippyBridgeModule>> * {
return nil; // 或返回希望替换的module实例
} launchOptions:option executorKey:nil];
// 将bridge保存起来
安卓代码示例:
mHippyEngine.initEngine(new HippyEngine.EngineListener() {
@Override
public void onInitialized(EngineInitStatus statusCode, String msg) {
if (statusCode == EngineInitStatus.STATUS_OK) {
HippyBundleLoader loader = ...;
// e.g.:
// new HippyAssetBundleLoader(context, assetName, true, "demo");
// new HippyFileBundleLoader(filePath, true, "demo");
mHippyEngine.preloadModule(loader);
}
}
}
优点:
1. 不依赖页面数据,在任何页面都可考虑使用预加载
2. 前端也可以在预加载时提前fetch数据
缺点:
1. 优化效果相对预渲染不够明显
如果还希望在预加载基础上继续优化体验,还可以进一步提前执行步骤 3 的逻辑。
优点:
1. 优化效果十分明显
缺点:
1. 内存开销大
2. 无法对动态数据的页面使用
3. 页面发版会导致命中率降低
4. 预渲染时的Dimension可能与访问时不一致,导致布局异常
5. 在Android上预渲染只能使用AppContext,导致页面打开后也无法使用依赖Activity的能力,例如Modal(Dialog)组件
接入代码示例:
iOS 代码示例
HippyRootView *hippyRootView = [[HippyRootView alloc]
initWithBridge:bridge
businessURL:businessURL
moduleName:appName
initialProperties:properties
delegate:delegate];
// 将hippyRootView保存起来
执行这一步后,HippyRootView内部将会执行实例的加载,但由于rootView未被真正挂在到VC的UI树上,所以暂时不可见。
当需要显示的时候可以在VC的view上将hippyRootView加为子view。
[parent addSubview:hippyRootView];
// 必要时可以更新一下frame
hippyRootView.frame = newFrame;
// 如果在addSubview后还有其他的subview的添加,可以考虑通过bringSubviewToFront将hippyRootView改为最前方显示
[parent bringSubviewToFront:hippyRootView];
安卓代码示例:
除了需要把 ModuleLoadParams.context 设置为 AppContext 外,其余步骤和常规加载一致。
HippyEngine.ModuleLoadParams loadParams = new HippyEngine.ModuleLoadParams();
loadParams.context = getApplicationContext();
...
mHippyView = mHippyEngine.loadModule(loadParams, listener, null);
SSR 代替预渲染方案的另一选择。
方案原理:把 1-3 阶段放在服务端执行,解析成最终的 Hippy指令 的字符串,启动时直接反序列化 Hippy指令,下发Hippy终端执行渲染。
优化效果:性能数据与预渲染接近
详见QQ游戏中心优化 https://km.woa.com/articles/show/564026?kmref=search&from_page=1&no=1
优点:
1. 可不依赖终端方案方案,前端执行
2. 相比预渲染方案更节省内存
缺点:
1. 需要执行事件挂载逻辑,可交互时间会延迟
2. 会带来服务器的运营成本
在不方便应用 预渲染/SSR/Native-Vue的场景。可以考虑提前下载数据,节省 3阶段 网络 IO 耗时:
1. 可以在终端预请求,在启动Hippy后,把数据传给前端
2. 可以结合预加载,在前端 JS 自执行阶段,前端预请求数据。等待组件渲染时直接使用。
1. Dom Node 缓存(安卓)
接入方案详见:https://doc.openhippy.com/#/feature/feature2.0/dom-cache
2. RenderNode 缓存(3.0 安卓)
接入方案详见:https://doc.openhippy.com/#/feature/feature3.0/render-node-snapshot
在不方便做 JS 预加载的场景,如果要优化 JS 自执行耗时,可以考虑对 JS 引擎本身做优化:
把编译和解析的结果缓存下来,等到下次遇到相同的文件,直接跳过这个过程,把直接缓存好的数据拿来使用;
Hippy 2.0: Hermes 接入指引文档:https://iwiki.woa.com/p/4007348225
Hippy 3.0 Hermes会直接集成,文档待补充。
接入文档指引:https://iwiki.woa.com/p/491739348
Hippy 打包插件只支持一个jsbundle包的生成。随着业务逻辑越来越复杂,jsbundle越来越大、包的加载时间越来越长。为了解决这个问题,Hippy 在 2.2.0 版本增添了动态加载能力,Hippy 的开发人员可以按照业务需求来动态引入 JS。
Hippy 在 2.5.5 版本增加了远程网络加载模式的支持,业务可对每个bundle自定义不同的加载模式。
webpack打包脚本中引入插件:HippyDynamicImportPlugin
const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin');
module.exports = {
...
plugins: [
new HippyDynamicImportPlugin(),
],
};
(1)加载本地 JS 包:
与 Web 开发动态加载能力一样,直接使用 import() 语法即可
(2)加载远程 JS 包:
i. webpack打包脚本配置全局 publicPath(可选)
// webpack output 配置
output: {
...
publicPath: 'https://xxxx/hippy/hippyVueDemo/',
},
ii. 在业务代码引用分包的入口配置 magic comment的 webpackChunkName(必须) 和 customChunkPath(可选),如果没有配置customChunkPath,会默认使用全局 publicPath; 以 Hippy-Vue 为例:
// Hippy-Vue 配置
AsyncComponentFromHttp: () => import(/* customChunkPath: "https://xxx/hippy/hippyVueDemo/", webpackChunkName: "asyncComponentFromHttp" */'./dynamicImport/async-component-http.vue')
.then(res => res)
.catch(err => console.error('import async remote component error', err))
接入方案详见:https://doc.openhippy.com/#/feature/feature2.0/jsi
优化效果:
Android 代码示例:
// 初始化引擎开启 enableTurbo
val initParams = HippyEngine.EngineInitParams()
initParams.enableTurbo = true
// 定义 module
@HippyNativeModule(name = "demoTurbo")
class DemoTurboModule(context: HippyEngineContext?) : HippyNativeModuleBase(context) {
...
@HippyMethod(isSync = true)
fun getNum(num: Double): Double = num
...
}
iOS 代码示例:
// 方式一:bridge初始化时通过配置参数设置生效
NSDictionary *launchOptions = @{@"EnableTurbo": @(DEMO_ENABLE_TURBO)};
HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:self
bundleURL:[NSURL fileURLWithPath:commonBundlePath]
moduleProvider:nil
launchOptions:launchOptions
executorKey:@"demoTurbo"];
// 方式二:bridge初始化完成后,设置属性生效
HippyRootView *rootView = [[HippyRootView alloc] initWithBridge:nil
businessURL:nil
moduleName:@"demoTurbo"
initialProperties:@{@"isSimulator": @(isSimulator)}
launchOptions:nil
shareOptions:nil
debugMode:YES
delegate:nil];
rootView.bridge.enableTurbo = YES;
@implementation TurboConfig
...
// 注册模块
HIPPY_EXPORT_TURBO_MODULE(TurboConfig)
// 注册交互函数
HIPPY_EXPORT_TURBO_METHOD(getInfo) {
return self.strInfo;
}
HIPPY_EXPORT_TURBO_METHOD(setInfo:(NSString *)string) {
self.strInfo = string;
return @(YES);
}
...
@end
Hippy-React 代码示例:
Hippy-Vue 代码示例:
1. 减少 getElemCss 方法的调用
Vue.Native.getElemCss()
2. 减少 scoped 的使用
vue scoped 能力是 2.15.0 版本引入,scoped会转换为 属性选择器,因此解析性能会比较差;
在 Hippy 3.0 版本,我们使用 css-module的方案优化了 scoped的解析方式,性能得到提升。
3. 减少 CSS 属性选择器、伪类等样式的使用
4. 升级 Hippy-Vue-Next(Vue3)
Vue3 升级指引:https://hippyjs.org/#/hippy-vue/vue3
Vue3 相比 Vue2 做了 Diff算法优化、静态提升、事件缓存等优化,从Vue框架层面会带来性能提升。实测了 Vue3 和 Vue2 在Hippy框架下的数据对比:
1. Hippy React节点更新优化:
https://iwiki.woa.com/p/1335183393
@hippy/react 在 2.12.0 版本应用上了最新的渲染优化,针对 react 16 和 react 17,先删除原来的 react-reconciler 包依赖,再分别引入
"@hippy/react-reconciler": "react16"
"@hippy/react-reconciler": "react17"
render 函数中打点统计。shouldComponentUpdate、PureComponent 等优化重复渲染。ListView 代替 ScrollView,ListView 通过 View 的缓存与重用大大提升渲染性能。getRowType,将 item 按类型合理拆分,同一类型的 item 的 RowType 相同,可复用。ListView item 的 DOM 结构一致:
v-show,不要用 v-if。if 条件来改变节点结构。ViewPager 的子节点尽量不要全加载,实现懒加载。src 和 background。 代码示例:在自定义ImageLoader中实现自己的缓存逻辑,也可使用第三方图片加载库,如 slide 等。
HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams();
// 必须:宿主(Hippy的使用者)的Context
// 若存在多个Activity加载多个业务jsbundle的情况,则这里初始化引擎时建议使用Application的Context
initParams.context = this;
...
// 必须:图片加载器
initParams.imageLoader = new MyImageLoader(this.getApplicationContext());
此外,也要检查业务代码自身的耗时,并做针对性优化。
1. 防抖节流:对滚动事件、数据上报等进行防抖节流,避免频繁触发计算
2. 耗时逻辑检查是否可以放在首屏后执行
除了上面的优化手段,交互优化在一些场景也是十分重要的,可以提升打开体验,让页面视觉上更流畅。
1. 骨架屏优化
2. Loading
3. 切换动画
优化总结、是否需要 终端/前端 同学支持。
| 优化方案 | 方案介绍 | 接入成本 | 优化效果 | 终端/前端工作 |
|---|---|---|---|---|
| 预加载 | 优化 JS Bundle包加载 | 中 | 中 | 终端工作 |
| 预渲染 | 基于预加载,进一步优化渲染耗时 | 中 | 好 | 终端工作 |
| SSR | 预渲染的替代方案 | 中 | 好 | 前端工作 |
| Native-Vue | 预渲染的另一种替代方案 | 高 | 好 | 前端工作 |
| 数据预请求 | 节省 IO 耗时 | 低 | 中 | 终端或前端工作 |
| 节点缓存 | 仅安卓支持,优化渲染耗时明显 | 低 | 中 | 终端工作 |
| JS引擎优化(Hermes) | 性能比JSC优化明显 | 高 | 好 | 终端工作 |
| Bundle动态加载 | 分包加载,节省 JS 执行耗时 | 中 | 中 | 前端工作 |
| JS API优化(JSI) | JSI 接口同步调用,等待耗时降低 | 低 | 中 | 前端工作 |
| Hippy-Vue 逻辑优化 | 优化 css 解析耗时 | 低 | 好 | 前端工作 |
| Hippy-React 逻辑优化 | 渲染队列优化 | 低 | 中 | 前端工作 |
| 渲染优化 | 组件使用的渲染耗时优化 | 低 | 中 | 前端工作 |
| 业务逻辑优化 | 业务自身逻辑优化 | 无 | 中 | 前端工作 |
| 交互优化 | 业务自身优化交互 | 无 | 体验上优化 | 前端工作 |
1. K歌 Hippy性能优化分享(一)
https://km.woa.com/articles/show/511822?kmref=search&from_page=1&no=7
2. k歌 Hippy性能优化分享(二)
https://km.woa.com/articles/show/484172?kmref=search&from_page=1&no=5
3. k歌 Hippy性能优化分享 (三)
https://km.woa.com/articles/show/504866?kmref=search&from_page=1&no=3
4. k歌渲染优化
https://km.woa.com/articles/show/465625?kmref=search&from_page=1&no=2
5. 小说 Hippy性能优化
https://km.woa.com/articles/show/484172?kmref=search&from_page=1&no=5
6. 浏览器小说阅读器内存优化
https://km.woa.com/articles/show/468088?kmref=search&from_page=1&no=6
7. QQ 增值Hippy性能优化
https://km.woa.com/articles/show/516658?kmref=search&from_page=1&no=4
8. QQ直播Hippy性能优化
https://km.woa.com/articles/show/557810?kmref=search&from_page=1&no=1
9. QQ游戏中心Hippy SSR优化
https://km.woa.com/articles/show/564026?kmref=search&from_page=1&no=1
10. QQ 音乐Hippy性能监控工具建设
https://km.woa.com/articles/show/494153?kmref=search&from_page=1&no=6
11. 浏览器 Hermes 引擎优化性能
https://km.woa.com/articles/show/567715?kmref=search&from_page=1&no=4
12. QB长视频业务优化-接入Hermes引擎
https://km.woa.com/articles/show/564575?kmref=search&from_page=1&no=7
13. Hippy 内存、CPU性能优化
https://km.woa.com/articles/show/484158?kmref=search&from_page=1&no=8
14. 闪现社区首屏优化
https://km.woa.com/articles/show/474636?kmref=search&from_page=1&no=9
15. NativeVue:解决Hippy等类RN框架首屏白屏的极致方案
https://km.woa.com/articles/show/527262?kmref=search&from_page=1&no=10
16. Hippy profile工具实现的思考
https://km.woa.com/articles/show/397231?kmref=search&from_page=1&no=6
https://km.woa.com/articles/show/445938
17. 记一次Hippy-React优化
https://km.woa.com/articles/show/431695?kmref=search&from_page=1&no=9
18. Flutter性能优化最佳实践
Flutter 应用性能优化最佳实践 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter
19. React Native 性能综述
20. 营地性能优化
https://km.woa.com/articles/show/599930?kmref=search&from_page=1&no=10