设置应用冷启动优化案例
简介 应用App的启动速度能够影响用户的首次体验,启动速度较慢的应用可能导致用户再次开启App的意图下降,或者卸载放弃该应用程序,所以,APP的启动速度可以直接影响一个APP的留存率和转化率,做出一个良好体验的应用,一定要考虑它的启动性能优化问题。 启动性能优化问题,开发者需要达成的目标就是应用冷(热
简介
应用App的启动速度能够影响用户的首次体验,启动速度较慢的应用可能导致用户再次开启App的意图下降,或者卸载放弃该应用程序,所以,APP的启动速度可以直接影响一个APP的留存率和转化率,做出一个良好体验的应用,一定要考虑它的启动性能优化问题。
启动性能优化问题,开发者需要达成的目标就是应用冷(热)启动无限接近或者超过性能指标。至于结果,就是给用户的感官体验越来越好,打开app的耗时少,并且整个转场过程不突兀。
应用启动概念
对于应用启动,首先,引入应用启动概念:
- 冷启动:首次打开app或者app彻底销毁后再次打开app。
- 热启动:应用运行中按home键再打开应用。
应用启动时间采集
OpenHarmony 应用启动时间采集目前主要使用以下2种方法:
- 预置条件
1.至少启动过一次APP,保证进入应用无任何弹窗提示;
2.PC端安装 avidemux2 工具;
3.一台支持8倍速慢动作拍摄的设备;
4.PC 端配置 hdc 工具;
- 视频拍摄:
1.使用慢动作相机打开八倍速慢动作开始记录手指点击桌面设置应用图标到应用完全打开的全部过程;
2.在PC 端使用工具 avidemux2 打开上述拍摄的视频;
3.通过单帧播放记录起点帧数和终点帧数;
4.通过时延公式 (终点帧数 - 起点帧数 )x 1000 / 240 计算出响应时延;
5.重复上述步骤5次,计算平均值,如果初出现偏差较大结果,单次结果作废;
起点帧数:手指离开屏幕第一帧
终点帧数:界面完全加载完成第一帧
- 基于 trace 计算:
1.使用 hdc 命令抓取应用启动的trace;
2.手指点击桌面设置应用图标到应用完全打开,等待trace 采集完毕;
3.使用 chrome 浏览器的tracing 插件打开采集到的trace 文件;
4.找到应用启动的 trace 采集起始点和结束点,使用插件中的标尺拉取两点之间的时间差;
5.重复上述步骤5次,计算平均值,如果初出现偏差较大结果,单次结果作废
采集起始点:StartAbility trace 节点前的最后一个 H:DispatchTouchEvent trace
采集结束点: H:RSMainThread::DoComposition trace 节点
备注:trace 采集参考文档:性能分析工具使用指导
应用冷启动流程
在OpenHarmony ,应用启动流程大致可以分为以下流程:
用户点击事件 --> 点击事件分发给大桌面 --> 大桌面应用启动动效 --> scenesession启动ability --> foundation启动进程 -->appSpawn 创建进程 --> 应用进程资源加载 -->首页绘制。
案例介绍
下文,将基于设置应用,详细介绍如何进行冷启动的性能优化。
AppSpawn 预加载
可以通过预加载一些so,加快冷启动的速度。预加载so 配置在appspawn_preload.json文件中。
文件路径base/startup/appspawn/appspawn_preload.json
{
"napi" : [
......
"app.ability.AbilityStage",
"app.ability.UIAbility",
"app.ability.UIExtensionAbility",
"hilog"
]
}
资源调度
OpenHarmony 资源调度子系统提供了资源调度的能力,可以通过一定的配置,为系统的性能、功耗均衡调度提供决策依据,使应用的性能功耗达到最佳。资源调度子系统具体可参考 resourceschedule_resource_schedule_service等相关仓库的介绍。
在开发者手机中,通过cgroup_action_config.json文件配置了分组策略,通过socperf_resource_config.xml文件配置了性能提频,具体可以参考开发者手机vendor/hys/laphone/resourceschedule 目录下的内容。在开发者手机中,通过切核配置,将foreground分组配置到到了cpu 4-7大核,将前台应用跑到了中大核,提升了应用的启动和运行速度。
{
"jobs" : [
{
"name" : "init",
"cmds" : [
......
]
},
{
"name" : "init",
"cmds" : [
......
"write /dev/cpuset/foreground/cpuset.cpus 4-7",
]
}
]
}
启动窗口优化
窗口创建的过程中,会默认加载模态窗口,如果产品不需要模态窗口,可以通过修改配置文件去掉,达到节省启动时间的效果。
配置文件路径:foundation/window/window_manager/resources/config/rk3568/window_manager_config.xml
该文件也可能位于产品化配制的文件夹下,具体看产品是如何配置的。
<Configs>
<!--decor enable is true means app main window show decoration-->
<decor enable="false">
......
</decor>
<!--startWindowTransitionAnimation enable is true means startWindow replace with alpha animation-->
<startWindowTransitionAnimation enable="false">
......
</startWindowTransitionAnimation>
</Configs>
见下图:采集开了模态窗口的trace,应用冷启动加载模态窗口时,会加载一些资源图标,这些资源图标会影响加载速度。
启动页配置
在应用创建初始化的过程中,包含了启动页图标(startWindowIcon)的解码,如果启动页图标分辨率过大,解码耗时会影响应用的启动速度,建议启动图标分辨路不要超过256像素x256像素。
启动页图标和启动页背景位于应用的module.json5 中。
"abilities": [
{
"name": "ohos.samples.distributedmusicplayer.MainAbility",
......
"startWindowIcon": "$media:icon", // 启动页icon
"startWindowBackground": "$color:white" // 启动页背景
}
]
缩短Application&Ability初始化阶段耗时
在Application&Ability初始化阶段,会包括资源加载、虚拟机创建、Application&Ability对象的创建以及初始化,依赖模块的加载等。
资源加载优化
资源加载优化主要手段就是静态图片的处理以及移除多余资源文件。
图片的处理主要有2个手段:
- 1.图片压缩,可是使用图片压缩工具无损压缩图片的大小,减少加载静态资源的耗时和解析图片的耗时;
- 2.iocn 使用 svg 图标,svg 图标比png 或者jpeg 的图片加载速度要快;
移除多余资源文件:
OpenHarmony 的静态资源都会打包在一个bundle 文件中,移除多余的静态资源,可以加快bundle文件的加载速度。
OpenHarmony 社区的设置应用,包含了许多功能,如果开发者有不需要或者不支持的功能,可以将相关的资源删除。
例如有的开发版不支持nfc功能,则可以将nfc 功能相关的页面资源删除;有的开发版不需要modem功能,可以将modem 功能相关的页面资源删除。
依赖模块加载优化
应用代码执行前,应用程序必须找到并加载import 的所有模块,应用程序加载的每个模块都会增加启动时间,耗时的长短取决于import 包的数量和模块的大小。推荐开发者尽量使用系统提供的模块,并按需加载,来缩短应用程序的启动耗时,如果必须得模块,可以使用js 的懒加载机制来加载该模块。
- 应用程序如何加载import 的模块
// 编译前
import wifi from '@ohos.wifi';
// 编译后
globalThis.requireNapi("wifi");
requireNapi 如何运行的本文不再叙述,具体代码位于 foundation/arkui/napi/native_engine/impl/ark/ark_native_engine.cpp文件中。
- 懒加载wifi蓝牙模块
在设置应用中,wifi蓝牙模块在应用初始化LoadPageSource的时候加载,整个加载过程耗时72ms。而设置应用的首页需要显示wifi蓝牙的连接状态。优化这个过程,有2个方法:
1:删除是首页的wifi蓝牙加载,不显示状态;
2:使用js 的懒加载机制来加载wifi蓝牙模块;
3:优化wifi蓝牙的加载速度;
显然,采用1方案删除wifi蓝牙加载肯定不是最佳方案。方案3去优化wifi蓝牙的加载速度,涉及到wifi蓝牙闭源库加载、wifi蓝牙子系统的加载,方案3对于人力资源紧张的时候并不能快速见效并且难度较大。我们可以采用方案2迅速解决该问题,待人力资源充足的情况下,再去使用方案2优化。
下文介绍采用方案2,延迟加载wifi蓝牙模块:
1: 定义公共接口类DynamicInterface;
2:定义wifi蓝牙接口类,实现公共接口WifiInterface;
3:实现wifi蓝牙接口类,实现具体功能,import wifi蓝牙包WifiDynamic
4:定义工具类,使用js await import 机制导入第三步实现的实例类DynamicLoader
// DynamicInterface.ts
export interface DynamicInterface{}
// WifiInterface.ts
import { DynamicInterface } from "/DynamicInterface"
export interface WifiInterface extends DynamicInterface {
isWiFiActive(): boolean registerWiFiActiveObserver(callback: (active: boolean) => void)
}
// WifiDynamic.ts
export class WifiDynamic implements WifiInterface {
private TAG = 'WifiDynamic'
isWiFiActive(): boolean {
return wifi.isWifiActive()
}
registerWiFiActiveObserver(callback: (active: boolean) => void) {
wifi.on('wifiStateChange', (code) => {
LogUtil.debug(`${this.TAG} wifiStateChange-> code:${code}`);
if (code == 1) {
callback(true)
} else {
callback(false)
}
})
}
}
// DynamicLoader.ts
import { DynamicInterface } from "./DynamicInterface"
import { WifiInterface } from "./WifiInterface"
const dynamicMap: Map<string, DynamicInterface> = new Map<string, DynamicInterface>()
const WIFI_INTERFACE = "WIFI_Interface"
/**
* 应用 ts 的懒加载机制,
* 页面在 import 导包 wifi和蓝牙时,不会去加载WiFi和蓝牙的napi 模块,
* 在实际调用方法时,才会去加载 WiFi和蓝牙的napi模块。
*
*/
async function loaderWifi(): Promise<WifiInterface> {
if (!dynamicMap.has(WIFI_INTERFACE)) {
const {WifiDynamic} = await import("./WifiDynamic") // 就是懒加载机制
let wifiDynamic = new WifiDynamic()
dynamicMap.set(WIFI_INTERFACE, wifiDynamic)
}
return Promise.resolve(dynamicMap.get(WIFI_INTERFACE) as WifiInterface)
}
export {
loaderWifi
}
// 应用端使用
import { loaderWifi } from "./DynamicLoader";
aboutToAppear() {
......
// 延迟加载wifi和蓝牙
setTimeout(() => {
this.initWifi()
this.initBluetooth()
}, 300)
}
initWifi() {
// 懒1加载wifi
loaderWifi().then((wifi) => {
this.wifiStatus = wifi.isWiFiActive()
wifi.registerWiFiActiveObserver((active) => {
LogUtil.debug('CCCC settings SettingList wifi active=> ' + active)
AppStorage.SetOrCreate('wifiStatus', active)
})
}).catch((error: BusinessError) => {
LogUtil.error('CCCC settings SettingList loaderWifi error ' + JSON.stringify(error))
})
}
避免Ability 生命周期执行耗时操作
在应用的启动过程中,系统会执行Ability 的生命回调方法,因此,不建议在这些回调方法中执行耗时的操作,这些耗时的操作建议通过异步任务或者放到其他work线程去执行。例如上文中提到的wifi蓝牙的加载,放到了setTimeout宏任务中,不会影响生命周期函数产生额外的耗时。
UI 的优化
UI 性能优化有以下几个重要的措施:
- 1.选择合适的控件的控件
- 2.UI 布局的优化
- 3.减少 UI 的层级
在讲上面几个优化手段之前,首先引入一个概念:过度绘制。过度绘制(Overdraw)是指应用在渲染一帧的时间内对屏幕某个像素进行多次绘制。应用应该尽可能避免过度绘制。在 OpenHarmony 系统中,设置中的开发者选项提供了过度绘制检测工具,此工具能够展示页面上哪些区域出现了不必要的过度绘制,可以直观查看应用当前页面是否存在过度绘制的现象,并且可以直观对比优化前后的显示效果。
按照步骤开启:设置>开发者选项>调试 GPU 过度绘制
不同的颜色代表不同程度的过度绘制:无色到红色,颜色越深,表示过度绘制的层度越大。因此过度绘制的优化目标是使得显示区域的过度绘制色块大部分为无色或者为蓝色,避免不大面积出现红色。
LazyForEach
对列表界面,可以选择使用LazyForEach控件布局。 LazyForEach 控件具有数据懒加载机制,LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当 LazyForEach 在滚动容器中使用了,框架会根据滚动容器可视区域按需创建组件,当组件划出可视区域外时,框架会进行组件销毁回收以降低内存占用。
在OpenHarmony-4.1-Release 发布后,设置应用的二级列表界面都替换成了LazyForEach,替换后,二级页面的打开速度,列表的滑动帧率,都有显著的提升。
布局优化
查看设置应用 UI 代码,有 List 嵌套 List 以及多个Column 相互嵌套的情况,因而可以通过优化布局解决 list 和 Column 相互嵌套。
优化前示例:
List() {
......
ListItem() {
List() {
ListItem(){}
......
}
......
}
}
UI 扁平化处理后:
List(){
ListItem(){}
ListItem(){}
ListItem(){}
}
对比下面图片,在层级优化前,UI 测量加RSRenderThread线程最差接近400ms,优化后只有250ms ,优化150ms。
总结
在使用了上述优化手段后,开发者手机设置应用的冷启动性能有明显的提升,具体数据参考下表。
版本 | B307 | B316 | 优化值 |
---|---|---|---|
数据(ms) | 989.4 | 629.4 | 360 |
更多推荐
所有评论(0)