diff --git a/zh-cn/application-dev/performance/Readme.md b/zh-cn/application-dev/performance/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..02767ea378dfbbeb8fb4a8d73ba4e2b9117c6117 --- /dev/null +++ b/zh-cn/application-dev/performance/Readme.md @@ -0,0 +1,33 @@ +# 应用性能最佳实践 + +本文简要介绍了用于在OpenHarmony上提升应用性能的最佳实践。 + +用户期望应用能够快速启动、及时响应、无卡顿,本文将提供提升应用启动和响应速度、减少丢帧卡顿的一些方法和案例供应用开发者参考。 + +- 提升应用启动和响应速度 + + - [提升应用冷启动速度](http://gitee.com/sqsyqqy/docs/blob/master/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md) + + 应用启动时延是影响用户体验的关键要素。提升应用冷启动速度,建议在以下四个阶段进行优化: + + ​ 1、应用进程创建&初始化阶段 + + ​ 2、Application&Ability初始化阶段 + + ​ 3、Ability生命周期阶段 + + ​ 4、加载绘制首页阶段 + + - [提升应用响应速度](http://gitee.com/sqsyqqy/docs/blob/master/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-response.md) + + 应用对用户的输入需要快速反馈,以提升交互体验。建议通过避免主线程被非UI任务阻塞、减少组件刷新的数量的方法来提升应用响应速度。 + +- 减少丢帧卡顿 + + - [减少视图嵌套层次](http://gitee.com/sqsyqqy/docs/blob/master/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-view-nesting-levels.md) + + 应用将布局渲染到屏幕上的流畅度影响用户对质量的感知。建议移除多余的嵌套层次减少视图嵌套层次。 + + - [减少动画丢帧](http://gitee.com/sqsyqqy/docs/blob/master/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md) + + 应用播放动画的流畅度是影响用户体验的重要因素。建议通过使用系统提供的动效API来减少动画丢帧。 diff --git a/zh-cn/application-dev/performance/figure/application-cold-start.png b/zh-cn/application-dev/performance/figure/application-cold-start.png new file mode 100644 index 0000000000000000000000000000000000000000..782cf4691ff66549ed9bc26fb7e0520817aba8ee Binary files /dev/null and b/zh-cn/application-dev/performance/figure/application-cold-start.png differ diff --git a/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md b/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md new file mode 100644 index 0000000000000000000000000000000000000000..0f7d4682082c8962454a4d1e707ae157e624ef1b --- /dev/null +++ b/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-cold-start-speed.md @@ -0,0 +1,110 @@ +# 提升应用冷启动速度 + +应用启动时延是影响用户体验的关键要素。当应用启动时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用, 这个启动方式就叫做冷启动。 + +## 分析应用冷启动耗时 + +OpenHarmony的应用冷启动过程大致可分成以下四个阶段:应用进程创建&初始化、Application&Ability初始化、Ability生命周期、加载绘制首页,如下图: + +![输入图片说明](../figure/application-cold-start.png) + +## 1、缩短应用进程创建&初始化阶段耗时 + +该阶段主要是系统完成应用进程的创建以及初始化的过程,包含了启动页图标(startWindowIcon)的解码。 + +### 设置合适分辨率的startWindowIcon + +如果启动页图标分辨率过大,解码耗时会影响应用的启动速度,建议启动页图标分辨率不超过256像素*256像素,如下所示: + +```json + "abilities": [ + { + "name": "EntryAbility", + "srcEntrance": "./ets/entryability/EntryAbility.ts", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startWindowIcon", // 在这里修改启动页图标,建议不要超过256像素x256像素 + "startWindowBackground": "$color:start_window_background", + "visible": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ] +``` + +## 2、缩短Application&Ability初始化阶段耗时 + +该阶段主要是资源加载、虚拟机创建、Application&Ability相关对象的创建与初始化、依赖模块的加载等。 + +### 减少import的模块 + +应用代码执行前,应用程序必须找到并加载import的所有模块,应用程序加载的每个额外的第三方框架或者模块都会增加启动时间,耗时长短取决于加载的第三方框架或者模块的数量和大小。推荐开发者尽可能使用系统提供的模块,按需加载,来缩短应用程序的启动耗时。 + +## 3、缩短Ability生命周期阶段耗时 + +该阶段主要是Ability的启动生命周期,执行相应的生命周期回调。 + +### 避免在Ability生命周期回调接口进行耗时操作 + +在应用启动流程中,系统会执行Ability的生命周期回调函数。因此,不建议在这些回调函数中执行耗时过长的操作,耗时操作建议通过异步任务延迟处理或者放到其他线程执行。 + +在这些生命周期回调里,推荐开发者只做必要的操作,详情可以参考:[UIAbility组件生命周期](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/application-models/uiability-lifecycle.md) + +## 4、缩短加载绘制首页阶段耗时 + +该阶段主要是加载首页内容、测量布局、刷新组件并绘制。 + +### 自定义组件生命周期回调接口里避免耗时操作 + +自定义组件的生命周期变更会调用相应的回调函数。 + +aboutToAppear函数会在创建自定义组件实例后,页面绘制之前执行,以下代码在aboutToAppear中对耗时长的计算任务进行了异步处理,避免在该接口执行该耗时操作,不阻塞页面绘制。 + +```javascript +@Entry +@Component +struct Index { + @State private text: string = undefined; + private count: number = undefined; + + aboutToAppear() { + this.computeTaskAsync(); // 异步任务 + this.text = "hello world"; + } + + build() { + Column({space: 10}) { + Text(this.text).fontSize(50) + } + .width('100%') + .height('100%') + .padding(10) + } + + computeTask() { + this.count = 0; + while (this.count < 10000000) { + this.count++; + } + this.text = 'task complete'; + } + + // 运算任务异步处理 + private computeTaskAsync() { + new Promise((resolved, rejected) => { + setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行 + this.computeTask(); + }, 1000) + }) + } +} +``` \ No newline at end of file diff --git a/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-response.md b/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-response.md new file mode 100644 index 0000000000000000000000000000000000000000..72a4a4567689d83875cd0340a848ad793f732190 --- /dev/null +++ b/zh-cn/application-dev/performance/improve-application-startup-and-response/improve-application-response.md @@ -0,0 +1,325 @@ +# 提升应用响应速度 + +应用对用户的输入需要快速反馈,以提升交互体验,因此本文提供了以下方法来提升应用响应速度。 + +- 避免主线程被非UI任务阻塞 +- 减少组件刷新的数量 + +## 避免主线程被非UI任务阻塞 + +在应用响应用户输入期间,应用主线程应尽可能只执行UI任务(待显示数据的准备、可见视图组件的更新等),非UI的耗时任务(长时间加载的内容等)建议通过异步任务延迟处理或者分配到其他线程处理。 + +### 使用组件异步加载特性 + +OpenHarmony提供的Image组件默认生效异步加载特性,当应用在页面上展示一批本地图片的时候,会先显示空白占位块,当图片在其他线程加载完毕后,再替换占位块。这样图片加载就可以不阻塞页面的显示,给用户带来良好的交互体验。因此,只在加载图片耗时比较短的情况下建议下述代码。 + +```javascript +@Entry +@Component +struct ImageExample1 { + build() { + Column() { + Row() { + Image('resources/base/media/sss001.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + Image('resources/base/media/sss002.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + Image('resources/base/media/sss003.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + Image('resources/base/media/sss004.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') + } + // 此处省略若干个Row容器,每个容器内都包含如上的若干Image组件 + } + } +} +``` + +建议:在加载图片的耗时比较短的时候,通过异步加载的效果会大打折扣,建议配置Image的syncLoad属性。 + +```javascript +@Entry +@Component +struct ImageExample1 { + build() { + Column() { + Row() { + Image('resources/base/media/sss001.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + Image('resources/base/media/sss002.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + Image('resources/base/media/sss003.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + Image('resources/base/media/sss004.jpg') + .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) + } + // 此处省略若干个Row容器,每个容器内都包含如上的若干Image组件 + } + } +} +``` + +### 使用TaskPool线程池异步处理 + +OpenHarmony提供了[TaskPool线程池](https://gitee.com/sqsyqqy/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-taskpool.md),相比worker线程,TaskPool提供了任务优先级设置、线程池自动管理机制,示例如下: + +```javascript +import taskpool from '@ohos.taskpool'; + +@Concurrent +function computeTask(arr: string[]): string[] { + // 模拟一个计算密集型任务 + let count = 0; + while (count < 100000000) { + count++; + } + return arr.reverse(); +} + +@Entry +@Component +struct AspectRatioExample { + @State children: string[] = ['1', '2', '3', '4', '5', '6']; + + aboutToAppear() { + this.computeTaskInTaskPool(); + } + + async computeTaskInTaskPool() { + const param = this.children.slice(); + let task = new taskpool.Task(computeTask, param); + // @ts-ignore + this.children = await taskpool.execute(task); + } + + build() { + // 组件布局 + } +} +``` + +### 创建异步任务 + +以下代码展示了将一个长时间执行的非UI任务通过Promise声明成异步任务,主线程可以先进行用户反馈-绘制初始页面。等主线程空闲时,再执行异步任务。等到异步任务运行完毕后,重绘相关组件刷新页面。 + +```javascript +@Entry +@Component +struct AspectRatioExample { + @State private children: string[] = ['1', '2', '3', '4', '5', '6']; + private count: number = undefined; + + aboutToAppear() { + this.computeTaskAsync(); // 调用异步运算函数 + } + + // 模拟一个计算密集型任务 + computeTask() { + this.count = 0; + while (this.count < 100000000) { + this.count++; + } + this.children = this.children.reverse(); + } + + computeTaskAsync() { + new Promise((resolved, rejected) => { + setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行 + this.computeTask(); + }, 1000) + }) + } + + build() { + // 组件布局 + } +} +``` + +## 减少刷新的组件数量 + +应用刷新页面时需要尽可能减少刷新的组件数量,如果数量过多会导致主线程执行测量、布局的耗时过长,还会在自定义组件新建和销毁过程中,多次调用aboutToAppear()、aboutToDisappear()方法,增加主线程负载。 + +### 使用容器限制刷新范围 + +反例:如果容器内有组件被if条件包含,if条件结果变更会触发创建和销毁该组件,如果此时影响到容器的布局,该容器内所有组件都会刷新,导致主线程UI刷新耗时过长。 + +以下代码的Text('New Page')组件被状态变量isVisible控制,isVisible为true时创建,false时销毁。当isVisible发生变化时,Stack容器内的所有组件都会刷新: + +```javascript +@Entry +@Component +struct StackExample { + @State isVisible : boolean = false; + + build() { + Column() { + Stack({alignContent: Alignment.Top}) { + Text().width('100%').height('70%').backgroundColor(0xd2cab3) + .align(Alignment.Center).textAlign(TextAlign.Center); + + // 此处省略100个相同的背景Text组件 + + if (this.isVisible) { + Text('New Page').height("100%").height("70%").backgroundColor(0xd2cab3) + .align(Alignment.Center).textAlign(TextAlign.Center); + } + } + Button("press").onClick(() => { + this.isVisible = !(this.isVisible); + }) + } + } +} +``` + +建议:对于这种受状态变量控制的组件,在if外套一层容器,减少刷新范围。 + +```javascript +@Entry +@Component +struct StackExample { + @State isVisible : boolean = false; + + build() { + Column() { + Stack({alignContent: Alignment.Top}) { + Text().width('100%').height('70%').backgroundColor(0xd2cab3) + .align(Alignment.Center).textAlign(TextAlign.Center); + + // 此处省略100个相同的背景Text组件 + + Stack() { + if (this.isVisible) { + Text('New Page').height("100%").height("70%").backgroundColor(0xd2cab3) + .align(Alignment.Center).textAlign(TextAlign.Center); + } + }.width('100%').height('70%') + } + Button("press").onClick(() => { + this.isVisible = !(this.isVisible); + }) + } + } +} +``` + +### 按需加载列表组件的元素 + +反例:this.arr中的每一项元素都被初始化和加载,数组中的元素有10000个,主线程执行耗时长。 + +```javascript +@Entry +@Component +struct MyComponent { + @State arr: number[] = Array.from(Array(10000), (v,k) =>k); + build() { + List() { + ForEach(this.arr, (item: number) => { + ListItem() { + Text(`item value: ${item}`) + } + }, (item: number) => item.toString()) + } + } +} +``` + +建议:这种情况下用LazyForEach替换ForEach,LazyForEach一般只加载可见的元素,避免一次性初始化和加载所有元素。 + +```javascript +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = [] + + public totalCount(): number { + return 0 + } + + public getData(index: number): any { + return undefined + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + console.info('add listener') + this.listeners.push(listener) + } + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + console.info('remove listener') + this.listeners.splice(pos, 1) + } + } + + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded() + }) + } + + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index) + }) + } + + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index) + }) + } + + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index) + }) + } + + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to) + }) + } +} + +class MyDataSource extends BasicDataSource { + private dataArray: string[] = Array.from(Array(10000), (v, k) => k.toString()); + + public totalCount(): number { + return this.dataArray.length + } + + public getData(index: number): any { + return this.dataArray[index] + } + + public addData(index: number, data: string): void { + this.dataArray.splice(index, 0, data) + this.notifyDataAdd(index) + } + + public pushData(data: string): void { + this.dataArray.push(data) + this.notifyDataAdd(this.dataArray.length - 1) + } +} + +@Entry +@Component +struct MyComponent { + private data: MyDataSource = new MyDataSource() + + build() { + List() { + LazyForEach(this.data, (item: string) => { + ListItem() { + Text(item).fontSize(20).margin({ left: 10 }) + } + }, item => item) + } + } +} +``` \ No newline at end of file diff --git a/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md b/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md new file mode 100644 index 0000000000000000000000000000000000000000..63a328927a57d7363f5a3743dda6f828d587b744 --- /dev/null +++ b/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-animation-frame-loss.md @@ -0,0 +1,142 @@ +# 减少动画丢帧 + +在播放动画或者生成动画时,画面产生停滞而导致帧率过低的现象,称为动画丢帧。 + +播放动画时,系统需要在一个刷新周期内完成动画变化曲线的计算,完成组件布局绘制等操作。建议使用系统提供的动画接口,只需设置曲线类型、终点位置、时长等信息,就能够满足常用的动画功能,减少UI主线程的负载。 + +反例:应用使用了自定义动画,动画曲线计算过程很容易引起UI线程高负载,易导致丢帧。 + +```javascript +@Entry +@Component +struct AttrAnimationExample { + @State widthSize: number = 200 + @State heightSize: number = 100 + @State flag: boolean = true + + computeSize() { + let duration = 2000 + let period = 16 + let widthSizeEnd = undefined + let heightSizeEnd = undefined + if (this.flag) { + widthSizeEnd = 100 + heightSizeEnd = 50 + } else { + widthSizeEnd = 200 + heightSizeEnd = 100 + } + let doTimes = duration / period + let deltaHeight = (heightSizeEnd - this.heightSize) / doTimes + let deltaWeight = (widthSizeEnd - this.widthSize) / doTimes + for (let i = 1; i <= doTimes; i++) { + let t = period * (i); + setTimeout(() => { + this.heightSize = this.heightSize + deltaHeight + this.widthSize = this.widthSize + deltaWeight + }, t) + } + this.flag = !this.flag + } + + build() { + Column() { + Button('click me') + .onClick(() => { + let delay = 500 + setTimeout(() => { this.computeSize() }, delay) + }) + .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff) + }.width('100%').margin({ top: 5 }) + } +} +``` + +## 使用系统提供的属性动效API + +建议:通过系统提供的属性动效API实现上述动效功能。 + +```javascript +@Entry +@Component +struct AttrAnimationExample { + @State widthSize: number = 200 + @State heightSize: number = 100 + @State flag: boolean = true + + build() { + Column() { + Button('click me') + .onClick((event: ClickEvent) => { + if (this.flag) { + this.widthSize = 100 + this.heightSize = 50 + } else { + this.widthSize = 200 + this.heightSize = 100 + } + this.flag = !this.flag + }) + .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff) + .animation({ + duration: 2000, // 动画时长 + curve: Curve.Linear, // 动画曲线 + delay: 500, // 动画延迟 + iterations: 1, // 播放次数 + playMode: PlayMode.Normal // 动画模式 + }) // 对Button组件的宽高属性进行动画配置 + }.width('100%').margin({ top: 5 }) + } +} +``` + +更详细的API文档请参考:https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-animatorproperty.md + +## 使用系统提供的显式动效API + +建议:通过系统提供的显式动效API实现上述动效功能。 + +```javascript +@Entry +@Component +struct AnimateToExample { + @State widthSize: number = 200; + @State heightSize: number = 100; + @State flag: boolean = true; + + build() { + Column() { + Button('click me') + .onClick((event: ClickEvent) => { + if (this.flag) { + animateTo({ + duration: 2000, // 动画时长 + curve: Curve.Linear, // 动画曲线 + delay: 500, // 动画延迟 + iterations: 1, // 播放次数 + playMode: PlayMode.Normal // 动画模式 + }, () => { + this.widthSize = 100; + this.heightSize = 50; + }) + } else { + animateTo({ + duration: 2000, // 动画时长 + curve: Curve.Linear, // 动画曲线 + delay: 500, // 动画延迟 + iterations: 1, // 播放次数 + playMode: PlayMode.Normal // 动画模式 + }, () => { + this.widthSize = 200; + this.heightSize = 100; + }) + } + this.flag = !this.flag; + }) + .width(this.widthSize).height(this.heightSize).backgroundColor(0x317aff) + }.width('100%').margin({ top: 5 }) + } +} +``` + +更详细的API文档请参考:https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-explicit-animation.md \ No newline at end of file diff --git a/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-view-nesting-levels.md b/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-view-nesting-levels.md new file mode 100644 index 0000000000000000000000000000000000000000..ab4cf3697a3fdf5a9033019112b9d732ec05a401 --- /dev/null +++ b/zh-cn/application-dev/performance/reduce-frame-loss-and-frame-freezing/reduce-view-nesting-levels.md @@ -0,0 +1,64 @@ +# 减少视图嵌套层次 + +视图的嵌套层次会影响应用的性能。在屏幕刷新率为120Hz的设备上,每8.3ms刷新一帧,如果视图的嵌套层次多,可能会导致没法在8.3ms内完成一次屏幕刷新,就会造成丢帧卡顿,影响用户体验。因此推荐开发者移除多余的嵌套层次,缩短组件刷新耗时。 + +## 移除多余的嵌套层次 + +反例:使用了Grid来实现一个网格,但是在外层套了3层stack容器,导致组件刷新和渲染耗时长。 + +```javascript +@Entry +@Component +struct AspectRatioExample { + @State children: Number[] = Array.from(Array(900), (v, k) => k); + + build() { + Scroll() { + Grid() { + ForEach(this.children, (item) => { + GridItem() { + Stack() { + Stack() { + Stack() { + Text(item.toString()) + } + } + } + } + }, item => item) + } + .columnsTemplate('1fr 1fr 1fr 1fr') + .columnsGap(0) + .rowsGap(0) + .size({ width: "100%", height: "100%" }) + } + } +} +``` + +建议:通过减少冗余的Stack容器嵌套,每个GridItem的组件数比上面少了3个,缩短了组件刷新与渲染耗时。 + +```javascript +// xxx.ets +@Entry +@Component +struct AspectRatioExample { + @State children: Number[] = Array.from(Array(900), (v, k) => k); + + build() { + Scroll() { + Grid() { + ForEach(this.children, (item) => { + GridItem() { + Text(item.toString()) + } + }, item => item) + } + .columnsTemplate('1fr 1fr 1fr 1fr') + .columnsGap(0) + .rowsGap(0) + .size({ width: "100%", height: "100%" }) + } + } +} +``` \ No newline at end of file