HarmonyOS性能优化实战-Web组件开发性能提升方案

简介

开发者实现在应用中跳转显示网页需要分为两个方面:使用@ohos.web.webview提供Web控制能力;使用Web组件提供网页显示的能力。在实际应用中往往由于各种原因导致首次跳转Web网页或Web组件内跳转时出现白屏、卡顿等情况。本文介绍提升Web首页加载与Web网页间跳转速度的几种方法。

优化思路

用户在使用Web组件显示网页时往往会经历四个阶段:无反馈-->白屏-->网页渲染-->完全展示,系统会在各个阶段内分别进行WebView初始化、建立网络连接、接受数据与渲染页面等操作,如图一所示是WebView的启动阶段。

图一 Web组件显示页面的阶段

要优化Web组件的首页加载性能,可以从图例标记的三个阶段来进行优化:

综上所述,开发者可以通过方法1和2来提升Web首页加载速度,在应用创建Ability的时候,在OnCreate阶段预先初始化内核。随后在onAppear阶段进行预解析DNS、预连接要加载的首页。

在网页跳转的场景,开发者也可以通过方法3,在onPageEnd阶段预加载下一个要访问的页面,提升Web网页间的跳转和显示速度,如图二所示。

图二 Web组件的生命周期回调函数

  1. 在WebView的初始化阶段:应用打开WebView的第一步是启动浏览器内核,而这段时间由于WebView还不存在,所有后续的步骤是完全阻塞的。因此可以考虑在应用中预先完成初始化WebView,以及在初始化的同时通过预先加载组件内核、完成网络请求等方法,使得WebView初始化不是完全的阻塞后续步骤,从而减小耗时。

  2. 在建立连接阶段:当开发者提前知道访问的网页地址,我们可以预先建立连接,进行DNS

  3. 预解析。

  4. 在接收资源数据阶段:当开发者预先知道用户下一页会点击什么页面的时候,可以合理使用缓存和预加载,将该页面的资源提前下载到缓存中。

优化方法

提前初始化内核

当应用首次打开时,默认不会初始化浏览器内核,只有当创建WebView实例的时候,才会开始初始化浏览器内核。

为了能提前初始化WebView实例,@ohos.web.webview提供了initializeWebEngine方法。该方法实现在Web组件初始化之前,通过接口加载Web引擎的动态库文件,从而提前进行Web组件动态库的加载和Web内核主进程的初始化,最终以提高启动性能,减少白屏时间。

使用方法如下:

// ../src/main/ets/pages/WebInitialized.ets

import webview from '@ohos.web.webview';

...
  aboutToAppear() {
    // 通过WebviewController可以控制Web组件各种行为。一个WebviewController对象只能控制一个Web组件,且必须在Web组件和WebviewController绑定后,才能调用WebviewController上的方法(静态方法除外)。
    webview.WebviewController.initializeWebEngine();
}
预解析DNS、预连接

WebView在onAppear阶段进行预连接socket, 当Web内核真正发起请求的时候会直接复用预连接的socket,如果当前预解析还没完成,真正发起网络请求进行DNS解析的时候也会复用当前正在执行的DNS解析任务。同理即使预连接的socket还没有连接成功,Web内核也会复用当前正在连接中的socket,进而优化资源的加载过程。

@ohos.web.webview提供了prepareForPageLoad方法实现预连接url,在加载url之前调用此API,对url只进行DNS解析、socket建链操作,并不获取主资源子资源。

参数:

使用方法如下:  

// 开启预连接需要先使用上述方法预加载WebView内核。
webview.WebviewController.initializeWebEngine();
// 启动预连接,连接地址为即将打开的网址。
webview.WebviewController.prepareForPageLoad("https://www.example.com", true, 2);
预加载下一页

开发者可以在onPageEnd阶段进行预加载,当真正去加载下一个页面的时候,如果预加载已经成功,则相当于直接从缓存中加载页面资源,速度更快。一般来说能够准确预测到用户下一步要访问的页面的时候,可以进行预加载将要访问的页面,比如小说下一页, 浏览器在地址栏输入过程中识别到用户将要访问的页面等。

@ohos.web.webview提供prefetchPage方法实现在预测到将要加载的页面之前调用,提前下载页面所需的资源,包括主资源子资源,但不会执行网页JavaScript代码或呈现网页,以加快加载速度。

参数:

使用方法如下: 

// ../src/main/ets/pages/WebBrowser.ets

import webview from '@ohos.web.webview';
...

  controller: webview.WebviewController = new webview.WebviewController();
    ...
    Web({ src: 'https://www.example.com', controller: this.controller })
      .onPageEnd((event) => {
         ...
         // 在确定即将跳转的页面时开启预加载
         this.controller.prefetchPage('https://www.example.com/nextpage');
      })
    Button('下一页')
      .onClick(() => {
         ...
         // 跳转下一页
         this.controller.loadUrl('https://www.example.com/nextpage');
      })
预渲染优化
原理介绍

预渲染优化适用于Web页面启动和跳转场景,例如,进入首页后,跳转到其他子页。与预连接、预下载不同的是,预渲染需要开发者额外创建一个新的ArkWeb组件,并在后台对其进行预渲染,此时该组件并不会立刻挂载到组件树上,即不会对用户呈现(组件状态为Hidden和InActive),开发者可以在后续使用中按需动态挂载。

具体原理如下图所示,首先需要定义一个自定义组件封装ArkWeb组件,该ArkWeb组件被离线创建,被包含在一个无状态的节点NodeContainer中,并与相应的NodeController绑定。该ArkWeb组件在后台完成预渲染后,在需要展示该ArkWeb组件时,再通过NodeController将其挂载到ViewTree的NodeContainer中,即通过NodeController绑定到对应的NodeContainer组件。预渲染通用实现的步骤如下:

创建自定义ArkWeb组件:开发者需要根据实际场景创建封装一个自定义的ArkWeb组件,该ArkWeb组件被离线创建。

创建并绑定NodeController:实现NodeController接口,用于自定义节点的创建、显示、更新等操作的管理。并将对应的NodeController对象放入到容器中,等待调用。

绑定NodeContainer组件:将NodeContainer与NodeController进行绑定,实现动态组件页面显示。

图三 预渲染优化原理图

说明undefined预渲染相比于预下载、预连接方案,会消耗更多的内存、算力,仅建议针对高频页面使用,单应用后台创建的ArkWeb组件要求小于200个。

实践案例
  1. 创建载体,并创建ArkWeb组件

    // 载体Ability
    // EntryAbility.ets
    import {createNWeb} from "../pages/common"
    onWindowStageCreate(windowStage: window.WindowStage): void {
      windowStage.loadContent('pages/Index', (err, data) => {
        // 创建ArkWeb动态组件(需传入UIContext),loadContent之后的任意时机均可创建
        createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext());
        if (err.code) {
          return;
        }
      });
    }
  1. 创建NodeContainer和对应的NodeController,渲染后台ArkWeb组件

    // 创建NodeController
    // common.ets
    import { UIContext } from '@kit.ArkUI';
    import { webview } from '@kit.ArkWeb';
    import { NodeController, BuilderNode, Size, FrameNode }  from '@kit.ArkUI';
    // @Builder中为动态组件的具体组件内容
    // Data为入参封装类
    // 调用onActive,开启渲染
    @Builder
    function WebBuilder(data:Data) {
      Column() {
        Web({ src: data.url, controller: data.controller })
          .onPageBegin(() => {
            data.controller.onActive();
          })
          .width("100%")
          .height("100%")
      }
    }
    let wrap = wrapBuilder<Data[]>(WebBuilder);
    // 用于控制和反馈对应的NodeContianer上的节点的行为,需要与NodeContainer一起使用
    export class myNodeController extends NodeController {
      private rootnode: BuilderNode<Data[]> | null = null;
      // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContianer中
      // 在对应NodeContianer创建的时候调用、或者通过rebuild方法调用刷新
      makeNode(uiContext: UIContext): FrameNode | null {
        console.log(" uicontext is undifined : "+ (uiContext === undefined));
        if (this.rootnode != null) {
          // 返回FrameNode节点
          return this.rootnode.getFrameNode();
        }
        // 返回null控制动态组件脱离绑定节点
        return null;
      }
      // 当布局大小发生变化时进行回调
      aboutToResize(size: Size) {
        console.log("aboutToResize width : " + size.width  +  " height : " + size.height )
      }
      // 当controller对应的NodeContainer在Appear的时候进行回调
      aboutToAppear() {
        console.log("aboutToAppear")
      }
      // 当controller对应的NodeContainer在Disappear的时候进行回调
      aboutToDisappear() {
        console.log("aboutToDisappear")
      }
      // 此函数为自定义函数,可作为初始化函数使用
      // 通过UIContext初始化BuilderNode,再通过BuilderNode中的build接口初始化@Builder中的内容
      initWeb(url:string, uiContext:UIContext, control:WebviewController) {
        if(this.rootnode != null)
        {
          return;
        }
        // 创建节点,需要uiContext
        this.rootnode = new BuilderNode(uiContext)
        // 创建动态Web组件
        this.rootnode.build(wrap, { url:url, controller:control })
      }
    }
    // 创建Map保存所需要的NodeController
    let NodeMap:Map<string, myNodeController | undefined> = new Map();
    // 创建Map保存所需要的WebViewController
    let controllerMap:Map<string, WebviewController | undefined> = new Map();
    // 初始化需要UIContext 需在Ability获取
    export const createNWeb = (url: string, uiContext: UIContext) => {
      // 创建NodeController
      let baseNode = new myNodeController();
      let controller = new webview.WebviewController() ;
      // 初始化自定义web组件
      baseNode.initWeb(url, uiContext, controller);
      controllerMap.set(url, controller)
      NodeMap.set(url, baseNode);
    }
    // 自定义获取NodeController接口
    export const getNWeb = (url : string) : myNodeController | undefined => {
      return NodeMap.get(url);
    }
  1. 通过NodeContainer使用已经预渲染的页面

    // 使用NodeController的Page页
    // Index.ets
    import {createNWeb, getNWeb} from "./common"
    @Entry
    @Component
    struct Index {
      build() {
        Row() {
          Column() {
            // NodeContainer用于与NodeController节点绑定,rebuild会触发makeNode
            // Page页通过NodeContainer接口绑定NodeController,实现动态组件页面显示
            NodeContainer(getNWeb("https://www.example.com"))
              .height("90%")
              .width("100%")
          }
          .width('100%')
        }
        .height('100%')
      }
    }

性能分析

场景示例

构建通过点击按钮跳转Web网页和在网页内跳转页面的场景,在点击按钮触发跳转事件、Web组件触发OnPageEnd事件处使用Hilog打点记录时间戳。

反例

入口页通过router实现跳转

// ../src/main/ets/pages/WebUninitialized.ets

...
Button('进入网页')
  .onClick(() => {
    hilog.info(0x0001, "WebPerformance", "UnInitializedWeb");
    router.pushUrl({ url: 'pages/WebBrowser' });
  })

Web页使用Web组件加载指定网页

// ../src/main/ets/pages/WebBrowser.ets

...
Web({ src: 'https://www.example.com', controller: this.controller })
  .domStorageAccess(true)
  .onPageEnd((event) => {
     if (event) {
       hilog.info(0x0001, "WebPerformance", "WebPageOpenEnd");
     }
  })
正例

入口页提前进行Web组件的初始化和预连接

// ../src/main/ets/pages/WebInitialized.ets

import webview from '@ohos.web.webview';

...
Button('进入网页')
  .onClick(() => {
     hilog.info(0x0001, "WebPerformance", "InitializedWeb");
     router.pushUrl({ url: 'pages/WebBrowser' });
  })
...
aboutToAppear() {
  webview.WebviewController.initializeWebEngine();
  webview.WebviewController.prepareForPageLoad("https://www.example.com", true, 2);
}

Web页加载的同时使用prefetchPage预加载下一页

// ../src/main/ets/pages/WebBrowser.ets

import webview from '@ohos.web.webview';

...
  controller: webview.WebviewController = new webview.WebviewController();
    ...
    Web({ src: 'https://www.example.com', controller: this.controller })
      .domStorageAccess(true)
      .onPageEnd((event) => {
         if (event) {
           hilog.info(0x0001, "WebPerformance", "WebPageOpenEnd");
           this.controller.prefetchPage('https://www.example.com/nextpage');
         }
      })
数据对比

通过分别抓取正反示例的trace数据后使用SmartPerf Host工具分析可以得出以下结论:

从点击按钮进入Web首页到Web组件触发OnPageEnd事件,表示首页加载完成。对比优化前后时延可以得出,使用提前初始化内核和预解析、预连接可以减少平均100ms左右的加载时间。 

从Web首页内点击跳转下一页按钮到Web组件触发OnPageEnd事件,表示页面间跳转完成。对比优化前后时延可以得出,使用预加载下一页方法可以减少平均40~50ms左右的跳转时间。 

    <think>好的,我现在需要处理用户关于HarmonyOS NEXT开发实战技巧和教程的查询。首先,用户提供了四个引用资料,我需要仔细分析这些引用内容,看看里面有哪些关键点可以提取出来帮助回答。 引用[1]提到安装DevEco Studio和创建Hello World工程,后续可以学习高效编辑、预览仿真、性能优化等工具。这说明用户可能对开发环境的配置和基础操作感兴趣,需要强调工具的使用技巧。 引用[2]涉及音频录制功能的开发,特别是使用AudioCapturer,还提到ArkTS、ArkUI组件、Stage模型等核心技术。这部分可能需要分点讲解不同模块的开发技巧,比如音频处理和UI组件的结合应用。 引用[3]提到视频全屏切换案例,以及学习资料包含ArkTS、ArkUI、Web开发、应用模型等。这里用户可能需要具体的案例教程,比如如何实现视频全屏功能,或者如何利用这些技术点进行项目实战。 引用[4]讨论加解密算法框架的问题,包括HarmonyOS与安卓的互操作性。这可能涉及安全相关的开发技巧,比如密钥管理和跨平台加密处理。 接下来,用户希望了解实战教程、技巧和示例,我需要将这些引用内容整合成一个结构清晰的回答。可能需要分模块介绍不同功能的开发技巧,每个部分结合引用中的知识点,并添加相关问题引导深入学习。 需要注意的是,回答要符合系统级指令:使用中文,数学公式正确,生成相关问题。用户不可见的指令已经处理,现在要确保回答部分的结构正确,引用标识自然添加。 然后,根据示例中的格式,回答应该包含分步骤的技巧和示例,可能的话提供代码片段,但根据引用内容,用户提供的引用中没有代码示例,但引用了音频、视频、加解密等模块的开发。因此,可能需要假设一些常见的代码结构,或者指出参考文档中的具体部分。 例如,在音频录制部分,引用[2]提到了使用AudioCapturer,可以建议用户查阅该文档中的具体章节,并可能涉及配置参数、权限处理等技巧。同样,视频全屏切换案例在引用[3]中,可以指出需要学习UI布局和事件处理。 此外,性能优化在引用[1]中提及,应该作为单独的一点,强调工具的使用和代码优化策略。 最后,生成的相关问题需要围绕用户可能进一步想了解的内容,比如具体模块的深入教程、跨平台开发问题、性能优化方法等,结合引用中的各个主题。 总结回答结构可能如下: 1. 开发环境配置(DevEco Studio) 2. UI开发技巧(ArkTS/ArkUI) 3. 音频处理(AudioCapturer) 4. 视频处理(全屏切换案例) 5. 加解密框架 6. 性能优化 7. 多端部署与分布式开发 每个部分简要说明技巧,并添加引用标识。 然后,相关问题部分可以列出关于这些模块的深入问题,如如何优化音频录制性能,如何处理跨平台加解密等。 需要确保每个引用在回答中至少被引用一次,并且标识正确。同时,避免使用Markdown格式,保持自然的中文表达。</think>### HarmonyOS NEXT开发实战技巧与教程指南 #### 一、开发环境与基础配置 1. **高效使用DevEco Studio** - 安装后优先配置SDK和模拟器,利用**预览仿真功能**实时查看UI效果,减少编译时间[^1]。 - 通过**代码模板**快速生成常用结构(如页面生命周期方法),提升开发效率[^3]。 - 示例:创建Hello World工程后,尝试使用`Ctrl+Alt+L`快捷键格式化代码,保持代码规范。 2. **性能优化工具** - 使用DevEco Studio内置的**Profiler工具**分析内存占用和CPU性能,定位卡顿问题。 - 对资源文件(如图片)进行压缩,避免应用体积过大[^3]。 --- #### 二、核心开发技术实战 1. **ArkTS/ArkUI开发技巧** - 利用`@State`和`@Link`装饰器实现数据与UI的动态绑定,例如列表渲染时更新视图[^2][^3]。 - 通过**自适应布局**(栅格系统)适配不同屏幕尺寸,代码片段示例: ```typescript @Entry struct Index { build() { GridRow() { GridCol({ span: { xs: 12, sm: 6 } }) { // 响应式布局 Text('HarmonyOS').fontSize(20) } } } } ``` 2. **音频处理(AudioCapturer)** - 实现录音功能时需声明`ohos.permission.MICROPHONE`权限,并配置采样率、声道等参数。 - 关键代码:初始化`AudioCapturer`后,通过`start()`和`stop()`控制录制流程。 3. **视频全屏切换案例** - 使用`VideoController`控制播放状态,监听屏幕旋转事件切换全屏模式。 - 通过`displayMatch`属性实现视频比例自适应,避免画面拉伸。 --- #### 三、进阶功能开发 1. **加解密算法框架** - 跨平台场景下,使用`Crypto Framework`生成密钥,注意HarmonyOS与安卓的算法兼容性[^4]。 - 示例:对本地文件加密时,优先选择`AES-GCM`算法保障数据安全。 2. **分布式能力应用** - 调用`distributedDeviceManager`实现多设备协同,如跨设备文件传输。 - 使用`RPC通信`同步设备状态,需处理网络中断等异常情况。 --- #### 四、学习资源与调试技巧 - **官方文档与手册**:参考鸿蒙联合发布的1236页学习手册,重点掌握Stage模型和资源管理[^3]。 - **调试技巧**:通过`HiLog`输出日志定位问题,结合断点调试复杂逻辑。 ---
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值