# 加速Web页面的访问 当Web页面加载缓慢时,可以使用预连接、预加载和预获取post请求的能力加速Web页面的访问。 ## 预解析和预连接 可以通过[prepareForPageLoad()](../reference/apis-arkweb/js-apis-webview.md#prepareforpageload10)来预解析或者预连接将要加载的页面。 在下面的示例中,在Web组件的onAppear中对要加载的页面进行预连接。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; @Entry @Component struct WebComponent { webviewController: webview.WebviewController = new webview.WebviewController(); build() { Column() { Button('loadData') .onClick(() => { if (this.webviewController.accessBackward()) { this.webviewController.backward(); } }) Web({ src: 'https://www.example.com/', controller: this.webviewController }) .onAppear(() => { // 指定第二个参数为true,代表要进行预连接,如果为false该接口只会对网址进行dns预解析 // 第三个参数为要预连接socket的个数。最多允许6个。 webview.WebviewController.prepareForPageLoad('https://www.example.com/', true, 2); }) } } } ``` 也可以通过[initializeBrowserEngine()](../reference/apis-arkweb/js-apis-webview.md#initializewebengine)来提前初始化内核,然后在初始化内核后调用 [prepareForPageLoad()](../reference/apis-arkweb/js-apis-webview.md#prepareforpageload10)对即将要加载的页面进行预解析、预连接。这种方式适合提前对首页进行 预解析、预连接。 在下面的示例中,Ability的onCreate中提前初始化Web内核并对首页进行预连接。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; import { AbilityConstant, UIAbility, Want } from '@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()](../reference/apis-arkweb/js-apis-webview.md#prefetchpage10)来预加载即将要加载页面。 预加载会提前下载页面所需的资源,包括主资源子资源,但不会执行网页JavaScript代码。预加载是WebviewController的实例方法,需要一个已经关联好Web组件的WebviewController实例。 在下面的示例中,在onPageEnd的时候触发下一个要访问的页面的预加载。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; @Entry @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()](../reference/apis-arkweb/js-apis-webview.md#prefetchresource12)预获取将要加载页面中的post请求。在页面加载结束时,可以通过[clearPrefetchedResource()](../reference/apis-arkweb/js-apis-webview.md#clearprefetchedresource12)清除后续不再使用的预获取资源缓存。 以下示例,在Web组件onAppear中,对要加载页面中的post请求进行预获取。在onPageEnd中,可以清除预获取的post请求缓存。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; @Entry @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",]); }) } } } ``` 如果能够预测到Web组件将要加载页面或者即将要跳转页面中的post请求。可以通过[prefetchResource()](../reference/apis-arkweb/js-apis-webview.md#prefetchresource12)预获取即将要加载页面的post请求。 以下示例,在onPageEnd中,触发预获取一个要访问页面的post请求。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; @Entry @Component struct WebComponent { webviewController: webview.WebviewController = new webview.WebviewController(); build() { Column() { Web({ src: 'https://www.example.com/', controller: this.webviewController}) .onPageEnd(() => { // 预获取时,需要將"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); }) } } } ``` 也可以通过[initializeBrowserEngine()](../reference/apis-arkweb/js-apis-webview.md#initializewebengine)提前初始化内核,然后在初始化内核后调用[prefetchResource()](../reference/apis-arkweb/js-apis-webview.md#prefetchresource12)预获取将要加载页面中的post请求。这种方式适合提前预获取首页的post请求。 以下示例,在Ability的onCreate中,提前初始化Web内核并预获取首页的post请求。 ```ts // xxx.ets import { webview } from '@kit.ArkWeb'; import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { console.log("EntryAbility onCreate"); webview.WebviewController.initializeWebEngine(); // 预获取时,需要將"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); AppStorage.setOrCreate("abilityWant", want); console.log("EntryAbility onCreate done"); } } ``` ## 预编译生成编译缓存 可以通过[precompileJavaScript()](../reference/apis-arkweb/js-apis-webview.md#precompilejavascript12)在页面加载前提前生成脚本文件的编译缓存。 推荐配合动态组件使用,使用离线的Web组件用于生成字节码缓存,并在适当的时机加载业务用Web组件使用这些字节码缓存。下方是代码示例: 1. 首先,在EntryAbility中将UIContext存到localStorage中。 ```ts // EntryAbility.ets import { UIAbility } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; const localStorage: LocalStorage = new LocalStorage('uiContext'); export default class EntryAbility extends UIAbility { storage: LocalStorage = localStorage; onWindowStageCreate(windowStage: window.WindowStage) { windowStage.loadContent('pages/Index', this.storage, (err, data) => { if (err.code) { return; } this.storage.setOrCreate("uiContext", windowStage.getMainWindowSync().getUIContext()); }); } } ``` 2. 编写动态组件所需基础代码。 ```ts // DynamicComponent.ets import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; export interface BuilderData { url: string; controller: WebviewController; } const storage = LocalStorage.getShared(); export class NodeControllerImpl extends NodeController { private rootNode: BuilderNode | null = null; private wrappedBuilder: WrappedBuilder | null = null; constructor(wrappedBuilder: WrappedBuilder) { super(); this.wrappedBuilder = wrappedBuilder; } makeNode(): FrameNode | null { if (this.rootNode != null) { return this.rootNode.getFrameNode(); } return null; } initWeb(url: string, controller: WebviewController) { if(this.rootNode != null) { return; } const uiContext: UIContext = storage.get("uiContext") as UIContext; if (!uiContext) { return; } this.rootNode = new BuilderNode(uiContext); this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller }); } } export const createNode = (wrappedBuilder: WrappedBuilder, data: BuilderData) => { const baseNode = new NodeControllerImpl(wrappedBuilder); baseNode.initWeb(data.url, data.controller); return baseNode; } ``` 3. 编写用于生成字节码缓存的组件,本例中的本地Javascript资源内容通过文件读取接口读取rawfile目录下的本地文件。 ```ts // PrecompileWebview.ets import { BuilderData } from "./DynamicComponent"; import { Config, configs } from "./PrecompileConfig"; @Builder function WebBuilder(data: BuilderData) { Web({ src: data.url, controller: data.controller }) .onControllerAttached(() => { precompile(data.controller, configs); }) .fileAccess(true) } export const precompileWebview = wrapBuilder(WebBuilder); export const precompile = async (controller: WebviewController, configs: Array) => { for (const config of configs) { let content = await readRawFile(config.localPath); try { controller.precompileJavaScript(config.url, content, config.options) .then(errCode => { console.error("precompile successfully! " + errCode); }).catch((errCode: number) => { console.error("precompile failed. " + errCode); }); } catch (err) { console.error("precompile failed. " + err.code + " " + err.message); } } } async function readRawFile(path: string) { try { return await getContext().resourceManager.getRawFileContent(path);; } catch (err) { return new Uint8Array(0); } } ``` JavaScript资源的获取方式也可通过[网络请求](../reference/apis-network-kit/js-apis-http.md)的方式获取,但此方法获取到的http响应头非标准HTTP响应头格式,需额外将响应头转换成标准HTTP响应头格式后使用。如通过网络请求获取到的响应头是e-tag,则需要将其转换成E-Tag后使用。 4. 编写业务用组件代码。 ```ts // BusinessWebview.ets import { BuilderData } from "./DynamicComponent"; @Builder function WebBuilder(data: BuilderData) { // 此处组件可根据业务需要自行扩展 Web({ src: data.url, controller: data.controller }) .cacheMode(CacheMode.Default) } export const businessWebview = wrapBuilder(WebBuilder); ``` 5. 编写资源配置信息。 ```ts // PrecompileConfig.ets import { webview } from '@kit.ArkWeb' export interface Config { url: string, localPath: string, // 本地资源路径 options: webview.CacheOptions } export let configs: Array = [ { url: "https://www.example.com/example.js", localPath: "example.js", options: { responseHeaders: [ { headerKey: "E-Tag", headerValue: "aWO42N9P9dG/5xqYQCxsx+vDOoU="}, { headerKey: "Last-Modified", headerValue: "Wed, 21 Mar 2024 10:38:41 GMT"} ] } } ] ``` 6. 在页面中使用。 ```ts // Index.ets import { webview } from '@kit.ArkWeb'; import { NodeController } from '@kit.ArkUI'; import { createNode } from "./DynamicComponent" import { precompileWebview } from "./PrecompileWebview" import { businessWebview } from "./BusinessWebview" @Entry @Component struct Index { @State precompileNode: NodeController | undefined = undefined; precompileController: webview.WebviewController = new webview.WebviewController(); @State businessNode: NodeController | undefined = undefined; businessController: webview.WebviewController = new webview.WebviewController(); aboutToAppear(): void { // 初始化用于注入本地资源的Web组件 this.precompileNode = createNode(precompileWebview, { url: "https://www.example.com/empty.html", controller: this.precompileController}); } build() { Column() { // 在适当的时机加载业务用Web组件,本例以Button点击触发为例 Button("加载页面") .onClick(() => { this.businessNode = createNode(businessWebview, { url: "https://www.example.com/business.html", controller: this.businessController }); }) // 用于业务的Web组件 NodeContainer(this.businessNode); } } } ``` 当需要更新本地已经生成的编译字节码时,修改cacheOptions参数中responseHeaders中的E-Tag或Last-Modified响应头对应的值,再次调用接口即可。 ## 离线资源免拦截注入 可以通过[injectOfflineResources()](../reference/apis-arkweb/js-apis-webview.md#injectofflineresources12)在页面加载前提前将图片、样式表或脚本资源注入到应用的内存缓存中。 推荐配合动态组件使用,使用离线的Web组件用于将资源注入到内核的内存缓存中,并在适当的时机加载业务用Web组件使用这些资源。下方是代码示例: 1. 首先,在EntryAbility中将UIContext存到localStorage中。 ```ts // EntryAbility.ets import { UIAbility } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; const localStorage: LocalStorage = new LocalStorage('uiContext'); export default class EntryAbility extends UIAbility { storage: LocalStorage = localStorage; onWindowStageCreate(windowStage: window.WindowStage) { windowStage.loadContent('pages/Index', this.storage, (err, data) => { if (err.code) { return; } this.storage.setOrCreate("uiContext", windowStage.getMainWindowSync().getUIContext()); }); } } ``` 2. 编写动态组件所需基础代码。 ```ts // DynamicComponent.ets import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI'; export interface BuilderData { url: string; controller: WebviewController; } const storage = LocalStorage.getShared(); export class NodeControllerImpl extends NodeController { private rootNode: BuilderNode | null = null; private wrappedBuilder: WrappedBuilder | null = null; constructor(wrappedBuilder: WrappedBuilder) { super(); this.wrappedBuilder = wrappedBuilder; } makeNode(): FrameNode | null { if (this.rootNode != null) { return this.rootNode.getFrameNode(); } return null; } initWeb(url: string, controller: WebviewController) { if(this.rootNode != null) { return; } const uiContext: UIContext = storage.get("uiContext") as UIContext; if (!uiContext) { return; } this.rootNode = new BuilderNode(uiContext); this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller }); } } export const createNode = (wrappedBuilder: WrappedBuilder, data: BuilderData) => { const baseNode = new NodeControllerImpl(wrappedBuilder); baseNode.initWeb(data.url, data.controller); return baseNode; } ``` 3. 编写用于注入资源的组件代码,本例中的本地资源内容通过文件读取接口读取rawfile目录下的本地文件。 ```ts // InjectWebview.ets import { webview } from '@kit.ArkWeb'; import { resourceConfigs } from "./Resource"; import { BuilderData } from "./DynamicComponent"; @Builder function WebBuilder(data: BuilderData) { Web({ src: data.url, controller: data.controller }) .onControllerAttached(async () => { try { data.controller.injectOfflineResources(await getData ()); } catch (err) { console.error("error: " + err.code + " " + err.message); } }) .fileAccess(true) } export const injectWebview = wrapBuilder(WebBuilder); export async function getData() { const resourceMapArr: Array = []; // 读取配置,从rawfile目录中读取文件内容 for (let config of resourceConfigs) { let buf: Uint8Array = new Uint8Array(0); if (config.localPath) { buf = await readRawFile(config.localPath); } resourceMapArr.push({ urlList: config.urlList, resource: buf, responseHeaders: config.responseHeaders, type: config.type, }) } return resourceMapArr; } export async function readRawFile(url: string) { try { return await getContext().resourceManager.getRawFileContent(url); } catch (err) { return new Uint8Array(0); } } ``` 4. 编写业务用组件代码。 ```ts // BusinessWebview.ets import { BuilderData } from "./DynamicComponent"; @Builder function WebBuilder(data: BuilderData) { // 此处组件可根据业务需要自行扩展 Web({ src: data.url, controller: data.controller }) .cacheMode(CacheMode.Default) } export const businessWebview = wrapBuilder(WebBuilder); ``` 5. 编写资源配置信息。 ```ts // Resource.ets import { webview } from '@kit.ArkWeb'; export interface ResourceConfig { urlList: Array, type: webview.OfflineResourceType, responseHeaders: Array
, localPath: string, // 本地资源存放在rawfile目录下的路径 } export const resourceConfigs: Array = [ { localPath: "example.png", urlList: [ "https://www.example.com/", "https://www.example.com/path1/example.png", "https://www.example.com/path2/example.png", ], type: webview.OfflineResourceType.IMAGE, responseHeaders: [ { headerKey: "Cache-Control", headerValue: "max-age=1000" }, { headerKey: "Content-Type", headerValue: "image/png" }, ] }, { localPath: "example.js", urlList: [ // 仅提供一个url,这个url既作为资源的源,也作为资源的网络请求地址 "https://www.example.com/example.js", ], type: webview.OfflineResourceType.CLASSIC_JS, responseHeaders: [ // 以 ```