import webview from '@ohos.web.webview';
/**
* 渲染进程异常后的页面恢复策略。
*
* 渲染进程被系统回收后 Web 组件不会自动恢复,需主动重载,否则页面卡死/白屏。
* 恢复链:`loadUrl(fallbackUrl)` → `refresh()` → `onUnrecoverable()`。
*
* **为何 loadUrl 优先于 refresh**:进程崩溃后内部状态未清,`refresh()`/`reload()` 常失效
* (甚至不抛错却也未真正恢复),`loadUrl()` 能强制重建渲染管线。与 Android(新建实例后 loadUrl)、
* iOS(优先 load(URLRequest))对齐,亦符合社区推荐。
*
* **延迟恢复**:崩溃后立即 reload 可能因内存/资源仍紧张而再次崩溃(尤其 OOM 场景),
* 故延迟 `RECOVER_DELAY_MS` 再执行,给系统留出资源回收窗口(对齐社区实践)。
*/
export class RenderRecovery {
/** 崩溃后延迟恢复的毫秒数,避免瞬间抢占资源再次崩溃。 */
private static readonly RECOVER_DELAY_MS: number = 1000;
/**
* 尝试恢复 Web 组件(延迟 `RECOVER_DELAY_MS` 后执行,避免瞬间再次崩溃)。
*
* **重要**:返回的 timer id 必须由调用方保存,并在容器销毁/归还池时 `clearTimeout` 取消,
* 否则延迟窗口内用户关闭页面后,回调仍会访问已销毁/已归还的 controller,导致无效 reload
* 或污染已归还的池实例(野指针)。
*
* @param controller WebviewController
* @param fallbackUrl 优先用以 `loadUrl` 重载的 URL;空串表示无可用 URL(退化为 `refresh` 兜底)
* @param onUnrecoverable 全部尝试失败时的回调(如关闭容器)
* @returns 延迟恢复的 timer id,供调用方在生命周期结束时 `clearTimeout` 取消
*/
public static recover(
controller: webview.WebviewController,
fallbackUrl: string,
onUnrecoverable?: () => void
): number {
return setTimeout(() => {
RenderRecovery.performRecover(controller, fallbackUrl, onUnrecoverable);
}, RenderRecovery.RECOVER_DELAY_MS);
}
/**
* 立即执行一次恢复尝试(同步)。恢复链:`loadUrl(fallbackUrl)` → `refresh()` → `onUnrecoverable()`。
* 供 {@link recover} 延迟调度调用,亦便于单测同步验证。
*/
public static performRecover(
controller: webview.WebviewController,
fallbackUrl: string,
onUnrecoverable?: () => void
): void {
// 优先 loadUrl(fallbackUrl):进程崩溃后 refresh() 常因内部状态未清而失效,loadUrl 能强制重建渲染管线
// (与 Android/iOS 恢复方式对齐,符合社区推荐)。
if (fallbackUrl.length > 0) {
try {
controller.loadUrl(fallbackUrl);
return;
} catch (_e) {
// fallthrough:loadUrl 失败再退化尝试 refresh 兜底
}
}
// 无可用 URL / loadUrl 失败 → 退化尝试 refresh;仍失败则判定不可恢复(触发关闭容器)。
try {
controller.refresh();
} catch (_e) {
RenderRecovery.fireUnrecoverable(onUnrecoverable);
}
}
private static fireUnrecoverable(cb?: () => void): void {
if (cb !== undefined) {
cb();
}
}
/**
* 从 `onPageBegin` 事件中提取主框架 URL,用于 `recover()` 的 fallback 兜底。
* 提取失败返回 `current`,不覆盖原值。
*/
public static captureLastUrl(event: ESObject, current: string): string {
try {
const url: string = (event as ESObject).url as string;
return (url !== null && url !== undefined && url.length > 0) ? url : current;
} catch (_e) {
return current;
}
}
private constructor() {}
}
|