review: M5 双 Web 审查后的质量加固(两轮真机验证)
八角度审查双 Web 批次。修确凿问题: 铁律风险(最高) - network_config:base-config cleartextTrafficPermitted 由 false 改 true。我之前的"白名单收紧"比平台默认更严, 会拦掉 H5 可能访问的任意第三方明文 http 与本地 127.0.0.1 上传 → 违反 H5 零改动。改回允许明文(对齐原 Android) 正确性 - 子游戏 controller 不再复用:每次进子游戏 new WebviewController()(存 slotS.controller),消除"复用同一 controller 时 onControllerAttached 不再触发致第二个子游戏建不出桥"的竞态。真机两轮验证 subB 桥正常 ✓ - BridgeController.dispose 置 active=false:显式抑制 dispose 后能力延迟异步 callHandler(不靠 adapter emitter-off 偶然安全) - WebSlot.resumeLoaded 补发 setPostUrl:覆盖"非激活期渲染崩溃重载后 onFirstProgress 被闸门抑制丢失上传地址"。真机验证返回必带 setPostUrl ✓ - onRenderExited 不可见时延迟重载:非激活槽崩溃标记 needReload,激活时再 reload(官方建议可见后重载);activate 返回是否重载,resumeLoaded 据此避免重复推送 - 容器 aboutToDisappear:先退订 cancels 再 dispose 槽,关闭销毁期事件又建槽窗口 - LocationProvider:权限对话框 await 后加 disposed 守卫(防 onDestroy 后再注册监听泄漏+打已弃桥) - AudioProvider:createAVPlayer.then 加 disposed 守卫(创建期已销毁则直接 release,防孤儿播放器泄漏) - NetworkProvider.onForeground:重订阅后补播当前网络(覆盖非激活期网络变化,避免 H5 状态陈旧) 研判驳回:network_config 需 metadata 关联(官方文档证实 profile 路径自动识别);app 前后台只发 activeSlot(非激活大厅已收 appservice'2',正确)。 延期:WeChatApi 单例陈旧回调(随微信集成)。 devecocli build + scripts/test.sh 通过;模拟器两轮(大厅↔subA↔大厅↔subB↔大厅)无回归。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,8 @@ export struct BridgeGameContainer {
|
||||
pathStack: NavPathStack = new NavPathStack();
|
||||
params: BridgeGameParams = { entryUrl: '', launchType: '0' };
|
||||
private controllerL: webview.WebviewController = new webview.WebviewController();
|
||||
private controllerS: webview.WebviewController = new webview.WebviewController();
|
||||
// 子游戏 controller 不复用:每次进子游戏新建(随 slotS),避免复用同一 controller 时
|
||||
// onControllerAttached 不再触发导致第二个子游戏建不出桥(review 发现)。存于 slotS.controller。
|
||||
private uploadServer: LocalUploadServer = new LocalUploadServer();
|
||||
private hostCtx: common.UIAbilityContext | undefined = undefined;
|
||||
private config: ConfigManager | undefined = undefined;
|
||||
@@ -41,12 +42,13 @@ export struct BridgeGameContainer {
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
this.slotS?.dispose();
|
||||
this.slotL?.dispose();
|
||||
// 先退订事件,再 dispose——避免销毁期 SWITCH_GAME/FOREGROUND 等事件又触发建槽/回调
|
||||
for (const cancel of this.cancels) {
|
||||
cancel();
|
||||
}
|
||||
this.cancels = [];
|
||||
this.slotS?.dispose();
|
||||
this.slotL?.dispose();
|
||||
this.uploadServer.stop();
|
||||
this.slotS = undefined;
|
||||
this.slotL = undefined;
|
||||
@@ -134,7 +136,7 @@ export struct BridgeGameContainer {
|
||||
const values: AppDataValues = AppDataInjector.buildValues(cfg.getLocal(), `${res.localVersion()}`, '1');
|
||||
AppDataInjector.inject(subDir, values);
|
||||
this.slotL?.deactivate();
|
||||
this.slotS = new WebSlot('subgame', this.controllerS, true, this.uploadServer);
|
||||
this.slotS = new WebSlot('subgame', new webview.WebviewController(), true, this.uploadServer);
|
||||
this.applyOrientation(d.webtype !== '2'); // "3" 横 / "2" 竖
|
||||
this.subgameUrl = `file://${subDir}/index.html?Launchtype=1`; // 触发子游戏 Web 渲染 → setupSubgame
|
||||
}
|
||||
@@ -192,8 +194,8 @@ export struct BridgeGameContainer {
|
||||
.onRenderExited((event: OnRenderExitedEvent) => this.slotL?.onRenderExited(event.renderExitReason))
|
||||
|
||||
// 子游戏 Web(临时,subgameUrl 非空时存在)
|
||||
if (this.subgameUrl !== '') {
|
||||
Web({ src: this.subgameUrl, controller: this.controllerS })
|
||||
if (this.subgameUrl !== '' && this.slotS !== undefined) {
|
||||
Web({ src: this.subgameUrl, controller: this.slotS.controller })
|
||||
.javaScriptAccess(true).domStorageAccess(true).fileAccess(true)
|
||||
.mixedMode(MixedMode.All).cacheMode(CacheMode.None)
|
||||
.geolocationAccess(true).zoomAccess(false)
|
||||
|
||||
@@ -29,6 +29,7 @@ export class WebSlot {
|
||||
private firstDone: boolean = false;
|
||||
private active: boolean;
|
||||
private disposed: boolean = false;
|
||||
private needReload: boolean = false;
|
||||
|
||||
constructor(role: string, controller: webview.WebviewController, active: boolean, uploadServer: LocalUploadServer) {
|
||||
this.role = role;
|
||||
@@ -89,12 +90,22 @@ export class WebSlot {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
WebSlot.log.w(`[${this.role}] render exited (reason=${reason}); reloading`);
|
||||
WebSlot.log.w(`[${this.role}] render exited (reason=${reason})`);
|
||||
this.firstDone = false;
|
||||
if (this.active) {
|
||||
this.doReload();
|
||||
} else {
|
||||
// 不可见(非激活/隐藏)时不立即重载,标记待激活时重载(官方建议:可见后再 reload)
|
||||
this.needReload = true;
|
||||
}
|
||||
}
|
||||
|
||||
private doReload(): void {
|
||||
this.needReload = false;
|
||||
try {
|
||||
this.controller.refresh();
|
||||
} catch (e) {
|
||||
WebSlot.log.e(`[${this.role}] refresh after render exit failed: ${(e as BusinessError).message}`);
|
||||
WebSlot.log.e(`[${this.role}] reload after render exit failed: ${(e as BusinessError).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +124,8 @@ export class WebSlot {
|
||||
this.flushPending();
|
||||
}
|
||||
|
||||
/** 激活(onActive + 桥放行 + 能力前台)。页面已加载的大厅返回时另调 resumeLoaded 补发 appservice。 */
|
||||
activate(): void {
|
||||
/** 激活(onActive + 桥放行 + 能力前台)。返回是否触发了延迟重载(非激活期渲染崩溃 → 此刻重载)。 */
|
||||
activate(): boolean {
|
||||
this.active = true;
|
||||
try {
|
||||
this.controller.onActive();
|
||||
@@ -123,12 +134,26 @@ export class WebSlot {
|
||||
}
|
||||
this.bridgeCtrl?.setActive(true);
|
||||
this.registrar?.forEachForeground();
|
||||
if (this.needReload) {
|
||||
this.doReload(); // 重载后由 onFirstProgress 补 appservice/setPostUrl
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 大厅从子游戏返回(页面仍在、无新 100%):激活并直接补发 appservice('1') + 待发 getWebdata。 */
|
||||
/** 大厅从子游戏返回(页面仍在、无新 100%):激活并补发 appservice('1') + setPostUrl + 待发 getWebdata。
|
||||
* 补发 setPostUrl:覆盖"非激活期渲染崩溃重载后 onFirstProgress 被闸门抑制而丢失上传地址"的情形。 */
|
||||
resumeLoaded(): void {
|
||||
this.activate();
|
||||
this.bridgeCtrl?.callHandler(OutboundHandlers.AppService, '1');
|
||||
const reloaded: boolean = this.activate();
|
||||
if (reloaded) {
|
||||
// 页面正在重载(非激活期崩溃恢复):由重载后的 onFirstProgress 补发 appservice/setPostUrl/getWebdata
|
||||
return;
|
||||
}
|
||||
const b = this.bridgeCtrl;
|
||||
if (b !== undefined) {
|
||||
b.callHandler(OutboundHandlers.AppService, '1');
|
||||
b.callHandler(OutboundHandlers.SetPostUrl, this.uploadServer.baseUrl());
|
||||
}
|
||||
this.flushPending();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
{
|
||||
"network-security-config": {
|
||||
"base-config": {
|
||||
"cleartextTrafficPermitted": false
|
||||
},
|
||||
"domain-config": [
|
||||
{
|
||||
"domains": [
|
||||
{
|
||||
"include-subdomains": true,
|
||||
"name": "daoqi88.cn"
|
||||
}
|
||||
],
|
||||
"cleartextTrafficPermitted": true
|
||||
}
|
||||
]
|
||||
"cleartextTrafficPermitted": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user