鸿蒙开发进阶(HarmonyOS )加速Web页面的访问

打印 上一主题 下一主题

主题 1006|帖子 1006|积分 3018

 鸿蒙NEXT开发实战往期必看文章:
一分钟相识”纯血版!鸿蒙HarmonyOS Next应用开发!
“非常详细的” 鸿蒙HarmonyOS Next应用开发学习门路!(从零底子入门到精通)
HarmonyOS NEXT应用开发案例实践总结合(持续更新......)
HarmonyOS NEXT应用开发性能优化实践总结(持续更新......)

当Web页面加载缓慢时,可以利用预连接、预加载和预获取post请求的本事加速Web页面的访问。
预分析和预连接

可以通过prepareForPageLoad()来预分析大概预连接将要加载的页面。
在下面的示例中,在Web组件的onAppear中对要加载的页面举行预连接。
  1. // xxx.ets
  2. import { webview } from '@kit.ArkWeb';
  3. @Entry
  4. @Component
  5. struct WebComponent {
  6.   webviewController: webview.WebviewController = new webview.WebviewController();
  7.   build() {
  8.     Column() {
  9.       Button('loadData')
  10.         .onClick(() => {
  11.           if (this.webviewController.accessBackward()) {
  12.             this.webviewController.backward();
  13.           }
  14.         })
  15.       Web({ src: 'https://www.example.com/', controller: this.webviewController })
  16.         .onAppear(() => {
  17.           // 指定第二个参数为true,代表要进行预连接,如果为false该接口只会对网址进行dns预解析
  18.           // 第三个参数为要预连接socket的个数。最多允许6个。
  19.           webview.WebviewController.prepareForPageLoad('https://www.example.com/', true, 2);
  20.         })
  21.     }
  22.   }
  23. }
复制代码
也可以通过initializeBrowserEngine()来提前初始化内核,然后在初始化内核后调用
prepareForPageLoad()对即将要加载的页面举行预分析、预连接。这种方式适合提前对首页举行
预分析、预连接。
在下面的示例中,Ability的onCreate中提前初始化Web内核并对首页举行预连接。
  1. // xxx.ets
  2. import { webview } from '@kit.ArkWeb';
  3. import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
  4. export default class EntryAbility extends UIAbility {
  5.   onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  6.     console.log("EntryAbility onCreate");
  7.     webview.WebviewController.initializeWebEngine();
  8.     // 预连接时,需要將'https://www.example.com'替换成真实要访问的网站地址。
  9.     webview.WebviewController.prepareForPageLoad("https://www.example.com/", true, 2);
  10.     AppStorage.setOrCreate("abilityWant", want);
  11.     console.log("EntryAbility onCreate done");
  12.   }
  13. }
复制代码
预加载

如果可以或许预测到Web组件将要加载的页面大概即将要跳转的页面。可以通过prefetchPage()来预加载即将要加载页面。
预加载会提前下载页面所需的资源,包罗主资源子资源,但不会实行网页JavaScript代码。预加载是WebviewController的实例方法,必要一个已经关联好Web组件的WebviewController实例。
在下面的示例中,在onPageEnd的时候触发下一个要访问的页面的预加载。
  1. // xxx.ets
  2. import { webview } from '@kit.ArkWeb';
  3. @Entry
  4. @Component
  5. struct WebComponent {
  6.   webviewController: webview.WebviewController = new webview.WebviewController();
  7.   build() {
  8.     Column() {
  9.       Web({ src: 'https://www.example.com/', controller: this.webviewController })
  10.         .onPageEnd(() => {
  11.           // 预加载https://www.iana.org/help/example-domains。
  12.           this.webviewController.prefetchPage('https://www.iana.org/help/example-domains');
  13.         })
  14.     }
  15.   }
  16. }
复制代码
预获取post请求

可以通过prefetchResource()预获取将要加载页面中的post请求。在页面加载结束时,可以通过clearPrefetchedResource()打扫后续不再利用的预获取资源缓存。
以下示例,在Web组件onAppear中,对要加载页面中的post请求举行预获取。在onPageEnd中,可以打扫预获取的post请求缓存。
  1. // xxx.ets
  2. import { webview } from '@kit.ArkWeb';
  3. @Entry
  4. @Component
  5. struct WebComponent {
  6.   webviewController: webview.WebviewController = new webview.WebviewController();
  7.   
  8.   build() {
  9.     Column() {
  10.       Web({ src: "https://www.example.com/", controller: this.webviewController})
  11.         .onAppear(() => {
  12.           // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
  13.           webview.WebviewController.prefetchResource(
  14.             {url:"https://www.example1.com/post?e=f&g=h",
  15.               method:"POST",
  16.               formData:"a=x&b=y",},
  17.             [{headerKey:"c",
  18.               headerValue:"z",},],
  19.             "KeyX", 500);
  20.         })
  21.         .onPageEnd(() => {
  22.           // 清除后续不再使用的预获取资源缓存。
  23.           webview.WebviewController.clearPrefetchedResource(["KeyX",]);
  24.         })
  25.     }
  26.   }
  27. }
复制代码
如果可以或许预测到Web组件将要加载页面大概即将要跳转页面中的post请求。可以通过prefetchResource()预获取即将要加载页面的post请求。
以下示例,在onPageEnd中,触发预获取一个要访问页面的post请求。
  1. // xxx.ets
  2. import { webview } from '@kit.ArkWeb';
  3. @Entry
  4. @Component
  5. struct WebComponent {
  6.   webviewController: webview.WebviewController = new webview.WebviewController();
  7.   
  8.   build() {
  9.     Column() {
  10.       Web({ src: 'https://www.example.com/', controller: this.webviewController})
  11.         .onPageEnd(() => {
  12.           // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
  13.           webview.WebviewController.prefetchResource(
  14.             {url:"https://www.example1.com/post?e=f&g=h",
  15.               method:"POST",
  16.               formData:"a=x&b=y",},
  17.             [{headerKey:"c",
  18.               headerValue:"z",},],
  19.             "KeyX", 500);
  20.         })
  21.     }
  22.   }
  23. }
复制代码
也可以通过initializeBrowserEngine()提前初始化内核,然后在初始化内核后调用prefetchResource()预获取将要加载页面中的post请求。这种方式适合提前预获取首页的post请求。
以下示例,在Ability的onCreate中,提前初始化Web内核并预获取首页的post请求。
  1. // xxx.ets
  2. import { webview } from '@kit.ArkWeb';
  3. import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
  4. export default class EntryAbility extends UIAbility {
  5.   onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  6.     console.log("EntryAbility onCreate");
  7.     webview.WebviewController.initializeWebEngine();
  8.     // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
  9.     webview.WebviewController.prefetchResource(
  10.       {url:"https://www.example1.com/post?e=f&g=h",
  11.         method:"POST",
  12.         formData:"a=x&b=y",},
  13.       [{headerKey:"c",
  14.         headerValue:"z",},],
  15.       "KeyX", 500);
  16.     AppStorage.setOrCreate("abilityWant", want);
  17.     console.log("EntryAbility onCreate done");
  18.   }
  19. }
复制代码
预编译天生编译缓存

可以通过precompileJavaScript()在页面加载前提前天生脚本文件的编译缓存。
保举配合动态组件利用,利用离线的Web组件用于天生字节码缓存,并在适当的时机加载业务用Web组件利用这些字节码缓存。下方是代码示例:

  • 首先,在EntryAbility中将UIContext存到localStorage中。
    1. // EntryAbility.ets
    2. import { UIAbility } from '@kit.AbilityKit';
    3. import { window } from '@kit.ArkUI';
    4. const localStorage: LocalStorage = new LocalStorage('uiContext');
    5. export default class EntryAbility extends UIAbility {
    6.   storage: LocalStorage = localStorage;
    7.   onWindowStageCreate(windowStage: window.WindowStage) {
    8.     windowStage.loadContent('pages/Index', this.storage, (err, data) => {
    9.       if (err.code) {
    10.         return;
    11.       }
    12.       this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext());
    13.     });
    14.   }
    15. }
    复制代码
  • 编写动态组件所需底子代码。
    1. // DynamicComponent.ets
    2. import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
    3. export interface BuilderData {
    4.   url: string;
    5.   controller: WebviewController;
    6. }
    7. const storage = LocalStorage.getShared();
    8. export class NodeControllerImpl extends NodeController {
    9.   private rootNode: BuilderNode<BuilderData[]> | null = null;
    10.   private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
    11.   constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>) {
    12.     super();
    13.     this.wrappedBuilder = wrappedBuilder;
    14.   }
    15.   makeNode(): FrameNode | null {
    16.     if (this.rootNode != null) {
    17.       return this.rootNode.getFrameNode();
    18.     }
    19.     return null;
    20.   }
    21.   initWeb(url: string, controller: WebviewController) {
    22.     if(this.rootNode != null) {
    23.       return;
    24.     }
    25.     const uiContext: UIContext = storage.get<UIContext>("uiContext") as UIContext;
    26.     if (!uiContext) {
    27.       return;
    28.     }
    29.     this.rootNode = new BuilderNode(uiContext);
    30.     this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller });
    31.   }
    32. }
    33. export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
    34.   const baseNode = new NodeControllerImpl(wrappedBuilder);
    35.   baseNode.initWeb(data.url, data.controller);
    36.   return baseNode;
    37. }
    复制代码
  • 编写用于天生字节码缓存的组件,本例中的本地Javascript资源内容通过文件读取接口读取rawfile目录下的本地文件。
    1. // PrecompileWebview.ets
    2. import { BuilderData } from "./DynamicComponent";
    3. import { Config, configs } from "./PrecompileConfig";
    4. @Builder
    5. function WebBuilder(data: BuilderData) {
    6.   Web({ src: data.url, controller: data.controller })
    7.     .onControllerAttached(() => {
    8.       precompile(data.controller, configs);
    9.     })
    10.     .fileAccess(true)
    11. }
    12. export const precompileWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    13. export const precompile = async (controller: WebviewController, configs: Array<Config>) => {
    14.   for (const config of configs) {
    15.     let content = await readRawFile(config.localPath);
    16.     try {
    17.       controller.precompileJavaScript(config.url, content, config.options)
    18.         .then(errCode => {
    19.           console.error("precompile successfully! " + errCode);
    20.         }).catch((errCode: number) => {
    21.           console.error("precompile failed. " + errCode);
    22.       });
    23.     } catch (err) {
    24.       console.error("precompile failed. " + err.code + " " + err.message);
    25.     }
    26.   }
    27. }
    28. async function readRawFile(path: string) {
    29.   try {
    30.     return await getContext().resourceManager.getRawFileContent(path);;
    31.   } catch (err) {
    32.     return new Uint8Array(0);
    33.   }
    34. }
    复制代码
JavaScript资源的获取方式也可通过网络请求的方式获取,但此方法获取到的http响应头非标准HTTP响应头格式,需额外将响应头转换成标准HTTP响应头格式后利用。如通过网络请求获取到的响应头是e-tag,则必要将其转换成E-Tag后利用。

  • 编写业务用组件代码。
    1. // BusinessWebview.ets
    2. import { BuilderData } from "./DynamicComponent";
    3. @Builder
    4. function WebBuilder(data: BuilderData) {
    5.   // 此处组件可根据业务需要自行扩展
    6.   Web({ src: data.url, controller: data.controller })
    7.     .cacheMode(CacheMode.Default)
    8. }
    9. export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    复制代码
  • 编写资源设置信息。
    1. // PrecompileConfig.ets
    2. import { webview } from '@kit.ArkWeb'
    3. export interface Config {
    4.   url:  string,
    5.   localPath: string, // 本地资源路径
    6.   options: webview.CacheOptions
    7. }
    8. export let configs: Array<Config> = [
    9.   {
    10.     url: "https://www.example.com/example.js",
    11.     localPath: "example.js",
    12.     options: {
    13.       responseHeaders: [
    14.         { headerKey: "E-Tag", headerValue: "aWO42N9P9dG/5xqYQCxsx+vDOoU="},
    15.         { headerKey: "Last-Modified", headerValue: "Wed, 21 Mar 2024 10:38:41 GMT"}
    16.       ]
    17.     }
    18.   }
    19. ]
    复制代码
  • 在页面中利用。
    1. // Index.ets
    2. import { webview } from '@kit.ArkWeb';
    3. import { NodeController } from '@kit.ArkUI';
    4. import { createNode } from "./DynamicComponent"
    5. import { precompileWebview } from "./PrecompileWebview"
    6. import { businessWebview } from "./BusinessWebview"
    7. @Entry
    8. @Component
    9. struct Index {
    10.   @State precompileNode: NodeController | undefined = undefined;
    11.   precompileController: webview.WebviewController = new webview.WebviewController();
    12.   @State businessNode: NodeController | undefined = undefined;
    13.   businessController: webview.WebviewController = new webview.WebviewController();
    14.   aboutToAppear(): void {
    15.     // 初始化用于注入本地资源的Web组件
    16.     this.precompileNode = createNode(precompileWebview,
    17.       { url: "https://www.example.com/empty.html", controller: this.precompileController});
    18.   }
    19.   build() {
    20.     Column() {
    21.       // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
    22.       Button("加载页面")
    23.         .onClick(() => {
    24.           this.businessNode = createNode(businessWebview, {
    25.             url:  "https://www.example.com/business.html",
    26.             controller: this.businessController
    27.           });
    28.         })
    29.       // 用于业务的Web组件
    30.       NodeContainer(this.businessNode);
    31.     }
    32.   }
    33. }
    复制代码
当必要更新本地已经天生的编译字节码时,修改cacheOptions参数中responseHeaders中的E-Tag或Last-Modified响应头对应的值,再次调用接口即可。
离线资源免拦截注入

可以通过injectOfflineResources()在页面加载前提前将图片、样式表或脚本资源注入到应用的内存缓存中。
保举配合动态组件利用,利用离线的Web组件用于将资源注入到内核的内存缓存中,并在适当的时机加载业务用Web组件利用这些资源。下方是代码示例:

  • 首先,在EntryAbility中将UIContext存到localStorage中。
    1. // EntryAbility.ets
    2. import { UIAbility } from '@kit.AbilityKit';
    3. import { window } from '@kit.ArkUI';
    4. const localStorage: LocalStorage = new LocalStorage('uiContext');
    5. export default class EntryAbility extends UIAbility {
    6.   storage: LocalStorage = localStorage;
    7.   onWindowStageCreate(windowStage: window.WindowStage) {
    8.     windowStage.loadContent('pages/Index', this.storage, (err, data) => {
    9.       if (err.code) {
    10.         return;
    11.       }
    12.       this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext());
    13.     });
    14.   }
    15. }
    复制代码
  • 编写动态组件所需底子代码。
    1. // DynamicComponent.ets
    2. import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
    3. export interface BuilderData {
    4.   url: string;
    5.   controller: WebviewController;
    6. }
    7. const storage = LocalStorage.getShared();
    8. export class NodeControllerImpl extends NodeController {
    9.   private rootNode: BuilderNode<BuilderData[]> | null = null;
    10.   private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
    11.   constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>) {
    12.     super();
    13.     this.wrappedBuilder = wrappedBuilder;
    14.   }
    15.   makeNode(): FrameNode | null {
    16.     if (this.rootNode != null) {
    17.       return this.rootNode.getFrameNode();
    18.     }
    19.     return null;
    20.   }
    21.   initWeb(url: string, controller: WebviewController) {
    22.     if(this.rootNode != null) {
    23.       return;
    24.     }
    25.     const uiContext: UIContext = storage.get<UIContext>("uiContext") as UIContext;
    26.     if (!uiContext) {
    27.       return;
    28.     }
    29.     this.rootNode = new BuilderNode(uiContext);
    30.     this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller });
    31.   }
    32. }
    33. export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
    34.   const baseNode = new NodeControllerImpl(wrappedBuilder);
    35.   baseNode.initWeb(data.url, data.controller);
    36.   return baseNode;
    37. }
    复制代码
  • 编写用于注入资源的组件代码,本例中的本地资源内容通过文件读取接口读取rawfile目录下的本地文件。
    1. // InjectWebview.ets
    2. import { webview } from '@kit.ArkWeb';
    3. import { resourceConfigs } from "./Resource";
    4. import { BuilderData } from "./DynamicComponent";
    5. @Builder
    6. function WebBuilder(data: BuilderData) {
    7.   Web({ src: data.url, controller: data.controller })
    8.     .onControllerAttached(async () => {
    9.       try {
    10.         data.controller.injectOfflineResources(await getData ());
    11.       } catch (err) {
    12.         console.error("error: " + err.code + " " + err.message);
    13.       }
    14.     })
    15.     .fileAccess(true)
    16. }
    17. export const injectWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    18. export async function getData() {
    19.   const resourceMapArr: Array<webview.OfflineResourceMap> = [];
    20.   // 读取配置,从rawfile目录中读取文件内容
    21.   for (let config of resourceConfigs) {
    22.     let buf: Uint8Array = new Uint8Array(0);
    23.     if (config.localPath) {
    24.       buf = await readRawFile(config.localPath);
    25.     }
    26.     resourceMapArr.push({
    27.       urlList: config.urlList,
    28.       resource: buf,
    29.       responseHeaders: config.responseHeaders,
    30.       type: config.type,
    31.     })
    32.   }
    33.   return resourceMapArr;
    34. }
    35. export async function readRawFile(url: string) {
    36.   try {
    37.     return await getContext().resourceManager.getRawFileContent(url);
    38.   } catch (err) {
    39.     return new Uint8Array(0);
    40.   }
    41. }
    复制代码
  • 编写业务用组件代码。
    1. // BusinessWebview.ets
    2. import { BuilderData } from "./DynamicComponent";
    3. @Builder
    4. function WebBuilder(data: BuilderData) {
    5.   // 此处组件可根据业务需要自行扩展
    6.   Web({ src: data.url, controller: data.controller })
    7.     .cacheMode(CacheMode.Default)
    8. }
    9. export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder);
    复制代码
  • 编写资源设置信息。
    1. // Resource.ets
    2. import { webview } from '@kit.ArkWeb';
    3. export interface ResourceConfig {
    4.   urlList: Array<string>,
    5.   type: webview.OfflineResourceType,
    6.   responseHeaders: Array<Header>,
    7.   localPath: string, // 本地资源存放在rawfile目录下的路径
    8. }
    9. export const resourceConfigs: Array<ResourceConfig> = [
    10.   {
    11.     localPath: "example.png",
    12.     urlList: [
    13.       "https://www.example.com/",
    14.       "https://www.example.com/path1/example.png",
    15.       "https://www.example.com/path2/example.png",
    16.     ],
    17.     type: webview.OfflineResourceType.IMAGE,
    18.     responseHeaders: [
    19.       { headerKey: "Cache-Control", headerValue: "max-age=1000" },
    20.       { headerKey: "Content-Type", headerValue: "image/png" },
    21.     ]
    22.   },
    23.   {
    24.     localPath: "example.js",
    25.     urlList: [ // 仅提供一个url,这个url既作为资源的源,也作为资源的网络请求地址
    26.       "https://www.example.com/example.js",
    27.     ],
    28.     type: webview.OfflineResourceType.CLASSIC_JS,
    29.     responseHeaders: [
    30.       // 以<script crossorigin="anoymous" />方式使用,提供额外的响应头
    31.       { headerKey: "Cross-Origin", headerValue:"anonymous" }
    32.     ]
    33.   },
    34. ];
    复制代码
  • 在页面中利用。
    1. // Index.ets
    2. import { webview } from '@kit.ArkWeb';
    3. import { NodeController } from '@kit.ArkUI';
    4. import { createNode } from "./DynamicComponent"
    5. import { injectWebview } from "./InjectWebview"
    6. import { businessWebview } from "./BusinessWebview"
    7. @Entry
    8. @Component
    9. struct Index {
    10.   @State injectNode: NodeController | undefined = undefined;
    11.   injectController: webview.WebviewController = new webview.WebviewController();
    12.   @State businessNode: NodeController | undefined = undefined;
    13.   businessController: webview.WebviewController = new webview.WebviewController();
    14.   aboutToAppear(): void {
    15.     // 初始化用于注入本地资源的Web组件, 提供一个空的html页面作为url即可
    16.     this.injectNode = createNode(injectWebview,
    17.         { url: "https://www.example.com/empty.html", controller: this.injectController});
    18.   }
    19.   build() {
    20.     Column() {
    21.       // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
    22.       Button("加载页面")
    23.         .onClick(() => {
    24.           this.businessNode = createNode(businessWebview, {
    25.             url: "https://www.example.com/business.html",
    26.             controller: this.businessController
    27.           });
    28.         })
    29.       // 用于业务的Web组件
    30.       NodeContainer(this.businessNode);
    31.     }
    32.   }
    33. }
    复制代码
  • 加载的HTML网页示例。
    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head></head>
    4. <body>
    5.   <img src="https://www.example.com/path1/request.png" />
    6.   <img src="https://www.example.com/path2/request.png" />
    7.   <script src="https://www.example.com/example.js" crossorigin="anonymous"></script>
    8. </body>
    9. </html>
    复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊雷无声

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表