HarmonyOS鸿蒙Next中优化Web组件实时加载部分url的时间过长问题

HarmonyOS鸿蒙Next中优化Web组件实时加载部分url的时间过长问题

【问题现象】

某些应用如资讯类应用,使用了Web组件来加载相应的云端Html页面。在某些页面内容较大时,页面加载速度较慢,一般需要20s以上,影响用户体验。

【背景知识】

加速Web页面的访问

【定位思路】

对于Web组件加载缓慢优化的思路:

  1. 可以使用预连接、预加载、预获取post请求的能力和预编译生成编译缓存加速Web页面的访问。
  2. 可以通过模板快载来优化加载速度。

【解决方案】

  • 预解析和预连接

    可以通过prepareForPageLoad()来预解析或者预连接将要加载的页面,Ability的onCreate中提前初始化Web内核并对首页进行预连接,示例代码如下:

    // xxx.ets
    import { webview } from '[@kit](/user/kit).ArkWeb';
    import { AbilityConstant, UIAbility, Want } from '[@kit](/user/kit).AbilityKit';
    
    export default class EntryAbility extends UIAbility {
      onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
        console.log("EntryAbility onCreate");
        webview.WebviewController.initializeWebEngine();
        // 预连接时,需要將'https://www.example.com'替换成真实要访问的网站地址。
        webview.WebviewController.prepareForPageLoad("https://www.example.com/", true, 2);
        AppStorage.setOrCreate("abilityWant", want);
        console.log("EntryAbility onCreate done");
      }
    }
    
  • 预加载

    能够预测到Web组件将要加载的页面或者即将要跳转的页面。可以通过prefetchPage()来预加载即将要加载页面。预加载会提前下载页面所需的资源,包括主资源子资源,但不会执行网页JavaScript代码。预加载是WebviewController的实例方法,需要一个已经关联好Web组件的WebviewController实例。在下面的示例中,在onPageEnd的时候触发下一个要访问的页面的预加载:

    // xxx.ets
    import { webview } from '[@kit](/user/kit).ArkWeb';
    
    [@Entry](/user/Entry)
    [@Component](/user/Component)
    struct WebComponent {
      webviewController: webview.WebviewController = new webview.WebviewController();
    
      build() {
        Column() {
          Web({ src: 'https://www.example.com/', controller: this.webviewController })
            .onPageEnd(() => {
              // 预加载https://www.iana.org/help/example-domains。
              this.webviewController.prefetchPage('https://www.iana.org/help/example-domains');
            })
        }
      }
    }
    
  • 预获取post请求

    可以通过prefetchResource()预获取将要加载页面中的post请求。在页面加载结束时,可以通过clearPrefetchedResource()清除后续不再使用的预获取资源缓存。以下示例,在Web组件onAppear中,对要加载页面中的post请求进行预获取。在onPageEnd中,可以清除预获取的post请求缓存:

    // xxx.ets
    import { webview } from '[@kit](/user/kit).ArkWeb';
    
    [@Entry](/user/Entry)
    [@Component](/user/Component)
    struct WebComponent {
      webviewController: webview.WebviewController = new webview.WebviewController();
    
      build() {
        Column() {
          Web({ src: "https://www.example.com/", controller: this.webviewController })
            .onAppear(() => {
              // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
              webview.WebviewController.prefetchResource(
                {url:"https://www.example1.com/post?e=f&g=h",
                  method:"POST",
                  formData:"a=x&b=y",},
                [{headerKey:"c",
                  headerValue:"z",},],
                "KeyX", 500);
            })
            .onPageEnd(() => {
              // 清除后续不再使用的预获取资源缓存。
              webview.WebviewController.clearPrefetchedResource(["KeyX",]);
            })
        }
      }
    }
    
  • 预编译生成编译缓存

    可以通过precompileJavaScript()在页面加载前提前生成脚本文件的编译缓存。推荐配合动态组件使用,使用离线的Web组件用于生成字节码缓存,并在适当的时机加载业务用Web组件使用这些字节码缓存。

    参考代码如下:

    // 载体Ability
    // EntryAbility.ets
    import { createNWeb } from "../pages/common"
    
    onWindowStageCreate(windowStage:window.WindowStage):
    void {
       windowStage.loadContent('pages/Index',(err,data)=>{
       // 创建Web动态组件(需传入UIContext),loadContent之后的任意时机均可创建
       createNWeb
       (
       "https://www.example.com",
       windowStage.getMainWindowSync().getUIContext());
       if(err.code){
       return;
       }
      }
     );
    }
    
    // 创建NodeController
    // common.ets
    import { UIContext, NodeController, BuilderNode, Size, FrameNode } from '[@kit](/user/kit).ArkUI';
    import { webview } from '[@kit](/user/kit).ArkWeb';
    
    // [@Builder](/user/Builder)中为动态组件的具体组件内容
    // Data为入参封装类
    class Data {
      url: string = "https://www.example.com";
      controller: WebviewController = new webview.WebviewController();
    }
    
    [@Builder](/user/Builder)
    function WebBuilder(data: Data) {
      Column() {
        Web({ src: data.url, controller: data.controller })
          .width("100%")
          .height("100%")
      }
    }
    
    let wrap = wrapBuilder<Data[]>(WebBuilder);
    
    // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用
    export class myNodeController extends NodeController {
      private rootnode: BuilderNode<Data[]> | null = null;
    
      // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContainer中
      // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新
      makeNode(uiContext: UIContext): FrameNode | null {
        console.log(" uicontext is undefined : " + (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](/user/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);
    }
    
    // 使用NodeController的Page页
    // Index.ets
    import { getNWeb } from "./common"
    
    [@Entry](/user/Entry)
    [@Component](/user/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实例不建议超过200个。


更多关于HarmonyOS鸿蒙Next中优化Web组件实时加载部分url的时间过长问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中优化Web组件实时加载部分url的时间过长问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


优化HarmonyOS鸿蒙Next中Web组件实时加载部分URL时间过长问题,可通过以下方式:

  1. 预解析和预连接:使用prepareForPageLoad()在Ability的onCreate中预解析或预连接页面,提前初始化Web内核。

  2. 预加载:使用prefetchPage()onPageEnd时预加载即将访问的页面资源,提前下载主资源及子资源。

  3. 预获取post请求:使用prefetchResource()在页面加载前预获取post请求资源,并在onPageEnd时清除不再使用的预获取缓存。

  4. 预编译生成编译缓存:使用precompileJavaScript()在页面加载前生成脚本文件的编译缓存,配合动态组件使用,提升加载速度。

后台启动的Web实例不建议超过200个。

回到顶部