improve-application-response.md 9.6 KB
Newer Older
S
s30044643 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 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线程池,相比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)
    }
  }
}
```