HarmonyOS ArkWeb 系列之组件生命周期全解:从加载到渲染的每个关键节点
文章目录先把生命周期的顺序理清楚onControllerAttached最早能操作的时机onPageBegin 和 onPageEndloading 动画的起止点onFirstContentfulPaint性能监控的关键指标onLoadInterceptURL 级别的拦截onInterceptRequest响应级别的替换onRenderExited渲染进程崩溃了怎么办完整的生命周期示例各回调触发时机速查表写在最后你有没有遇到这种场景想在网页加载完之后执行一段 JS却发现注入太早 DOM 还没就绪或者想在页面开始加载时显示 loading 动画结果时机不对这些问题的根源都是没搞清楚 Web 组件的加载生命周期。先把生命周期的顺序理清楚Web 组件从创建到内容可见大致经过这几个阶段onControllerAttached最早能操作的时机这是 Web 组件控制器和内核绑定完成后的第一个回调。很多初始化操作应该在这里做Web({src:https://www.baidu.com,controller:this.controller}).onControllerAttached((){// ✅ 推荐在此做初始化// 1. 动态设置用户代理this.controller.setCustomUserAgent(MyApp/1.0 HarmonyOS);// 2. 注入 JS 对象让网页可以调用原生方法this.controller.registerJavaScriptProxy({/* ... */},NativeBridge,[methodName]);// 3. 动态 loadUrl// this.controller.loadUrl(https://another.com);console.info(控制器已绑定可以开始初始化了);})注意在onControllerAttached之前调用控制器方法比如在组件创建时就调loadUrl会抛异常。onPageBegin 和 onPageEndloading 动画的起止点这两个是最常用的。import{webview}fromkit.ArkWeb;EntryComponentstruct WebLifecycleDemo{controller:webview.WebviewControllernewwebview.WebviewController();StateisLoading:booleanfalse;StateloadProgress:number0;build(){Column(){// loading 指示器仅在加载时显示if(this.isLoading){Row(){LoadingProgress().width(24).height(24)Text(加载中${this.loadProgress}%).fontSize(14).margin({left:8})}.padding(8).width(100%)}// 进度条加载时显示Progress({value:this.loadProgress,total:100,type:ProgressType.Linear}).width(100%).opacity(this.isLoading?1:0)Web({src:https://www.baidu.com,controller:this.controller}).onPageBegin((event){// 页面开始加载显示 loadingthis.isLoadingtrue;this.loadProgress0;if(event){console.info(开始加载,event.url);}}).onProgressChange((event){// 进度更新0~100if(event){this.loadProgressevent.newProgress;}}).onPageEnd((event){// 页面加载完成隐藏 loading执行 JSthis.isLoadingfalse;if(event){console.info(加载完成,event.url);// ✅ 推荐在此执行 JSDOM 已就绪this.controller.runJavaScript(document.title).then((title){console.info(页面标题,title);});}}).width(100%).layoutWeight(1)}.width(100%).height(100%)}}onFirstContentfulPaint性能监控的关键指标FCPFirst Contentful Paint是衡量网页性能的核心指标之一表示用户看到第一帧有意义内容的时间。Web({src:https://www.baidu.com,controller:this.controller}).onFirstContentfulPaint(event{if(event){console.info(FCP 数据,导航开始时间tick,event.navigationStartTick,首帧绘制耗时ms,event.firstContentfulPaintMs);// 可以上报到性能监控系统// reportPerformance(FCP, event.firstContentfulPaintMs);}})navigationStartTick是单调递增的时间戳firstContentfulPaintMs是从导航开始到首帧绘制的毫秒数。onLoadInterceptURL 级别的拦截在页面真正加载之前可以检查 URL 决定是否允许加载Web({src:https://www.baidu.com,controller:this.controller}).onLoadIntercept((event){if(event){consturlevent.data.getRequestUrl();constisMainFrameevent.data.isMainFrame();constisRedirectevent.data.isRedirect();console.info(拦截检查,url,主框架,isMainFrame);// 过滤掉某些域名if(url.includes(ads.example.com)){returntrue;// 返回 true 阻止加载}}returnfalse;// 返回 false 允许加载})onInterceptRequest响应级别的替换这个比onLoadIntercept更强大——不仅能阻止还能替换响应内容Web({src:https://www.example.com,controller:this.controller}).onInterceptRequest((event){if(event){consturlevent.request.getRequestUrl();console.info(请求拦截,url);// 把某个 URL 的响应替换为本地内容if(urlhttps://www.example.com/offline.html){constresponsenewWebResourceResponse();response.setResponseData(h1离线模式/h1);response.setResponseEncoding(utf-8);response.setResponseMimeType(text/html);response.setResponseCode(200);response.setReasonMessage(OK);returnresponse;}}returnnull;// 返回 null 不拦截按原来方式加载})onRenderExited渲染进程崩溃了怎么办Web 渲染是在独立进程里跑的极端情况下这个进程会退出OOM、崩溃等。这时候onRenderExited会触发Web({src:https://www.example.com,controller:this.controller}).onRenderExited((event){if(event){console.error(渲染进程退出原因,event.renderExitReason);// 可以提示用户并提供重新加载按钮this.showReloadTiptrue;}})renderExitReason的可能值包括RenderExitReason.ProcessAbnormalTermination进程异常终止RenderExitReason.ProcessWasKilled进程被系统杀死RenderExitReason.ProcessCrashed进程崩溃完整的生命周期示例把上面所有回调整合到一起import{webview}fromkit.ArkWeb;import{BusinessError}fromkit.BasicServicesKit;EntryComponentstruct WebFullLifecycleDemo{controller:webview.WebviewControllernewwebview.WebviewController();responseWeb:WebResourceResponsenewWebResourceResponse();aboutToAppear():void{// 开启 Web 调试开发阶段使用生产环境关闭try{webview.WebviewController.setWebDebuggingAccess(true);}catch(error){console.error(开启调试失败:${(errorasBusinessError).message});}}build(){Column(){Web({src:https://www.example.com,controller:this.controller}).onControllerAttached((){console.info(① 控制器绑定完成开始初始化);}).onLoadIntercept((event){if(event){console.info(② URL 拦截检查,event.data.getRequestUrl());}returnfalse;// 允许加载}).onInterceptRequest((event){if(event){console.info(③ 请求拦截,event.request.getRequestUrl());}returnnull;// 不替换响应}).onPageBegin((event){if(event){console.info(④ 页面开始加载,event.url);}}).onProgressChange((event){if(event){console.info(⑤ 加载进度,event.newProgress%);}}).onFirstContentfulPaint(event{if(event){console.info(⑥ 首帧绘制耗时,event.firstContentfulPaintMs,ms);}}).onPageEnd((event){if(event){console.info(⑦ 页面加载完成,event.url);// 现在可以安全地执行 JS}}).onPageVisible((event){console.info(⑧ 页面内容可见,event.url);}).onRenderExited((event){if(event){console.error(⑨ 渲染进程退出,event.renderExitReason);}}).onDisAppear((){this.getUIContext().getPromptAction().showToast({message:Web 组件已隐藏,duration:2000});})}}}各回调触发时机速查表回调触发时机常见用途onControllerAttached控制器绑定完成初始化、设置用户代理、注入对象onLoadInterceptURL 即将加载前拦截特定 URLonInterceptRequest资源请求发出前替换响应内容离线缓存onPageBegin页面开始加载显示 loadingonProgressChange加载进度变化更新进度条onFirstContentfulPaint首帧内容绘制性能监控onPageEnd页面加载完成隐藏 loading执行 JSonPageVisible页面内容可见统计页面展示onRenderExited渲染进程退出显示重新加载提示onDisAppearWeb 组件从视图树移除清理资源写在最后生命周期这块掌握了很多时机问题就迎刃而解了。记住核心规则初始化放onControllerAttachedJS 脚本放onPageEndloading 动画放onPageBegin开、onPageEnd关。