1# 性能提升的其他方法 2 3开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。 4 5## 设置List组件的宽高 6 7在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。 8 9> **说明:** 10> 11> Scroll嵌套List时: 12> 13> - List没有设置宽高,会布局List的所有子组件。 14> 15> - List设置宽高,会布局List显示区域内的子组件。 16> 17> - List使用[ForEach](../quick-start/arkts-rendering-control-foreach.md)加载子组件时,无论是否设置List的宽高,都会加载所有子组件。 18> 19> - List使用[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md)加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。 20 21```ts 22class BasicDataSource implements IDataSource { 23 private listeners: DataChangeListener[] = []; 24 private originDataArray: string[] = []; 25 26 public totalCount(): number { 27 return 0; 28 } 29 30 public getData(index: number): string { 31 return this.originDataArray[index]; 32 } 33 34 registerDataChangeListener(listener: DataChangeListener): void { 35 if (this.listeners.indexOf(listener) < 0) { 36 console.info('add listener'); 37 this.listeners.push(listener); 38 } 39 } 40 41 unregisterDataChangeListener(listener: DataChangeListener): void { 42 const pos = this.listeners.indexOf(listener); 43 if (pos >= 0) { 44 console.info('remove listener'); 45 this.listeners.splice(pos, 1); 46 } 47 } 48 49 notifyDataReload(): void { 50 this.listeners.forEach(listener => { 51 listener.onDataReloaded(); 52 }) 53 } 54 55 notifyDataAdd(index: number): void { 56 this.listeners.forEach(listener => { 57 listener.onDataAdd(index); 58 }) 59 } 60 61 notifyDataChange(index: number): void { 62 this.listeners.forEach(listener => { 63 listener.onDataChange(index); 64 }) 65 } 66 67 notifyDataDelete(index: number): void { 68 this.listeners.forEach(listener => { 69 listener.onDataDelete(index); 70 }) 71 } 72 73 notifyDataMove(from: number, to: number): void { 74 this.listeners.forEach(listener => { 75 listener.onDataMove(from, to); 76 }) 77 } 78} 79 80class MyDataSource extends BasicDataSource { 81 private dataArray: Array<string> = new Array(100).fill('test'); 82 83 public totalCount(): number { 84 return this.dataArray.length; 85 } 86 87 public getData(index: number): string { 88 return this.dataArray[index]; 89 } 90 91 public addData(index: number, data: string): void { 92 this.dataArray.splice(index, 0, data); 93 this.notifyDataAdd(index); 94 } 95 96 public pushData(data: string): void { 97 this.dataArray.push(data); 98 this.notifyDataAdd(this.dataArray.length - 1); 99 } 100} 101 102@Entry 103@Component 104struct MyComponent { 105 private data: MyDataSource = new MyDataSource(); 106 107 build() { 108 Scroll() { 109 List() { 110 LazyForEach(this.data, (item: string, index: number ) => { 111 ListItem() { 112 Row() { 113 Text('item value: ' + item + (index + 1)).fontSize(20).margin(10) 114 } 115 } 116 }) 117 } 118 } 119 } 120} 121``` 122 123因此,此场景下建议设置List子组件的宽高。 124 125```ts 126class BasicDataSource implements IDataSource { 127 private listeners: DataChangeListener[] = []; 128 private originDataArray: string[] = []; 129 130 public totalCount(): number { 131 return 0; 132 } 133 134 public getData(index: number): string { 135 return this.originDataArray[index]; 136 } 137 138 registerDataChangeListener(listener: DataChangeListener): void { 139 if (this.listeners.indexOf(listener) < 0) { 140 console.info('add listener'); 141 this.listeners.push(listener); 142 } 143 } 144 145 unregisterDataChangeListener(listener: DataChangeListener): void { 146 const pos = this.listeners.indexOf(listener); 147 if (pos >= 0) { 148 console.info('remove listener') 149 this.listeners.splice(pos, 1); 150 } 151 } 152 153 notifyDataReload(): void { 154 this.listeners.forEach(listener => { 155 listener.onDataReloaded(); 156 }) 157 } 158 159 notifyDataAdd(index: number): void { 160 this.listeners.forEach(listener => { 161 listener.onDataAdd(index); 162 }) 163 } 164 165 notifyDataChange(index: number): void { 166 this.listeners.forEach(listener => { 167 listener.onDataChange(index); 168 }) 169 } 170 171 notifyDataDelete(index: number): void { 172 this.listeners.forEach(listener => { 173 listener.onDataDelete(index); 174 }) 175 } 176 177 notifyDataMove(from: number, to: number): void { 178 this.listeners.forEach(listener => { 179 listener.onDataMove(from, to); 180 }) 181 } 182} 183 184class MyDataSource extends BasicDataSource { 185 private dataArray: Array<string> = new Array(100).fill('test') 186 187 public totalCount(): number { 188 return this.dataArray.length; 189 } 190 191 public getData(index: number): string { 192 return this.dataArray[index]; 193 } 194 195 public addData(index: number, data: string): void { 196 this.dataArray.splice(index, 0, data); 197 this.notifyDataAdd(index); 198 } 199 200 public pushData(data: string): void { 201 this.dataArray.push(data); 202 this.notifyDataAdd(this.dataArray.length - 1); 203 } 204} 205 206@Entry 207@Component 208struct MyComponent { 209 private data: MyDataSource = new MyDataSource(); 210 211 build() { 212 Scroll() { 213 List() { 214 LazyForEach(this.data, (item: string, index: number) => { 215 ListItem() { 216 Text('item value: ' + item + (index + 1)).fontSize(20).margin(10) 217 }.width('100%') 218 }) 219 }.width('100%').height(500) 220 }.backgroundColor(Color.Pink) 221 } 222} 223``` 224 225 226 227使用SmartPerf Host工具分别抓取List不设置宽高时和设置宽高时的trace数据。 228 229**List不设置宽高:** 230 231 232 233**List设置宽高:** 234 235 236 237从trace图可以看出,List不设置宽高时100个子组件全部参与布局,布局时间46.62ms。而给List设置宽高后只有给定高度内的12个子组件参与布局,布局时间减少到8.51ms,大幅提升了首次加载时的性能。 238 239## 使用Column/Row替代Flex 240 241由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。 242 243```ts 244@Entry 245@Component 246struct FlexBuild { 247 private data: string[] = new Array(20).fill(''); 248 build() { 249 Flex({ direction: FlexDirection.Column }) { 250 Flex({ direction: FlexDirection.Column }) { 251 Flex({ direction: FlexDirection.Column }) { 252 Flex({ direction: FlexDirection.Column }) { 253 Flex({ direction: FlexDirection.Column }) { 254 ForEach(this.data, (item: string, index: number) => { 255 Text(`Item ${index}`) 256 .width('100%') 257 .textAlign(TextAlign.Center) 258 }) 259 } 260 } 261 } 262 } 263 } 264 } 265} 266``` 267 268上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。 269 270```ts 271@Entry 272@Component 273struct ColumnAndRowBuild { 274 private data: string[] = new Array(20).fill(''); 275 build() { 276 Row() { 277 Row() { 278 Row() { 279 Row() { 280 Column() { 281 ForEach(this.data, (item: string, index: number) => { 282 Text(`Item ${index}`) 283 .width('100%') 284 .textAlign(TextAlign.Center) 285 }) 286 } 287 } 288 } 289 } 290 } 291 } 292} 293``` 294 295 296 297使用SmartPerf Host抓取上述两种不同布局方式示例程序的trace数据,对比其性能消耗,如下表所示。 298 299|对比指标|Flex布局|Column/Row| 300|--------|--------|--------| 301|Build耗时(ms)|4.27|2.51| 302|Measure耗时(ms)|2.98|1.04| 303|Layout耗时(ms)|0.34|0.24| 304 305可以看出布局深度和节点数相同的情况下,Flex的性能明显低于Column和Row容器,此时使用Column/Row替换Flex可以显著减少应用布局的性能消耗。 306 307## 减少应用滑动白块 308 309应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。 310如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。 311如下是使用cachedCount参数的例子: 312 313```ts 314@Entry 315@Component 316struct MyComponent { 317 private source: MyDataSource = new MyDataSource(); 318 319 build() { 320 List() { 321 LazyForEach(this.source, (item:string) => { 322 ListItem() { 323 Text("Hello" + item) 324 .fontSize(50) 325 .onAppear(() => { 326 console.log("appear:" + item) 327 }) 328 } 329 }) 330 }.cachedCount(3) // 扩大数值appear日志范围会变大 331 } 332} 333 334class MyDataSource implements IDataSource { 335 data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 336 337 public totalCount(): number { 338 return this.data.length 339 } 340 341 public getData(index: number): number { 342 return this.data[index] 343 } 344 345 registerDataChangeListener(listener: DataChangeListener): void { 346 } 347 348 unregisterDataChangeListener(listener: DataChangeListener): void { 349 } 350} 351``` 352 353 354**使用说明:** 355cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。 356 357更多关于cachedCount的使用指导,请参考文档[列表场景性能提升实践](list-perf-improvment.md#缓存列表项)。