鸿蒙|性能优化-启动时长优化

48 阅读3分钟

概览:本文介绍了启动时长优化的四大方向,减少耗时操作、资源提前加载、操作延时触发、并发能力使用。并以Web页面为例,通过预解析、预连接、预下载、预渲染四大策略,实现启动时长优化。

什么是启动时长?

启动时长分为两类,我们把应用被拉起至应用显示页面的执行时间,称之为应用启动时长 。把页面被拉起至显示页面的执行时间,称之为页面启动时长。

启动时长优化的方向

1、耗时操作较少
减少debug日志、冗余空回调
减少Trace打点
避免耗时接口
减少数据库API调用次数,冗余的操作

2、资源提前加载
网络请求优化:同步任务(在同步任务里发起网络请求) > 微任务 > 宏任务
Web组件加载性能优化:网址 > 解析 > 连接 > 下载 > 渲染
媒体资源预下载:在前一个页面下载图片存入AppStorage,下个页面可以直接使用, @StorageLink('imageUrl')

3、操作延时触发
延迟加载:import lazy {A} from "./A"  lazy的模块解析在冷启动依旧会触发遍历,导入的变量A被使用到时,触发模块的源码执行。
动态加载:let A = await import("./A)  创建异步任务开销,执行到动态加载时,触发依赖模块的模块解析+源码执行。

4、并发能力应用
使用多线程能力:taskpool(关注耗时推荐taskpool)、worker(关注内存情况推荐使用worker,相对taskpool重量级一些)
使用异步能力:const res = await Promise.allSettled([getData1()],[getData2()],[getData3()])

普通使用web组件

从index页面跳转到ArkWebIndex页面,首先会有白屏等待情况并有一个抖动。

index 页面

@Entry
@Component
struct Index {
  message: string = '性能优化-web页面启动时常优化'
  navPathStack: NavPathStack = new NavPathStack()

  build() {
    Navigation(this.navPathStack) {
      Button(this.message).onClick(() => {
        this.navPathStack.pushPathByName('ArkWebIndexPage', null);
      })
    }
    .hideTitleBar(true)
    .mode(NavigationMode.Stack)
  }
}

ArkWebIndex页面

import webview from "@ohos.web.webview"

export const WEB_URL = 'http://developer.huawei.com/consumer/cn'

@Builder
export function ArkWebIndexBuilder(name: string, param: Object) {
  ArkWebIndex()
}

@Component
export struct ArkWebIndex {
  webviewController: webview.WebviewController = new webview.WebviewController()
  navPathStack: NavPathStack = new NavPathStack()

  build() {
    NavDestination() {
      Web({
        src: WEB_URL,
        controller: this.webviewController
      })
        .domStorageAccess(true)
        .zoomAccess(true)
        .fileAccess(true)
        .mixedMode(MixedMode.All)
        .width('100%')
        .height('100%')
    }
    .hideTitleBar(true)
    .onReady((context: NavDestinationContext) => {
      this.navPathStack = context.pathStack
      console.info('ArkWebIndex onReady')
    })
  }
}

配置:resources>base>profile>router_map.json

{
  "routerMap": [
    {
      "name": "ArkWebIndexPage",
      "pageSourceFile": "src/main/ets/pages/ArkWebIndex.ets",
      "buildFunction": "ArkWebIndexBuilder"
    }
  ]
}

配置:module.json5

"routerMap": "$profile:router_map",
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  },
]

优化方案

通过预解析、预连接、预下载、预渲染,将Web页面的网络请求和组件创建等操作提前执行,从而消除页面跳转后的白屏等待时间。

index 页面

import { webview } from '@kit.ArkWeb'
import { creatWeb } from '../utils/ArkWebUtil'
import { WEB_URL } from './ArkWebIndex'

@Entry
@Component
struct Index {
  message: string = '性能优化-web页面启动时常优化'
  navPathStack: NavPathStack = new NavPathStack()

  aboutToAppear(): void {
    // 预解析、预连接
    webview.WebviewController.initializeWebEngine() // 初始化引擎
    webview.WebviewController.prepareForPageLoad(WEB_URL, true, 1) //(预解析的web地址,是否要进行预连接,socket数量)
    // 预下载
    // 预渲染
    creatWeb(WEB_URL, this.getUIContext()) // 创建web组件
  }

  build() {
    Navigation(this.navPathStack) {
      Button(this.message).onClick(() => {
        this.navPathStack.pushPathByName('ArkWebIndexPage', null);
      })
    }
    .hideTitleBar(true)
    .mode(NavigationMode.Stack)
  }
}

ArkWebIndex页面

import webview from "@ohos.web.webview"
import { getWeb } from "../utils/ArkWebUtil"

export const WEB_URL = 'http://developer.huawei.com/consumer/cn'

@Builder
export function ArkWebIndexBuilder(name: string, param: Object) {
  ArkWebIndex()
}

@Component
export struct ArkWebIndex {
  webviewController: webview.WebviewController = new webview.WebviewController()
  navPathStack: NavPathStack = new NavPathStack()

  build() {
    NavDestination() {
      NodeContainer(getWeb(WEB_URL)) // 自定义节点,将创建好的web组件放进节点
    }
    .hideTitleBar(true)
    .onReady((context: NavDestinationContext) => {
      this.navPathStack = context.pathStack
      console.info('ArkWebIndex onReady')
    })
  }
}

ArkWebUtil

import { BuilderNode, FrameNode, NodeController } from '@ohos.arkui.node'
import { UIContext } from '@ohos.arkui.UIContext'
import { webview } from '@kit.ArkWeb'

interface Data {
  url: string
  controller: WebviewController
}

@Builder
function WebBuilder(data: Data) {
  Web({
    src: data.url,
    controller: data.controller
  })
    .domStorageAccess(true)
    .zoomAccess(true)
    .fileAccess(true)
    .mixedMode(MixedMode.All)
    .width('100%')
    .height('100%')
    .onAppear(() => {
      // 预下载
      data.controller.prefetchPage(data.url)
    })
    .onPageBegin(() => {
      // 页面后台渲染默认为非激活状态需手动激活
      data.controller.onActive()
    })
}

let wrap = wrapBuilder(WebBuilder)

class myNodeController extends NodeController {
  // 要渲染节点的内容
  rootNode: BuilderNode<Data[]> | null = null

  // 返回要渲染的节点
  makeNode(uiContext: UIContext): FrameNode | null {
    if (this.rootNode != null) {
      const parent = this.rootNode.getFrameNode()?.getParent() // 判断有没有把内容挂载到root上
      if (parent) {
        parent.clearChildren() // 如果有parent,parent清空一下子节点
      }
      let root = new FrameNode(uiContext) // 要渲染的节点
      root.appendChild(this.rootNode?.getFrameNode()) // 把rootNode添加到要渲染的节点上
      return root
    }
    return null
  }

  // 挂载web组件到要渲染的节点上
  initWeb(url: string, uiContext: UIContext) {
    this.rootNode = new BuilderNode(uiContext)
    this.rootNode.build(wrap, { url, controller: new webview.WebviewController })
  }
}

let NodeMap: Map<string, myNodeController | null> = new Map()

// 创建web组件
export const creatWeb = (url: string, uiContext: UIContext) => {
  let baseNode = new myNodeController()
  baseNode.initWeb(url, uiContext)
  NodeMap.set(url, baseNode) // 存起来,渲染web时取baseNode
}

// 返回创建好的web组件
export const getWeb = (url: string) => {
  return NodeMap.get(url)
}