• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 加速Web页面的访问
2
3当Web页面加载缓慢时,可以使用预连接、预加载和预获取post请求的能力加速Web页面的访问。
4
5## 预解析和预连接
6
7可以通过[prepareForPageLoad()](../reference/apis-arkweb/js-apis-webview.md#prepareforpageload10)来预解析或者预连接将要加载的页面。
8
9  在下面的示例中,在Web组件的onAppear中对要加载的页面进行预连接。
10
11```ts
12// xxx.ets
13import { webview } from '@kit.ArkWeb';
14
15@Entry
16@Component
17struct WebComponent {
18  webviewController: webview.WebviewController = new webview.WebviewController();
19
20  build() {
21    Column() {
22      Button('loadData')
23        .onClick(() => {
24          if (this.webviewController.accessBackward()) {
25            this.webviewController.backward();
26          }
27        })
28      Web({ src: 'https://www.example.com/', controller: this.webviewController })
29        .onAppear(() => {
30          // 指定第二个参数为true,代表要进行预连接,如果为false该接口只会对网址进行dns预解析
31          // 第三个参数为要预连接socket的个数。最多允许6个。
32          webview.WebviewController.prepareForPageLoad('https://www.example.com/', true, 2);
33        })
34    }
35  }
36}
37```
38
39也可以通过[initializeBrowserEngine()](../reference/apis-arkweb/js-apis-webview.md#initializewebengine)来提前初始化内核,然后在初始化内核后调用
40[prepareForPageLoad()](../reference/apis-arkweb/js-apis-webview.md#prepareforpageload10)对即将要加载的页面进行预解析、预连接。这种方式适合提前对首页进行
41预解析、预连接。
42
43  在下面的示例中,Ability的onCreate中提前初始化Web内核并对首页进行预连接。
44
45```ts
46// xxx.ets
47import { webview } from '@kit.ArkWeb';
48import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
49
50export default class EntryAbility extends UIAbility {
51  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
52    console.log("EntryAbility onCreate");
53    webview.WebviewController.initializeWebEngine();
54    // 预连接时,需要將'https://www.example.com'替换成真实要访问的网站地址。
55    webview.WebviewController.prepareForPageLoad("https://www.example.com/", true, 2);
56    AppStorage.setOrCreate("abilityWant", want);
57    console.log("EntryAbility onCreate done");
58  }
59}
60```
61
62## 预加载
63
64如果能够预测到Web组件将要加载的页面或者即将要跳转的页面。可以通过[prefetchPage()](../reference/apis-arkweb/js-apis-webview.md#prefetchpage10)来预加载即将要加载页面。
65
66预加载会提前下载页面所需的资源,包括主资源子资源,但不会执行网页JavaScript代码。预加载是WebviewController的实例方法,需要一个已经关联好Web组件的WebviewController实例。
67
68在下面的示例中,在onPageEnd的时候触发下一个要访问的页面的预加载。
69
70```ts
71// xxx.ets
72import { webview } from '@kit.ArkWeb';
73
74@Entry
75@Component
76struct WebComponent {
77  webviewController: webview.WebviewController = new webview.WebviewController();
78
79  build() {
80    Column() {
81      Web({ src: 'https://www.example.com/', controller: this.webviewController })
82        .onPageEnd(() => {
83          // 预加载https://www.iana.org/help/example-domains84          this.webviewController.prefetchPage('https://www.iana.org/help/example-domains');
85        })
86    }
87  }
88}
89```
90
91## 预获取post请求
92
93可以通过[prefetchResource()](../reference/apis-arkweb/js-apis-webview.md#prefetchresource12)预获取将要加载页面中的post请求。在页面加载结束时,可以通过[clearPrefetchedResource()](../reference/apis-arkweb/js-apis-webview.md#clearprefetchedresource12)清除后续不再使用的预获取资源缓存。
94
95  以下示例,在Web组件onAppear中,对要加载页面中的post请求进行预获取。在onPageEnd中,可以清除预获取的post请求缓存。
96
97```ts
98// xxx.ets
99import { webview } from '@kit.ArkWeb';
100
101@Entry
102@Component
103struct WebComponent {
104  webviewController: webview.WebviewController = new webview.WebviewController();
105
106  build() {
107    Column() {
108      Web({ src: "https://www.example.com/", controller: this.webviewController })
109        .onAppear(() => {
110          // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
111          webview.WebviewController.prefetchResource(
112            {
113              url: "https://www.example1.com/post?e=f&g=h",
114              method: "POST",
115              formData: "a=x&b=y",
116            },
117            [{
118              headerKey: "c",
119              headerValue: "z",
120            },],
121            "KeyX", 500);
122        })
123        .onPageEnd(() => {
124          // 清除后续不再使用的预获取资源缓存。
125          webview.WebviewController.clearPrefetchedResource(["KeyX",]);
126        })
127    }
128  }
129}
130```
131
132如果能够预测到Web组件将要加载页面或者即将要跳转页面中的post请求。可以通过[prefetchResource()](../reference/apis-arkweb/js-apis-webview.md#prefetchresource12)预获取即将要加载页面的post请求。
133
134  以下示例,在onPageEnd中,触发预获取一个要访问页面的post请求。
135
136```ts
137// xxx.ets
138import { webview } from '@kit.ArkWeb';
139
140@Entry
141@Component
142struct WebComponent {
143  webviewController: webview.WebviewController = new webview.WebviewController();
144
145  build() {
146    Column() {
147      Web({ src: 'https://www.example.com/', controller: this.webviewController })
148        .onPageEnd(() => {
149          // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
150          webview.WebviewController.prefetchResource(
151            {
152              url: "https://www.example1.com/post?e=f&g=h",
153              method: "POST",
154              formData: "a=x&b=y",
155            },
156            [{
157              headerKey: "c",
158              headerValue: "z",
159            },],
160            "KeyX", 500);
161        })
162    }
163  }
164}
165```
166
167也可以通过[initializeBrowserEngine()](../reference/apis-arkweb/js-apis-webview.md#initializewebengine)提前初始化内核,然后在初始化内核后调用[prefetchResource()](../reference/apis-arkweb/js-apis-webview.md#prefetchresource12)预获取将要加载页面中的post请求。这种方式适合提前预获取首页的post请求。
168
169  以下示例,在Ability的onCreate中,提前初始化Web内核并预获取首页的post请求。
170
171```ts
172// xxx.ets
173import { webview } from '@kit.ArkWeb';
174import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
175
176export default class EntryAbility extends UIAbility {
177  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
178    console.log("EntryAbility onCreate");
179    webview.WebviewController.initializeWebEngine();
180    // 预获取时,需要將"https://www.example1.com/post?e=f&g=h"替换成真实要访问的网站地址。
181    webview.WebviewController.prefetchResource(
182      {
183        url: "https://www.example1.com/post?e=f&g=h",
184        method: "POST",
185        formData: "a=x&b=y",
186      },
187      [{
188        headerKey: "c",
189        headerValue: "z",
190      },],
191      "KeyX", 500);
192    AppStorage.setOrCreate("abilityWant", want);
193    console.log("EntryAbility onCreate done");
194  }
195}
196```
197
198## 预编译生成编译缓存
199
200可以通过[precompileJavaScript()](../reference/apis-arkweb/js-apis-webview.md#precompilejavascript12)在页面加载前提前生成脚本文件的编译缓存。
201
202推荐配合动态组件使用,使用离线的Web组件用于生成字节码缓存,并在适当的时机加载业务用Web组件使用这些字节码缓存。下方是代码示例:
203
2041. 首先,在EntryAbility中将UIContext存到localStorage中。
205
206   ```ts
207   // EntryAbility.ets
208   import { UIAbility } from '@kit.AbilityKit';
209   import { window } from '@kit.ArkUI';
210
211   const localStorage: LocalStorage = new LocalStorage('uiContext');
212
213   export default class EntryAbility extends UIAbility {
214     storage: LocalStorage = localStorage;
215
216     onWindowStageCreate(windowStage: window.WindowStage) {
217       windowStage.loadContent('pages/Index', this.storage, (err, data) => {
218         if (err.code) {
219           return;
220         }
221
222         this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext());
223       });
224     }
225   }
226   ```
227
2282. 编写动态组件所需基础代码。
229
230   ```ts
231   // DynamicComponent.ets
232   import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
233
234   export interface BuilderData {
235     url: string;
236     controller: WebviewController;
237   }
238
239   const storage = LocalStorage.getShared();
240
241   export class NodeControllerImpl extends NodeController {
242     private rootNode: BuilderNode<BuilderData[]> | null = null;
243     private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
244
245     constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>) {
246       super();
247       this.wrappedBuilder = wrappedBuilder;
248     }
249
250     makeNode(): FrameNode | null {
251       if (this.rootNode != null) {
252         return this.rootNode.getFrameNode();
253       }
254       return null;
255     }
256
257     initWeb(url: string, controller: WebviewController) {
258       if(this.rootNode != null) {
259         return;
260       }
261
262       const uiContext: UIContext = storage.get<UIContext>("uiContext") as UIContext;
263       if (!uiContext) {
264         return;
265       }
266       this.rootNode = new BuilderNode(uiContext);
267       this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller });
268     }
269   }
270
271   export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
272     const baseNode = new NodeControllerImpl(wrappedBuilder);
273     baseNode.initWeb(data.url, data.controller);
274     return baseNode;
275   }
276   ```
277
2783. 编写用于生成字节码缓存的组件,本例中的本地Javascript资源内容通过文件读取接口读取rawfile目录下的本地文件。
279
280   ```ts
281   // PrecompileWebview.ets
282   import { BuilderData } from "./DynamicComponent";
283   import { Config, configs } from "./PrecompileConfig";
284
285   @Builder
286   function WebBuilder(data: BuilderData) {
287     Web({ src: data.url, controller: data.controller })
288       .onControllerAttached(() => {
289         precompile(data.controller, configs);
290       })
291       .fileAccess(true)
292   }
293
294   export const precompileWebview = wrapBuilder<BuilderData[]>(WebBuilder);
295
296   export const precompile = async (controller: WebviewController, configs: Array<Config>) => {
297     for (const config of configs) {
298       let content = await readRawFile(config.localPath);
299
300       try {
301         controller.precompileJavaScript(config.url, content, config.options)
302           .then(errCode => {
303             console.error("precompile successfully! " + errCode);
304           }).catch((errCode: number) => {
305             console.error("precompile failed. " + errCode);
306         });
307       } catch (err) {
308         console.error("precompile failed. " + err.code + " " + err.message);
309       }
310     }
311   }
312
313   async function readRawFile(path: string) {
314     try {
315       return await getContext().resourceManager.getRawFileContent(path);;
316     } catch (err) {
317       return new Uint8Array(0);
318     }
319   }
320   ```
321
322JavaScript资源的获取方式也可通过[网络请求](../reference/apis-network-kit/js-apis-http.md)的方式获取,但此方法获取到的http响应头非标准HTTP响应头格式,需额外将响应头转换成标准HTTP响应头格式后使用。如通过网络请求获取到的响应头是e-tag,则需要将其转换成E-Tag后使用。
323
3244. 编写业务用组件代码。
325
326   ```ts
327   // BusinessWebview.ets
328   import { BuilderData } from "./DynamicComponent";
329
330   @Builder
331   function WebBuilder(data: BuilderData) {
332     // 此处组件可根据业务需要自行扩展
333     Web({ src: data.url, controller: data.controller })
334       .cacheMode(CacheMode.Default)
335   }
336
337   export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder);
338   ```
339
3405. 编写资源配置信息。
341
342   ```ts
343   // PrecompileConfig.ets
344   import { webview } from '@kit.ArkWeb'
345
346   export interface Config {
347     url:  string,
348     localPath: string, // 本地资源路径
349     options: webview.CacheOptions
350   }
351
352   export let configs: Array<Config> = [
353     {
354       url: "https://www.example.com/example.js",
355       localPath: "example.js",
356       options: {
357         responseHeaders: [
358           { headerKey: "E-Tag", headerValue: "aWO42N9P9dG/5xqYQCxsx+vDOoU="},
359           { headerKey: "Last-Modified", headerValue: "Wed, 21 Mar 2024 10:38:41 GMT"}
360         ]
361       }
362     }
363   ]
364   ```
365
3666. 在页面中使用。
367
368   ```ts
369   // Index.ets
370   import { webview } from '@kit.ArkWeb';
371   import { NodeController } from '@kit.ArkUI';
372   import { createNode } from "./DynamicComponent"
373   import { precompileWebview } from "./PrecompileWebview"
374   import { businessWebview } from "./BusinessWebview"
375
376   @Entry
377   @Component
378   struct Index {
379     @State precompileNode: NodeController | undefined = undefined;
380     precompileController: webview.WebviewController = new webview.WebviewController();
381
382     @State businessNode: NodeController | undefined = undefined;
383     businessController: webview.WebviewController = new webview.WebviewController();
384
385     aboutToAppear(): void {
386       // 初始化用于注入本地资源的Web组件
387       this.precompileNode = createNode(precompileWebview,
388         { url: "https://www.example.com/empty.html", controller: this.precompileController});
389     }
390
391     build() {
392       Column() {
393         // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
394         Button("加载页面")
395           .onClick(() => {
396             this.businessNode = createNode(businessWebview, {
397               url:  "https://www.example.com/business.html",
398               controller: this.businessController
399             });
400           })
401         // 用于业务的Web组件
402         NodeContainer(this.businessNode);
403       }
404     }
405   }
406   ```
407
408当需要更新本地已经生成的编译字节码时,修改cacheOptions参数中responseHeaders中的E-Tag或Last-Modified响应头对应的值,再次调用接口即可。
409
410## 离线资源免拦截注入
411可以通过[injectOfflineResources()](../reference/apis-arkweb/js-apis-webview.md#injectofflineresources12)在页面加载前提前将图片、样式表或脚本资源注入到应用的内存缓存中。
412
413推荐配合动态组件使用,使用离线的Web组件用于将资源注入到内核的内存缓存中,并在适当的时机加载业务用Web组件使用这些资源。下方是代码示例:
414
4151. 首先,在EntryAbility中将UIContext存到localStorage中。
416
417   ```ts
418   // EntryAbility.ets
419   import { UIAbility } from '@kit.AbilityKit';
420   import { window } from '@kit.ArkUI';
421
422   const localStorage: LocalStorage = new LocalStorage('uiContext');
423
424   export default class EntryAbility extends UIAbility {
425     storage: LocalStorage = localStorage;
426
427     onWindowStageCreate(windowStage: window.WindowStage) {
428       windowStage.loadContent('pages/Index', this.storage, (err, data) => {
429         if (err.code) {
430           return;
431         }
432
433         this.storage.setOrCreate<UIContext>("uiContext", windowStage.getMainWindowSync().getUIContext());
434       });
435     }
436   }
437   ```
438
4392. 编写动态组件所需基础代码。
440
441   ```ts
442   // DynamicComponent.ets
443   import { NodeController, BuilderNode, FrameNode, UIContext } from '@kit.ArkUI';
444
445   export interface BuilderData {
446     url: string;
447     controller: WebviewController;
448   }
449
450   const storage = LocalStorage.getShared();
451
452   export class NodeControllerImpl extends NodeController {
453     private rootNode: BuilderNode<BuilderData[]> | null = null;
454     private wrappedBuilder: WrappedBuilder<BuilderData[]> | null = null;
455
456     constructor(wrappedBuilder: WrappedBuilder<BuilderData[]>) {
457       super();
458       this.wrappedBuilder = wrappedBuilder;
459     }
460
461     makeNode(): FrameNode | null {
462       if (this.rootNode != null) {
463         return this.rootNode.getFrameNode();
464       }
465       return null;
466     }
467
468     initWeb(url: string, controller: WebviewController) {
469       if(this.rootNode != null) {
470         return;
471       }
472
473       const uiContext: UIContext = storage.get<UIContext>("uiContext") as UIContext;
474       if (!uiContext) {
475         return;
476       }
477       this.rootNode = new BuilderNode(uiContext);
478       this.rootNode.build(this.wrappedBuilder, { url: url, controller: controller });
479     }
480   }
481
482   export const createNode = (wrappedBuilder: WrappedBuilder<BuilderData[]>, data: BuilderData) => {
483     const baseNode = new NodeControllerImpl(wrappedBuilder);
484     baseNode.initWeb(data.url, data.controller);
485     return baseNode;
486   }
487   ```
488
4893. 编写用于注入资源的组件代码,本例中的本地资源内容通过文件读取接口读取rawfile目录下的本地文件。
490
491   <!--code_no_check-->
492   ```ts
493   // InjectWebview.ets
494   import { webview } from '@kit.ArkWeb';
495   import { resourceConfigs } from "./Resource";
496   import { BuilderData } from "./DynamicComponent";
497
498   @Builder
499   function WebBuilder(data: BuilderData) {
500     Web({ src: data.url, controller: data.controller })
501       .onControllerAttached(async () => {
502         try {
503           data.controller.injectOfflineResources(await getData ());
504         } catch (err) {
505           console.error("error: " + err.code + " " + err.message);
506         }
507       })
508       .fileAccess(true)
509   }
510
511   export const injectWebview = wrapBuilder<BuilderData[]>(WebBuilder);
512
513   export async function getData() {
514     const resourceMapArr: Array<webview.OfflineResourceMap> = [];
515
516     // 读取配置,从rawfile目录中读取文件内容
517     for (let config of resourceConfigs) {
518       let buf: Uint8Array = new Uint8Array(0);
519       if (config.localPath) {
520         buf = await readRawFile(config.localPath);
521       }
522
523       resourceMapArr.push({
524         urlList: config.urlList,
525         resource: buf,
526         responseHeaders: config.responseHeaders,
527         type: config.type,
528       })
529     }
530
531     return resourceMapArr;
532   }
533
534   export async function readRawFile(url: string) {
535     try {
536       return await getContext().resourceManager.getRawFileContent(url);
537     } catch (err) {
538       return new Uint8Array(0);
539     }
540   }
541   ```
542
5434. 编写业务用组件代码。
544
545   <!--code_no_check-->
546   ```ts
547   // BusinessWebview.ets
548   import { BuilderData } from "./DynamicComponent";
549
550   @Builder
551   function WebBuilder(data: BuilderData) {
552     // 此处组件可根据业务需要自行扩展
553     Web({ src: data.url, controller: data.controller })
554       .cacheMode(CacheMode.Default)
555   }
556
557   export const businessWebview = wrapBuilder<BuilderData[]>(WebBuilder);
558   ```
559
5605. 编写资源配置信息。
561
562   ```ts
563   // Resource.ets
564   import { webview } from '@kit.ArkWeb';
565
566   export interface ResourceConfig {
567     urlList: Array<string>,
568     type: webview.OfflineResourceType,
569     responseHeaders: Array<Header>,
570     localPath: string, // 本地资源存放在rawfile目录下的路径
571   }
572
573   export const resourceConfigs: Array<ResourceConfig> = [
574     {
575       localPath: "example.png",
576       urlList: [
577         "https://www.example.com/",
578         "https://www.example.com/path1/example.png",
579         "https://www.example.com/path2/example.png",
580       ],
581       type: webview.OfflineResourceType.IMAGE,
582       responseHeaders: [
583         { headerKey: "Cache-Control", headerValue: "max-age=1000" },
584         { headerKey: "Content-Type", headerValue: "image/png" },
585       ]
586     },
587     {
588       localPath: "example.js",
589       urlList: [ // 仅提供一个url,这个url既作为资源的源,也作为资源的网络请求地址
590         "https://www.example.com/example.js",
591       ],
592       type: webview.OfflineResourceType.CLASSIC_JS,
593       responseHeaders: [
594         // 以<script crossorigin="anoymous" />方式使用,提供额外的响应头
595         { headerKey: "Cross-Origin", headerValue:"anonymous" }
596       ]
597     },
598   ];
599   ```
600
6016. 在页面中使用。
602   ```ts
603   // Index.ets
604   import { webview } from '@kit.ArkWeb';
605   import { NodeController } from '@kit.ArkUI';
606   import { createNode } from "./DynamicComponent"
607   import { injectWebview } from "./InjectWebview"
608   import { businessWebview } from "./BusinessWebview"
609
610   @Entry
611   @Component
612   struct Index {
613     @State injectNode: NodeController | undefined = undefined;
614     injectController: webview.WebviewController = new webview.WebviewController();
615
616     @State businessNode: NodeController | undefined = undefined;
617     businessController: webview.WebviewController = new webview.WebviewController();
618
619     aboutToAppear(): void {
620       // 初始化用于注入本地资源的Web组件, 提供一个空的html页面作为url即可
621       this.injectNode = createNode(injectWebview,
622           { url: "https://www.example.com/empty.html", controller: this.injectController});
623     }
624
625     build() {
626       Column() {
627         // 在适当的时机加载业务用Web组件,本例以Button点击触发为例
628         Button("加载页面")
629           .onClick(() => {
630             this.businessNode = createNode(businessWebview, {
631               url: "https://www.example.com/business.html",
632               controller: this.businessController
633             });
634           })
635         // 用于业务的Web组件
636         NodeContainer(this.businessNode);
637       }
638     }
639   }
640   ```
641
6427. 加载的HTML网页示例。
643
644   ```HTML
645   <!DOCTYPE html>
646   <html lang="en">
647   <head></head>
648   <body>
649     <img src="https://www.example.com/path1/request.png" />
650     <img src="https://www.example.com/path2/request.png" />
651     <script src="https://www.example.com/example.js" crossorigin="anonymous"></script>
652   </body>
653   </html>
654   ```
655