1# 提升应用响应速度 2 3应用对用户的输入需要快速反馈,以提升交互体验,因此本文提供了以下方法来提升应用响应速度。 4 5- 避免主线程被非UI任务阻塞 6- 减少组件刷新的数量 7 8## 避免主线程被非UI任务阻塞 9 10在应用响应用户输入期间,应用主线程应尽可能只执行UI任务(待显示数据的准备、可见视图组件的更新等),非UI的耗时任务(长时间加载的内容等)建议通过异步任务延迟处理或者分配到其他线程处理。 11 12### 使用组件异步加载特性 13 14OpenHarmony提供的Image组件默认生效异步加载特性,当应用在页面上展示一批本地图片的时候,会先显示空白占位块,当图片在其他线程加载完毕后,再替换占位块。这样图片加载就可以不阻塞页面的显示,给用户带来良好的交互体验。因此,只在加载图片耗时比较短的情况下建议下述代码。 15 16```typescript 17@Entry 18@Component 19struct ImageExample1 { 20 build() { 21 Column() { 22 Row() { 23 Image('resources/base/media/sss001.jpg') 24 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') 25 Image('resources/base/media/sss002.jpg') 26 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') 27 Image('resources/base/media/sss003.jpg') 28 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') 29 Image('resources/base/media/sss004.jpg') 30 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%') 31 } 32 // 此处省略若干个Row容器,每个容器内都包含如上的若干Image组件 33 } 34 } 35} 36``` 37 38建议:在加载图片的耗时比较短的时候,通过异步加载的效果会大打折扣,建议配置Image的syncLoad属性。 39 40```typescript 41@Entry 42@Component 43struct ImageExample2 { 44 build() { 45 Column() { 46 Row() { 47 Image('resources/base/media/sss001.jpg') 48 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) 49 Image('resources/base/media/sss002.jpg') 50 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) 51 Image('resources/base/media/sss003.jpg') 52 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) 53 Image('resources/base/media/sss004.jpg') 54 .border({ width: 1 }).borderStyle(BorderStyle.Dashed).aspectRatio(1).width('25%').height('12.5%').syncLoad(true) 55 } 56 // 此处省略若干个Row容器,每个容器内都包含如上的若干Image组件 57 } 58 } 59} 60``` 61 62### 使用TaskPool线程池异步处理 63 64OpenHarmony提供了[TaskPool线程池](../reference/apis/js-apis-taskpool.md),相比worker线程,TaskPool提供了任务优先级设置、线程池自动管理机制,示例如下: 65 66```typescript 67import taskpool from '@ohos.taskpool'; 68 69@Concurrent 70function computeTask(arr: string[]): string[] { 71 // 模拟一个计算密集型任务 72 let count = 0; 73 while (count < 100000000) { 74 count++; 75 } 76 return arr.reverse(); 77} 78 79@Entry 80@Component 81struct AspectRatioExample3 { 82 @State children: string[] = ['1', '2', '3', '4', '5', '6']; 83 84 aboutToAppear() { 85 this.computeTaskInTaskPool(); 86 } 87 88 async computeTaskInTaskPool() { 89 const param = this.children.slice(); 90 let task = new taskpool.Task(computeTask, param); 91 await taskpool.execute(task); 92 } 93 94 build() { 95 // 组件布局 96 } 97} 98``` 99 100### 创建异步任务 101 102以下代码展示了将一个长时间执行的非UI任务通过Promise声明成异步任务,主线程可以先进行用户反馈-绘制初始页面。等主线程空闲时,再执行异步任务。等到异步任务运行完毕后,重绘相关组件刷新页面。 103 104```typescript 105@Entry 106@Component 107struct AspectRatioExample4 { 108 @State private children: string[] = ['1', '2', '3', '4', '5', '6']; 109 private count: number = 0; 110 111 aboutToAppear() { 112 this.computeTaskAsync(); // 调用异步运算函数 113 } 114 115 // 模拟一个计算密集型任务 116 computeTask() { 117 this.count = 0; 118 while (this.count < 100000000) { 119 this.count++; 120 } 121 this.children = this.children.reverse(); 122 } 123 124 computeTaskAsync() { 125 setTimeout(() => { // 这里使用setTimeout来实现异步延迟运行 126 this.computeTask(); 127 }, 1000) 128 } 129 130 build() { 131 // 组件布局 132 } 133} 134``` 135 136## 减少刷新的组件数量 137 138应用刷新页面时需要尽可能减少刷新的组件数量,如果数量过多会导致主线程执行测量、布局的耗时过长,还会在自定义组件新建和销毁过程中,多次调用aboutToAppear()、aboutToDisappear()方法,增加主线程负载。 139 140### 使用容器限制刷新范围 141 142反例:如果容器内有组件被if条件包含,if条件结果变更会触发创建和销毁该组件,如果此时影响到容器的布局,该容器内所有组件都会刷新,导致主线程UI刷新耗时过长。 143 144以下代码的Text('New Page')组件被状态变量isVisible控制,isVisible为true时创建,false时销毁。当isVisible发生变化时,Stack容器内的所有组件都会刷新: 145 146```typescript 147@Entry 148@Component 149struct StackExample5 { 150 @State isVisible : boolean = false; 151 152 build() { 153 Column() { 154 Stack({alignContent: Alignment.Top}) { 155 Text().width('100%').height('70%').backgroundColor(0xd2cab3) 156 .align(Alignment.Center).textAlign(TextAlign.Center); 157 158 // 此处省略100个相同的背景Text组件 159 160 if (this.isVisible) { 161 Text('New Page').height("100%").height("70%").backgroundColor(0xd2cab3) 162 .align(Alignment.Center).textAlign(TextAlign.Center); 163 } 164 } 165 Button("press").onClick(() => { 166 this.isVisible = !(this.isVisible); 167 }) 168 } 169 } 170} 171``` 172 173建议:对于这种受状态变量控制的组件,在if外套一层容器,减少刷新范围。 174 175```typescript 176@Entry 177@Component 178struct StackExample6 { 179 @State isVisible : boolean = false; 180 181 build() { 182 Column() { 183 Stack({alignContent: Alignment.Top}) { 184 Text().width('100%').height('70%').backgroundColor(0xd2cab3) 185 .align(Alignment.Center).textAlign(TextAlign.Center); 186 187 // 此处省略100个相同的背景Text组件 188 189 Stack() { 190 if (this.isVisible) { 191 Text('New Page').height("100%").height("70%").backgroundColor(0xd2cab3) 192 .align(Alignment.Center).textAlign(TextAlign.Center); 193 } 194 }.width('100%').height('70%') 195 } 196 Button("press").onClick(() => { 197 this.isVisible = !(this.isVisible); 198 }) 199 } 200 } 201} 202``` 203 204### 按需加载列表组件的元素 205 206反例:this.arr中的每一项元素都被初始化和加载,数组中的元素有10000个,主线程执行耗时长。 207 208```typescript 209@Entry 210@Component 211struct MyComponent7 { 212 @State arr: number[] = Array.from(Array<number>(10000), (v,k) =>k); 213 build() { 214 List() { 215 ForEach(this.arr, (item: number) => { 216 ListItem() { 217 Text(`item value: ${item}`) 218 } 219 }, (item: number) => item.toString()) 220 } 221 } 222} 223``` 224 225建议:这种情况下用LazyForEach替换ForEach,LazyForEach一般只加载可见的元素,避免一次性初始化和加载所有元素。 226 227```typescript 228class BasicDataSource implements IDataSource { 229 private listeners: DataChangeListener[] = [] 230 231 public totalCount(): number { 232 return 0 233 } 234 235 public getData(index: number): string { 236 return '' 237 } 238 239 registerDataChangeListener(listener: DataChangeListener): void { 240 if (this.listeners.indexOf(listener) < 0) { 241 console.info('add listener') 242 this.listeners.push(listener) 243 } 244 } 245 246 unregisterDataChangeListener(listener: DataChangeListener): void { 247 const pos = this.listeners.indexOf(listener); 248 if (pos >= 0) { 249 console.info('remove listener') 250 this.listeners.splice(pos, 1) 251 } 252 } 253 254 notifyDataReload(): void { 255 this.listeners.forEach(listener => { 256 listener.onDataReloaded() 257 }) 258 } 259 260 notifyDataAdd(index: number): void { 261 this.listeners.forEach(listener => { 262 listener.onDataAdd(index) 263 }) 264 } 265 266 notifyDataChange(index: number): void { 267 this.listeners.forEach(listener => { 268 listener.onDataChange(index) 269 }) 270 } 271 272 notifyDataDelete(index: number): void { 273 this.listeners.forEach(listener => { 274 listener.onDataDelete(index) 275 }) 276 } 277 278 notifyDataMove(from: number, to: number): void { 279 this.listeners.forEach(listener => { 280 listener.onDataMove(from, to) 281 }) 282 } 283} 284 285class MyDataSource extends BasicDataSource { 286 private dataArray: string[] = Array.from(Array<number>(10000), (v, k) => k.toString()); 287 288 public totalCount(): number { 289 return this.dataArray.length 290 } 291 292 public getData(index: number): string { 293 return this.dataArray[index] 294 } 295 296 public addData(index: number, data: string): void { 297 this.dataArray.splice(index, 0, data) 298 this.notifyDataAdd(index) 299 } 300 301 public pushData(data: string): void { 302 this.dataArray.push(data) 303 this.notifyDataAdd(this.dataArray.length - 1) 304 } 305} 306 307@Entry 308@Component 309struct MyComponent { 310 private data: MyDataSource = new MyDataSource() 311 312 build() { 313 List() { 314 LazyForEach(this.data, (item: string) => { 315 ListItem() { 316 Text(item).fontSize(20).margin({ left: 10 }) 317 } 318 }, (item:string) => item) 319 } 320 } 321} 322```