文章目录
-
-
- 先把生命周期的顺序理清楚
- onControllerAttached:最早能操作的时机
- onPageBegin 和 onPageEnd:loading 动画的起止点
- onFirstContentfulPaint:性能监控的关键指标
- onLoadIntercept:URL 级别的拦截
- 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 和 onPageEnd:loading 动画的起止点
这两个是最常用的。
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebLifecycleDemo {
controller: webview.WebviewController = new webview.WebviewController();
@State isLoading: boolean = false;
@State loadProgress: number = 0;
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) => {
// 页面开始加载:显示 loading
this.isLoading = true;
this.loadProgress = 0;
if (event) {
console.info('开始加载:', event.url);
}
})
.onProgressChange((event) => {
// 进度更新:0~100
if (event) {
this.loadProgress = event.newProgress;
}
})
.onPageEnd((event) => {
// 页面加载完成:隐藏 loading,执行 JS
this.isLoading = false;
if (event) {
console.info('加载完成:', event.url);
// ✅ 推荐在此执行 JS,DOM 已就绪
this.controller.runJavaScript('document.title')
.then((title) => {
console.info('页面标题:', title);
});
}
})
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
onFirstContentfulPaint:性能监控的关键指标
FCP(First 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 是从导航开始到首帧绘制的毫秒数。
onLoadIntercept:URL 级别的拦截
在页面真正加载之前,可以检查 URL 决定是否允许加载:
Web({ src: 'https://www.baidu.com', controller: this.controller })
.onLoadIntercept((event) => {
if (event) {
const url = event.data.getRequestUrl();
const isMainFrame = event.data.isMainFrame();
const isRedirect = event.data.isRedirect();
console.info('拦截检查:', url, '主框架:', isMainFrame);
// 过滤掉某些域名
if (url.includes('ads.example.com')) {
return true; // 返回 true = 阻止加载
}
}
return false; // 返回 false = 允许加载
})
onInterceptRequest:响应级别的替换
这个比 onLoadIntercept 更强大——不仅能阻止,还能替换响应内容:
Web({ src: 'https://www.example.com', controller: this.controller })
.onInterceptRequest((event) => {
if (event) {
const url = event.request.getRequestUrl();
console.info('请求拦截:', url);
// 把某个 URL 的响应替换为本地内容
if (url === 'https://www.example.com/offline.html') {
const response = new WebResourceResponse();
response.setResponseData('<h1>离线模式</h1>');
response.setResponseEncoding('utf-8');
response.setResponseMimeType('text/html');
response.setResponseCode(200);
response.setReasonMessage('OK');
return response;
}
}
return null; // 返回 null = 不拦截,按原来方式加载
})
onRenderExited:渲染进程崩溃了怎么办
Web 渲染是在独立进程里跑的,极端情况下这个进程会退出(OOM、崩溃等)。这时候 onRenderExited 会触发:
Web({ src: 'https://www.example.com', controller: this.controller })
.onRenderExited((event) => {
if (event) {
console.error('渲染进程退出,原因:', event.renderExitReason);
// 可以提示用户并提供重新加载按钮
this.showReloadTip = true;
}
})
renderExitReason 的可能值包括:
- RenderExitReason.ProcessAbnormalTermination:进程异常终止
- RenderExitReason.ProcessWasKilled:进程被系统杀死
- RenderExitReason.ProcessCrashed:进程崩溃
完整的生命周期示例
把上面所有回调整合到一起:
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct WebFullLifecycleDemo {
controller: webview.WebviewController = new webview.WebviewController();
responseWeb: WebResourceResponse = new WebResourceResponse();
aboutToAppear(): void {
// 开启 Web 调试(开发阶段使用,生产环境关闭)
try {
webview.WebviewController.setWebDebuggingAccess(true);
} catch (error) {
console.error(`开启调试失败: ${(error as BusinessError).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());
}
return false; // 允许加载
})
.onInterceptRequest((event) => {
if (event) {
console.info('③ 请求拦截:', event.request.getRequestUrl());
}
return null; // 不替换响应
})
.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 | 控制器绑定完成 | 初始化、设置用户代理、注入对象 |
| onLoadIntercept | URL 即将加载前 | 拦截特定 URL |
| onInterceptRequest | 资源请求发出前 | 替换响应内容(离线缓存) |
| onPageBegin | 页面开始加载 | 显示 loading |
| onProgressChange | 加载进度变化 | 更新进度条 |
| onFirstContentfulPaint | 首帧内容绘制 | 性能监控 |
| onPageEnd | 页面加载完成 | 隐藏 loading,执行 JS |
| onPageVisible | 页面内容可见 | 统计页面展示 |
| onRenderExited | 渲染进程退出 | 显示重新加载提示 |
| onDisAppear | Web 组件从视图树移除 | 清理资源 |
写在最后
生命周期这块掌握了,很多"时机问题"就迎刃而解了。记住核心规则:初始化放 onControllerAttached,JS 脚本放 onPageEnd,loading 动画放 onPageBegin 开、onPageEnd 关。







