• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Web组件的生命周期
2
3## 概述
4
5开发者可以使用Web组件加载本地或者在线网页。
6
7Web组件提供了丰富的组件生命周期回调接口,通过这些回调接口,开发者可以感知Web组件的生命周期状态变化,进行相关的业务处理。
8
9Web组件的状态主要包括:Controller绑定到Web组件、网页加载开始、网页加载进度、网页加载结束、页面即将可见等。
10
11**图1**  Web组件网页正常加载过程中的回调事件
12
13![web-event-sequence](figures/web-event-sequence.png)
14
15## Web组件网页加载的状态说明
16
17- [aboutToAppear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear)函数:在创建自定义组件的新实例后,在执行其build函数前执行。一般建议在此设置WebDebug调试模式[setWebDebuggingAccess](../reference/apis-arkweb/js-apis-webview.md#setwebdebuggingaccess)、设置Web内核自定义协议URL的跨域请求与fetch请求的权限[customizeSchemes](../reference/apis-arkweb/js-apis-webview.md#customizeschemes)、设置Cookie([configCookie](../reference/apis-arkweb/js-apis-webview.md#configcookie11-1))等。
18
19- [onControllerAttached](../reference/apis-arkweb/ts-basic-components-web.md#oncontrollerattached10)事件:当Controller成功绑定到Web组件时触发该回调,且禁止在该事件回调前调用Web组件相关的接口,否则会抛出js-error异常。推荐在此事件中注入JS对象[registerJavaScriptProxy](../reference/apis-arkweb/js-apis-webview.md#registerjavascriptproxy)、设置自定义用户代理[setCustomUserAgent](../reference/apis-arkweb/js-apis-webview.md#setcustomuseragent10),可以在回调中使用[loadUrl](../reference/apis-arkweb/js-apis-webview.md#loadurl),[getWebId](../reference/apis-arkweb/js-apis-webview.md#getwebid)等操作网页不相关的接口。但因该回调调用时网页还未加载,因此无法在回调中使用有关操作网页的接口,例如[zoomIn](../reference/apis-arkweb/js-apis-webview.md#zoomin)、[zoomOut](../reference/apis-arkweb/js-apis-webview.md#zoomout)等。
20
21- [onLoadIntercept](../reference/apis-arkweb/ts-basic-components-web.md#onloadintercept10)事件:当Web组件加载url之前触发该回调,用于判断是否阻止此次访问。默认允许加载。
22
23- [onOverrideUrlLoading](../reference/apis-arkweb/ts-basic-components-web.md#onoverrideurlloading12)事件:当URL将要加载到当前Web中时,让宿主应用程序有机会获得控制权,回调函数返回true将导致当前Web中止加载URL,而返回false则会导致Web继续照常加载URL。onLoadIntercept接口和onOverrideUrlLoading接口行为不一致,触发时机也不同,所以在应用场景上存在一定区别。主要是在LoadUrl和iframe(HTML标签,表示HTML内联框架元素,用于将另一个页面嵌入到当前页面中)加载时,onLoadIntercept事件会正常回调到,但onOverrideUrlLoading事件在LoadUrl加载时不会触发,在iframe加载HTTP(s)协议或about:blank时也不会触发。详细介绍请见[onLoadIntercept](../reference/apis-arkweb/ts-basic-components-web.md#onloadintercept10)和[onOverrideUrlLoading](../reference/apis-arkweb/ts-basic-components-web.md#onoverrideurlloading12)的说明。
24
25- [onInterceptRequest](../reference/apis-arkweb/ts-basic-components-web.md#oninterceptrequest9)事件:当Web组件加载url之前触发该回调,用于拦截url并返回响应数据。
26
27- [onPageBegin](../reference/apis-arkweb/ts-basic-components-web.md#onpagebegin)事件:网页开始加载时触发该回调,且只在主frame(表示一个HTML元素,用于展示HTML页面的HTML元素)触发。如果是iframe或者frameset(用于包含frame的HTML标签)的内容加载时则不会触发此回调。多frame页面有可能同时开始加载,即使主frame已经加载结束,子frame也有可能才开始或者继续加载中。同一页面导航(片段、历史状态等)或者在提交前失败、被取消的导航等也不会触发该回调。
28
29- [onProgressChange](../reference/apis-arkweb/ts-basic-components-web.md#onprogresschange)事件:告知开发者当前页面加载的进度。多frame页面或者子frame有可能还在继续加载而主frame可能已经加载结束,所以在[onPageEnd](../reference/apis-arkweb/ts-basic-components-web.md#onpageend)事件后依然有可能收到该事件。
30
31- [onPageEnd](../reference/apis-arkweb/ts-basic-components-web.md#onpageend)事件:网页加载完成时触发该回调,且只在主frame触发。多frame页面有可能同时开始加载,即使主frame已经加载结束,子frame也有可能才开始或者继续加载中。同一页面导航(片段、历史状态等)或者在提交前失败、被取消的导航等也不会触发该回调。推荐在此回调中执行JavaScript脚本[loadUrl](../reference/apis-arkweb/js-apis-webview.md#loadurl)等。需要注意的是收到该回调并不能保证Web绘制的下一帧将反映此时DOM的状态。
32
33- [onPageVisible](../reference/apis-arkweb/ts-basic-components-web.md#onpagevisible9)事件:Web回调事件。渲染流程中当HTTP响应的主体开始加载,新页面即将可见时触发该回调。此时文档加载还处于早期,因此链接的资源比如在线CSS、在线图片等可能尚不可用。
34
35- [onRenderExited](../reference/apis-arkweb/ts-basic-components-web.md#onrenderexited9)事件:应用渲染进程异常退出时触发该回调,可以在此回调中进行系统资源的释放、数据的保存等操作。如果应用希望异常恢复,需要调用[loadUrl](../reference/apis-arkweb/js-apis-webview.md#loadurl)接口重新加载页面。详细用法参考[应用如何避免Web组件渲染子进程异常退出导致的页面卡死问题](#应用如何避免web组件渲染子进程异常退出导致的页面卡死问题)。
36
37- [onDisAppear](../reference/apis-arkui/arkui-ts/ts-universal-events-show-hide.md#ondisappear)事件:组件卸载消失时触发此回调。该事件为通用事件,指组件从组件树上卸载时触发的事件。
38
39应用侧代码。
40
41  ```ts
42  // xxx.ets
43  import { webview } from '@kit.ArkWeb';
44  import { BusinessError } from '@kit.BasicServicesKit';
45
46  @Entry
47  @Component
48  struct WebComponent {
49    controller: webview.WebviewController = new webview.WebviewController();
50    responseWeb: WebResourceResponse = new WebResourceResponse();
51    heads: Header[] = new Array();
52    @State webData: string = "<!DOCTYPE html>\n" +
53      "<html>\n" +
54      "<head>\n" +
55      "<title>intercept test</title>\n" +
56      "</head>\n" +
57      "<body>\n" +
58      "<h1>intercept test</h1>\n" +
59      "</body>\n" +
60      "</html>";
61
62    aboutToAppear(): void {
63      try {
64        webview.WebviewController.setWebDebuggingAccess(true);
65      } catch (error) {
66        console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
67      }
68    }
69
70    build() {
71      Column() {
72        Web({ src: $rawfile('index.html'), controller: this.controller })
73          .onControllerAttached(() => {
74            // 推荐在此loadUrl、设置自定义用户代理、注入JS对象等
75            console.log('onControllerAttached execute')
76          })
77          .onLoadIntercept((event) => {
78            if (event) {
79              console.log('onLoadIntercept url:' + event.data.getRequestUrl())
80              console.log('url:' + event.data.getRequestUrl())
81              console.log('isMainFrame:' + event.data.isMainFrame())
82              console.log('isRedirect:' + event.data.isRedirect())
83              console.log('isRequestGesture:' + event.data.isRequestGesture())
84            }
85            // 返回true表示阻止此次加载,否则允许此次加载
86            return false;
87          })
88          .onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => {
89            if (webResourceRequest && webResourceRequest.getRequestUrl() == "about:blank") {
90              return true;
91            }
92            return false;
93          })
94          .onInterceptRequest((event) => {
95            if (event) {
96              console.log('url:' + event.request.getRequestUrl());
97            }
98            let head1: Header = {
99              headerKey: "Connection",
100              headerValue: "keep-alive"
101            }
102            let head2: Header = {
103              headerKey: "Cache-Control",
104              headerValue: "no-cache"
105            }
106            // 将新元素追加到数组的末尾,并返回数组的新长度。
107            let length = this.heads.push(head1);
108            length = this.heads.push(head2);
109            console.log('The response header result length is :' + length);
110            this.responseWeb.setResponseHeader(this.heads);
111            this.responseWeb.setResponseData(this.webData);
112            this.responseWeb.setResponseEncoding('utf-8');
113            this.responseWeb.setResponseMimeType('text/html');
114            this.responseWeb.setResponseCode(200);
115            this.responseWeb.setReasonMessage('OK');
116            // 返回响应数据则按照响应数据加载,无响应数据则返回null表示按照原来的方式加载
117            return this.responseWeb;
118          })
119          .onPageBegin((event) => {
120            if (event) {
121              console.log('onPageBegin url:' + event.url);
122            }
123          })
124          .onFirstContentfulPaint(event => {
125            if (event) {
126              console.log("onFirstContentfulPaint:" + "[navigationStartTick]:" +
127              event.navigationStartTick + ", [firstContentfulPaintMs]:" +
128              event.firstContentfulPaintMs);
129            }
130          })
131          .onProgressChange((event) => {
132            if (event) {
133              console.log('newProgress:' + event.newProgress);
134            }
135          })
136          .onPageEnd((event) => {
137            // 推荐在此事件中执行JavaScript脚本
138            if (event) {
139              console.log('onPageEnd url:' + event.url);
140            }
141          })
142          .onPageVisible((event) => {
143            console.log('onPageVisible url:' + event.url);
144          })
145          .onRenderExited((event) => {
146            if (event) {
147              console.log('onRenderExited reason:' + event.renderExitReason);
148            }
149          })
150          .onDisAppear(() => {
151            this.getUIContext().getPromptAction().showToast({
152              message: 'The web is hidden',
153              duration: 2000
154            })
155          })
156      }
157    }
158  }
159  ```
160
161前端index.html162
163  ```html
164  <!-- index.html -->
165  <!DOCTYPE html>
166  <html>
167  <head>
168    <meta charset="UTF-8">
169  </head>
170  <body>
171  <h1>Hello, ArkWeb</h1>
172  </body>
173  </html>
174  ```
175
176## Web组件网页加载的性能指标
177
178网页加载过程中需要关注一些重要的性能指标。例如,FCP(First Contentful Paint)首次内容绘制,FMP(First Meaningful Paint)首次有效绘制,LCP(Largest Contentful Paint)最大内容绘制等。Web组件提供了如下接口来通知开发者。
179
180- [onFirstContentfulPaint](../reference/apis-arkweb/ts-basic-components-web.md#onfirstcontentfulpaint10)事件:网页首次内容绘制的回调函数。首次绘制文本、图像、非空白Canvas或者SVG的时间点。
181
182- [onFirstMeaningfulPaint](../reference/apis-arkweb/ts-basic-components-web.md#onfirstmeaningfulpaint12)事件:网页绘制页面主要内容的回调函数。首次绘制页面主要内容的时间点。
183
184- [onLargestContentfulPaint](../reference/apis-arkweb/ts-basic-components-web.md#onlargestcontentfulpaint12)事件:网页绘制页面最大内容的回调函数。可视区域内容最大的可见元素开始出现在页面上的时间点。
185
186## 应用如何避免Web组件渲染子进程异常退出导致的页面卡死问题
187
188ArkWeb(方舟Web)是一个Web组件平台,旨在为应用程序提供展示Web页面内容的功能,并向开发者提供一系列的能力,如页面加载、交互和调试等功能。使用ArkWeb相关应用时,可能因各种原因(例如前端偶现异常导致ArkWeb渲染子进程崩溃,或是打开的应用较多,系统资源紧张导致后台ArkWeb渲染子进程被终止)而出现页面卡死的问题,这时需要重新打开页面或重启应用来解决。
189
190在ArkWeb渲染子进程异常退出导致页面卡死后,应用可通过监听[onRenderExited](../reference/apis-arkweb/ts-basic-components-web.md#onrenderexited9)事件来获取具体的退出原因[RenderExitReason](../reference/apis-arkweb/ts-basic-components-web.md#renderexitreason9枚举说明),并在异常回调中根据退出的具体原因,执行相应的异常处理。
191
192**开发实践案例**
193```
194import { webview } from '@kit.ArkWeb'
195@Entry
196@Component
197struct WebComponent {
198  needReloadWhenVisible: boolean = false   // Web组件不可见时render退出后阻止重新加载页面,在可见时重新加载页面。
199  webIsVisible: boolean = false            // 判断Web组件是否可见。
200
201  // 此处是将子进程异常崩溃和其它异常原因做了区分,应用开发者可根据实际业务特点,细化对应异常的处理策略。
202  renderReloadMaxForCrashed: number = 5    // 设置因为异常崩溃后重新加载的最大重试次数,应用可根据业务特点,自行设置试错上限。
203  renderReloadCountForCrashed: number = 0  // 异常崩溃后重新加载的次数。
204  renderReloadMaxForOthers: number = 10    // 设置因为其它异常原因退出的最大重试次数,应用可根据业务特点,自行设置试错上限。
205  renderReloadCountForOthers: number = 0   // 其它异常原因退出后重新加载的次数。
206
207  // 创建Web组件。
208  controller: webview.WebviewController = new webview.WebviewController()
209
210  // 指定加载的页面。
211  url: string = "www.example.com"
212  build() {
213    Column() {
214      Web({ src: this.url, controller: this.controller })
215        .onVisibleAreaChange([0, 1.0], (isVisible) => {
216          this.webIsVisible = isVisible
217          if (isVisible && this.needReloadWhenVisible) { // Web组件可见时重新加载页面。
218            this.needReloadWhenVisible = false
219            this.controller.loadUrl(this.url)
220          }
221        })
222        // 应用监听渲染子进程异常退出回调,并进行异常处理。
223        .onRenderExited((event) => {
224          if (!event) {
225            return
226          }
227          if (event.renderExitReason == RenderExitReason.ProcessCrashed) {
228            if (this.renderReloadCountForCrashed >= this.renderReloadMaxForCrashed) {
229              // 设置重试次数上限保护,避免必现问题导致页面被循环加载。
230              return
231            }
232            console.log('renderReloadCountForCrashed: ' + this.renderReloadCountForCrashed)
233            this.renderReloadCountForCrashed++
234          } else {
235            if (this.renderReloadCountForOthers >= this.renderReloadMaxForOthers) {
236              // 设置重试次数上限保护, 避免必现问题导致页面被循环加载。
237              return
238            }
239            console.log('renderReloadCountForOthers: ' + this.renderReloadCountForOthers)
240            this.renderReloadCountForOthers++
241          }
242          if (this.webIsVisible) {
243            // Web组件可见则立即重新加载。
244            this.controller.loadUrl(this.url)
245            return
246          }
247          // Web组件不可见时不立即重新加载。
248          this.needReloadWhenVisible = true
249        })
250    }
251  }
252}
253```
254