目录

点击web页面分享按钮生成页面截图响应慢

问题现象

点击应用页面的分享按钮,等待一段时间之后才看见生成页面截图,响应慢。

背景知识

问题定位

以点击某Web应用页面分享按钮生成页面截图响应慢为例,具体定位过程如下:

  1. 使用ArkUI Inspector抓取显示页面截图的组件,发现该组件为Web,可知显示页面截图涉及到Web组件渲染输出。
  2. 使用DevEco Studio的ArkWeb分析抓取该过程的Trace信息,以应用收到手指离开屏幕事件的Trace关键字作为问题分析的起点。由于非连续不稳定的vsync信号不一定导致界面上的渲染行为,取问题分析终点为渲染服务render_service连续稳定渲染的第一帧vsync信号。可得到如下分析结果,从图中可知响应时间为2.2s左右。

  3. 由于Web页面的渲染绘制是由应用的render主线程触发,因此查看连续稳定渲染之前应用render主线的Trace信息,从图中可看到js编译与执行的Trace关键字,而在此之前render线程长时间处于sleeping状态,推测该截图信息不是由Web端生成,而是在ArkTS端。

  4. 由于Web网页显示内容会涉及到资源请求,在render线程的js编译与执行Trace关键字附近框选Trace信息,通过requestResource过滤,可看到应用有从沙箱目录中请求图片资源,查看设备目录下该图片资源,发现大小为6.9M。

    点击render线程最左侧Running状态,通过WakeUp From Tid不断找唤醒线程,发现最终唤醒线程为应用主线程,可知render线程进行js编译与执行是由主线程触发的。

  5. 找到上图红框中主线程最左侧的runnable状态,通过WakeUp From Tid发现主线程是由OS_TaskWorker线程唤醒。

    点击上图中跳转箭头找到OS_TaskWorker线程,该线程有执行PackToFile完成后的回调函数,PackToFile表示将图片数据编码进文件。

    通过上图中H:Napi complete, name:PackToFile处左侧runnable状态找到执行PackToFile的线程,可看到PackToFile耗时1.28s左右,图片分辨率为3375x7825。

    根据runnable状态查找执行PackToFile的线程的唤醒线程,发现唤醒线程为OS_TaskWorker线程,同时线程有执行screenshot截图,耗时为673ms左右。

    通过Running状态发现OS_TaskWorker线程的唤醒线程为应用主线程,可知应用收到点击事件之后,会通过taskpool执行生成截图的任务,首先截图,然后将截图数据编码到文件,保存到沙箱目录,最后通知到Web侧执行js代码,请求沙箱中的图片资源并将图片显示在Web页面中,该过程耗时主要集中在截图和数据编码到文件部分。

  6. 展开Callstack泳道,找到截图部分的调用栈,发现耗时较多为CoreCanvas::DrawImageRect,此处表示应用在Canvas画布上绘制图片。

    而图片编码处H:DoEncode部分,通过调用栈发现耗时主要集中在将图片编码进png格式文件。

    截图、图片编码进png格式文件耗时较多是因为绘制、编码的图片分辨率较大,达到了3375x7825。

分析结论

应用在生成页面截图时,设置的图片分辨率较大,截图、数据编码到文件耗时较多,导致响应慢的问题。

修改建议

  1. 减小生成的图片分辨率大小。
  2. 增加加载动效,如下示例代码。
    import { taskpool } from '@kit.ArkTS';
    import { BusinessError } from '@ohos.base';
    
    @Concurrent
    async function generalScreenShot(): Promise<PixelMap | undefined> {
      let pixelMap : PixelMap | undefined = undefined;
      // 耗时操作模拟生成截图
      for (let i = 0; i < 100000; i++) {
        console.info(`number:${i}`);
      }
      return pixelMap;
    }
    
    @Entry
    @Component
    struct DemoPage {
      @State isLoading: boolean = false;
    
      build() {
        RelativeContainer() {
          Button('分享')
            .id("shareButton")
            .onClick(() => {
              this.isLoading = true;
              taskpool.execute(generalScreenShot).then(() => {
                this.isLoading = false;
                // 显示分享界面
              }).catch((error: BusinessError) => {
                console.error(`Failed to execute task: errCode is ${error.code}, errMessage is ${error.message}`);
              });
            })
            .alignRules({
              center: { anchor: '__container__', align: VerticalAlign.Center },
              middle: { anchor: '__container__', align: HorizontalAlign.Center }
            })
          LoadingProgress()
            .color(Color.Blue)
            .width(50)
            .height(50)
            .visibility(this.isLoading ? Visibility.Visible : Visibility.None)
            .alignRules({
              top: { anchor: 'shareButton', align: VerticalAlign.Bottom },
              middle: { anchor: '__container__', align: HorizontalAlign.Center }
            })
        }
        .height('100%')
        .width('100%')
      }
    }