Files
Hippy/docs/api/performance.md
2025-10-13 12:29:34 +08:00

760 lines
22 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# **Hippy 业务冷启动性能优化指引**
## **背景**
经常有业务咨询 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冷启动核心逻辑和数据梳理](https://doc.weixin.qq.com/doc/w3_AJcAmQbdAFwmCbIe8OPScGUN6amkw?scode=AJEAIQdfAAo1QKXNGAAJcAmQbdAFw)
我们把启动阶段分为 4个阶段:Hippy 启动、Bundle加载、**应用实例执行**、渲染上屏。
不同规模的 Hippy 业务,这些阶段耗时不尽相同,大家可以先测试各阶段耗时占比,再针对性的来分析。
Hippy 团队过去有一些经验可供参考:
​ 1. 一般耗时大头是阶段 3 (一半耗时占比超过 50%)
业务可重点排查 **业务 JS 执行耗时**、**IO(网络、JS API)等待耗时。**此外,**节点数过多、CSS 属性比较复杂**,也会影响 Hippy SDK的计算耗时
​ 2. JS Bundle包过大,会影响阶段 2 的耗时
​ 3. 节点数过多,节点层级比较深,渲染样式比较复杂,会影响阶段 3 的耗时
## **如何监控定位:**
### **1.** **线上监控:**
​在 Hippy 3.0,我们设计了用于性能分析的 Performance API:
[Hippy 性能监控设计文档](https://doc.weixin.qq.com/doc/w3_ANsAsgZ1ACcBlbHY905RpW7Qj1vij?scode=AJEAIQdfAAosjxQAlNANsAsgZ1ACc)
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
### **2.** **Profile 工具:**
​ 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.** **预加载**
通过提前执行1+2阶段逻辑优化首屏耗时。
这两个阶段耗时主要被封装在HippyBridge中,因此可以通过提前初始化HippyBridge达成目的。
iOS 代码示例:
``` javascript
HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil bundleURL:url moduleProvider:^NSArray<id<HippyBridgeModule>> * {
return nil; // 或返回希望替换的module实例
} launchOptions:option executorKey:nil];
// 将bridge保存起来
```
安卓代码示例:
``` javascript
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. 优化效果相对预渲染不够明显
### **2.** **预渲染**
如果还希望在预加载基础上继续优化体验,还可以进一步提前执行步骤 3 的逻辑。
优点:
​ 1. 优化效果十分明显
缺点:
​ 1. 内存开销大
​ 2. 无法对动态数据的页面使用
​ 3. 页面发版会导致命中率降低
​ 4. 预渲染时的Dimension可能与访问时不一致,导致布局异常
​ 5. 在Android上预渲染只能使用AppContext,导致页面打开后也无法使用依赖Activity的能力,例如Modal(Dialog)组件
接入代码示例:
iOS 代码示例
``` javascript
HippyRootView *hippyRootView = [[HippyRootView alloc]
initWithBridge:bridge
businessURL:businessURL
moduleName:appName
initialProperties:properties
delegate:delegate];
// 将hippyRootView保存起来
```
执行这一步后,HippyRootView内部将会执行实例的加载,但由于rootView未被真正挂在到VC的UI树上,所以暂时不可见。
当需要显示的时候可以在VC的view上将hippyRootView加为子view。
``` javascript
[parent addSubview:hippyRootView];
// 必要时可以更新一下frame
hippyRootView.frame = newFrame;
// 如果在addSubview后还有其他的subview的添加,可以考虑通过bringSubviewToFront将hippyRootView改为最前方显示
[parent bringSubviewToFront:hippyRootView];
```
安卓代码示例:
除了需要把 ModuleLoadParams.context 设置为 AppContext 外,其余步骤和常规加载一致。
``` javascript
HippyEngine.ModuleLoadParams loadParams = new HippyEngine.ModuleLoadParams();
loadParams.context = getApplicationContext();
...
mHippyView = mHippyEngine.loadModule(loadParams, listener, null);
```
### **3.** **SSR**
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. 会带来服务器的运营成本
### **4.** **数据预请求:**
在不方便应用 预渲染/SSR/Native-Vue的场景。可以考虑提前下载数据,节省 3阶段 网络 IO 耗时:
​ 1. 可以在终端预请求,在启动Hippy后,把数据传给前端
​ 2. 可以结合预加载,在前端 JS 自执行阶段,前端预请求数据。等待组件渲染时直接使用。
### **5.** **节点缓存**
​ 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
### **6.** **JS 引擎优化**
在不方便做 JS 预加载的场景,如果要优化 JS 自执行耗时,可以考虑对 JS 引擎本身做优化:
#### **6.1.** **V8 编译开启 Code Cache**
把编译和解析的结果缓存下来,等到下次遇到相同的文件,直接跳过这个过程,把直接缓存好的数据拿来使用;
​Hippy 2.0: Hermes 接入指引文档:https://iwiki.woa.com/p/4007348225
​Hippy 3.0 Hermes会直接集成,文档待补充。
### **7.** **bundle 包动态加载**
接入文档指引:https://iwiki.woa.com/p/491739348
Hippy 打包插件只支持一个jsbundle包的生成。随着业务逻辑越来越复杂,jsbundle越来越大、包的加载时间越来越长。为了解决这个问题,Hippy 在 2.2.0 版本增添了动态加载能力,Hippy 的开发人员可以按照业务需求来动态引入 JS。
Hippy 在 2.5.5 版本增加了远程网络加载模式的支持,业务可对每个bundle自定义不同的加载模式。
webpack打包脚本中引入插件:HippyDynamicImportPlugin
``` javascript
const HippyDynamicImportPlugin = require('@hippy/hippy-dynamic-import-plugin');
module.exports = {
...
plugins: [
new HippyDynamicImportPlugin(),
],
};
```
​ (1)加载本地 JS 包:
​ 与 Web 开发动态加载能力一样,直接使用 import() 语法即可
​ (2)加载远程 JS 包:
i. webpack打包脚本配置全局 publicPath(可选)
``` javascript
// webpack output 配置
output: {
...
publicPath: 'https://xxxx/hippy/hippyVueDemo/',
},
```
ii. 在业务代码引用分包的入口配置 magic comment的 webpackChunkName(必须) 和 customChunkPath(可选),如果没有配置customChunkPath,会默认使用全局 publicPath; 以 Hippy-Vue 为例:
``` javascript
// 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))
```
### **8.** **JS API 优化**
#### **8.1.** **JS Bridge -> JSI**
​接入方案详见:https://doc.openhippy.com/#/feature/feature2.0/jsi
​优化效果:
Android 代码示例:
``` javascript
// 初始化引擎开启 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 代码示例:
``` javascript
// 方式一: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;
```
``` javascript
@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 代码示例:
https://github.com/Tencent/Hippy/blob/master/examples/hippy-react-demo/src/externals/Turbo/demoTurbo.js
Hippy-Vue 代码示例:
https://github.com/Tencent/Hippy/blob/master/examples/hippy-vue-demo/src/components/demos/demoTurbo.js
#### **8.2.** **减少首屏 JS API 调用次数**
### **9.** **Hippy-Vue 优化**
​ 1. 减少 getElemCss 方法的调用
``` javascript
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框架下的数据对比:
### **10. Hippy-React 优化**
​ 1. Hippy React节点更新优化:
https://iwiki.woa.com/p/1335183393
@hippy/react 在 2.12.0 版本应用上了最新的渲染优化,针对 react 16 和 react 17,先删除原来的 react-reconciler 包依赖,再分别引入
``` javascript
"@hippy/react-reconciler": "react16"
"@hippy/react-reconciler": "react17"
```
​
### **11.** **渲染优化:**
#### 1. 减少首屏节点数、节点层级
- 可以借助客户端 IDE 来直观地查看 "DOM" 树,使用前端的 DevTools 的 View 树来检查冗余节点。
#### 2. 减少节点重复渲染的次数
- 可以在高频渲染节点的 `render` 函数中打点统计。
- 使用 `shouldComponentUpdate`、`PureComponent` 等优化重复渲染。
#### 3. 节点优化:节点合并,扁平化(触发 Hippy 引擎优化)
- 减少冗余事件挂载。
#### 4. 圆角、裁剪、描边等渲染性能较差,尽量减少使用
#### 5. 减少过度绘制
- 移除重复的背景色。
- 减少图层覆盖。
#### 6. ListView
- 尽量使用 `ListView` 代替 `ScrollView`,`ListView` 通过 View 的缓存与重用大大提升渲染性能。
- 使用 `getRowType`,将 item 按类型合理拆分,同一类型的 item 的 `RowType` 相同,可复用。
- 严格保持 `ListView` item 的 DOM 结构一致:
- **hippy-vue**:item 中的节点可以用 `v-show`,不要用 `v-if`。
- **hippy-react**:item 中的节点尽量不要用 `if` 条件来改变节点结构。
#### 7. ViewPager
- `ViewPager` 的子节点尽量不要全加载,实现懒加载。
#### 8. Image
- 图片压缩。
- 图片按需加载。
- 避免同时使用 `src` 和 `background`。
- 大图放 CDN。
- 减少 base64 图片使用。
- 图片缓存(内存缓存、磁盘缓存)。
​ 代码示例:在自定义ImageLoader中实现自己的缓存逻辑,也可使用第三方图片加载库,如 slide 等。
``` javascript
HippyEngine.EngineInitParams initParams = new HippyEngine.EngineInitParams();
// 必须:宿主(Hippy的使用者)的Context
// 若存在多个Activity加载多个业务jsbundle的情况,则这里初始化引擎时建议使用Application的Context
initParams.context = this;
...
// 必须:图片加载器
initParams.imageLoader = new MyImageLoader(this.getApplicationContext());
```
### **12.** **业务逻辑执行优化**
此外,也要检查业务代码自身的耗时,并做针对性优化。
​ 1. 防抖节流:对滚动事件、数据上报等进行防抖节流,避免频繁触发计算
​ 2. 耗时逻辑检查是否可以放在首屏后执行
### **13.** **交互优化**
除了上面的优化手段,交互优化在一些场景也是十分重要的,可以提升打开体验,让页面视觉上更流畅。
​ 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](https://flutter.cn/docs/perf/best-practices)
​ 19. React Native 性能综述
[性能综述 · React Native 中文网](https://reactnative.cn/docs/performance)
​ 20. 营地性能优化
https://km.woa.com/articles/show/599930?kmref=search&from_page=1&no=10