diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-129x29.png b/docs/Res/AppIcon-1.appiconset/AppIcon-129x29.png new file mode 100644 index 0000000..7cde344 Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-129x29.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-129x29@2x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-129x29@2x.png new file mode 100644 index 0000000..69407ba Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-129x29@2x.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-129x29@3x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-129x29@3x.png new file mode 100644 index 0000000..699582e Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-129x29@3x.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-140x40@2x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-140x40@2x.png new file mode 100644 index 0000000..e5b02e3 Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-140x40@2x.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-140x40@3x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-140x40@3x.png new file mode 100644 index 0000000..caac756 Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-140x40@3x.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-157x57.png b/docs/Res/AppIcon-1.appiconset/AppIcon-157x57.png new file mode 100644 index 0000000..e950abc Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-157x57.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-157x57@2x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-157x57@2x.png new file mode 100644 index 0000000..ee5d16c Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-157x57@2x.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-160x60@2x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-160x60@2x.png new file mode 100644 index 0000000..caac756 Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-160x60@2x.png differ diff --git a/docs/Res/AppIcon-1.appiconset/AppIcon-160x60@3x.png b/docs/Res/AppIcon-1.appiconset/AppIcon-160x60@3x.png new file mode 100644 index 0000000..1a69a62 Binary files /dev/null and b/docs/Res/AppIcon-1.appiconset/AppIcon-160x60@3x.png differ diff --git a/docs/Res/LaunchImage.png b/docs/Res/LaunchImage.png new file mode 100644 index 0000000..6f217f8 Binary files /dev/null and b/docs/Res/LaunchImage.png differ diff --git a/docs/Res/shareDouyin.imageset/shareDouyin.png b/docs/Res/shareDouyin.imageset/shareDouyin.png new file mode 100644 index 0000000..e39ea4b Binary files /dev/null and b/docs/Res/shareDouyin.imageset/shareDouyin.png differ diff --git a/docs/Res/shareDouyin.imageset/shareDouyin@2x.png b/docs/Res/shareDouyin.imageset/shareDouyin@2x.png new file mode 100644 index 0000000..b9b589c Binary files /dev/null and b/docs/Res/shareDouyin.imageset/shareDouyin@2x.png differ diff --git a/docs/Res/shareDouyin.imageset/shareDouyin@3x.png b/docs/Res/shareDouyin.imageset/shareDouyin@3x.png new file mode 100644 index 0000000..bafd499 Binary files /dev/null and b/docs/Res/shareDouyin.imageset/shareDouyin@3x.png differ diff --git a/docs/Res/shareQQ.imageset/shareQQ.png b/docs/Res/shareQQ.imageset/shareQQ.png new file mode 100644 index 0000000..f93fa44 Binary files /dev/null and b/docs/Res/shareQQ.imageset/shareQQ.png differ diff --git a/docs/Res/shareQQ.imageset/shareQQ@2x.png b/docs/Res/shareQQ.imageset/shareQQ@2x.png new file mode 100644 index 0000000..fc8d964 Binary files /dev/null and b/docs/Res/shareQQ.imageset/shareQQ@2x.png differ diff --git a/docs/Res/shareQQ.imageset/shareQQ@3x.png b/docs/Res/shareQQ.imageset/shareQQ@3x.png new file mode 100644 index 0000000..3f3d6d5 Binary files /dev/null and b/docs/Res/shareQQ.imageset/shareQQ@3x.png differ diff --git a/docs/Res/shareWechat.imageset/shareWechat.png b/docs/Res/shareWechat.imageset/shareWechat.png new file mode 100644 index 0000000..700805b Binary files /dev/null and b/docs/Res/shareWechat.imageset/shareWechat.png differ diff --git a/docs/Res/shareWechat.imageset/shareWechat@2x.png b/docs/Res/shareWechat.imageset/shareWechat@2x.png new file mode 100644 index 0000000..73c3115 Binary files /dev/null and b/docs/Res/shareWechat.imageset/shareWechat@2x.png differ diff --git a/docs/Res/shareWechat.imageset/shareWechat@3x.png b/docs/Res/shareWechat.imageset/shareWechat@3x.png new file mode 100644 index 0000000..94ae88d Binary files /dev/null and b/docs/Res/shareWechat.imageset/shareWechat@3x.png differ diff --git a/entry/src/main/ets/pages/BridgeGameContainer.ets b/entry/src/main/ets/pages/BridgeGameContainer.ets index b34f045..af8435b 100644 --- a/entry/src/main/ets/pages/BridgeGameContainer.ets +++ b/entry/src/main/ets/pages/BridgeGameContainer.ets @@ -53,6 +53,27 @@ export struct BridgeGameContainer { this.registrar = undefined; this.bridge = undefined; this.adapter = undefined; + this.hostCtx = undefined; + this.config = undefined; + this.resource = undefined; + } + + /** 页面进度首次 100% 时下发 appservice/setPostUrl(+子游戏返回大厅待发的 getWebdata)。幂等。 + * firstProgressDone 在子游戏切换/返回大厅时复位,使重载后再次下发。 */ + private firstReadyPush(): void { + if (this.firstProgressDone) { + return; + } + this.firstProgressDone = true; + const b = this.bridge; + if (b !== undefined) { + b.callHandler(OutboundHandlers.AppService, '1'); + b.callHandler(OutboundHandlers.SetPostUrl, this.uploadServer.baseUrl()); + if (this.pendingWebdata !== undefined) { + b.callHandler(OutboundHandlers.GetWebData, this.pendingWebdata); + this.pendingWebdata = undefined; + } + } } /** onControllerAttached:建桥 + 经组装根注册全部能力(框架 §A.1)。 */ @@ -197,19 +218,11 @@ export struct BridgeGameContainer { this.bridge?.onPageEnd(); }) .onProgressChange((event: OnProgressChangeEvent) => { - if (event.newProgress === 100 && !this.firstProgressDone) { - // 首次/每次切换后页面到 100% → 推 appservice(前台) + setPostUrl(对齐 Android onProgressChanged==100) - this.firstProgressDone = true; - const b = this.bridge; - if (b !== undefined) { - b.callHandler(OutboundHandlers.AppService, '1'); - b.callHandler(OutboundHandlers.SetPostUrl, this.uploadServer.baseUrl()); - } - // 子游戏返回大厅的回传数据,待大厅重载完成后下发 getWebdata - if (this.pendingWebdata !== undefined) { - b?.callHandler(OutboundHandlers.GetWebData, this.pendingWebdata); - this.pendingWebdata = undefined; - } + // 首次/每次切换后页面到 100% → 推 appservice(前台)+setPostUrl(对齐 Android onProgressChanged==100)。 + // 用 100%(而非更早的 onPageEnd):确保 H5 在 WebViewJavascriptBridgeReady 里已注册 appservice/setPostUrl + // 等 handler,避免出站早于注册被丢。loadUrl 切子游戏/返回大厅会再次产生 100% 回调(已复位 firstProgressDone)。 + if (event.newProgress === 100) { + this.firstReadyPush(); } }) } diff --git a/entry/src/main/ets/pages/GenericWebContainer.ets b/entry/src/main/ets/pages/GenericWebContainer.ets index 864209f..020d4bc 100644 --- a/entry/src/main/ets/pages/GenericWebContainer.ets +++ b/entry/src/main/ets/pages/GenericWebContainer.ets @@ -61,9 +61,9 @@ export struct GenericWebContainer { .catch((e: BusinessError) => Logger.tag('GenericWebContainer').w(`orientation failed: ${e.message}`)); } - /** JS 字符串字面量转义(用于 runJavaScript 直调)。 */ - private esc(s: string): string { - return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); + /** 生成安全的 JS 字符串实参字面量:JSON.stringify 完整转义引号/反斜杠/换行/行分隔符。 */ + private jsArg(s: string): string { + return JSON.stringify(s); } build() { @@ -87,7 +87,7 @@ export struct GenericWebContainer { // 首次 100% → 原生直调 getWebdata('')(§11.3) if (event.newProgress === 100 && !this.loaded) { this.loaded = true; - this.controller.runJavaScript(`getWebdata('${this.esc(this.params.data)}')`); + this.controller.runJavaScript(`getWebdata(${this.jsArg(this.params.data)})`); } }) } diff --git a/feature_capabilities/src/main/ets/providers/LocationProvider.ets b/feature_capabilities/src/main/ets/providers/LocationProvider.ets index eefef64..70b53f3 100644 --- a/feature_capabilities/src/main/ets/providers/LocationProvider.ets +++ b/feature_capabilities/src/main/ets/providers/LocationProvider.ets @@ -57,6 +57,8 @@ export class LocationProvider implements CapabilityProvider { if (continuous) { this.startContinuous(); } else { + // 单次请求前,若已有连续监听在跑则先停,避免单次后仍持续推送(H5 预期一次) + this.stopContinuous(); this.requestSingle(); } } @@ -163,9 +165,7 @@ export class LocationProvider implements CapabilityProvider { } private push(info: MaplocationInfo): void { + // 单次定位用 getCurrentLocation(一次性,无监听可停);连续定位由 stopshake 等价的 stopContinuous 管理。 this.bridge?.callHandler(OutboundHandlers.GetLocationInfo, JSON.stringify(info)); - if (!this.continuousOn) { - this.stopContinuous(); - } } } diff --git a/feature_capabilities/src/main/ets/providers/PhotoProvider.ets b/feature_capabilities/src/main/ets/providers/PhotoProvider.ets index 0ce49c8..d2201e4 100644 --- a/feature_capabilities/src/main/ets/providers/PhotoProvider.ets +++ b/feature_capabilities/src/main/ets/providers/PhotoProvider.ets @@ -31,19 +31,25 @@ export class PhotoProvider implements CapabilityProvider { if (ctx === undefined) { return; } - let list: SavephotoURLBean[]; + let parsed: Object; try { - list = JSON.parse(data) as SavephotoURLBean[]; + parsed = JSON.parse(data) as Object; } catch (e) { this.log.w(`getphoto bad json: ${(e as Error).message}`); return; } + // H5 可能传非数组 → 守卫,避免 .map 抛错致出站永不下发(H5 等待挂死) + if (!Array.isArray(parsed)) { + this.log.w('getphoto data is not an array'); + this.bridge?.callHandler(OutboundHandlers.GetPhoto, '[]'); + return; + } + const list: SavephotoURLBean[] = parsed as SavephotoURLBean[]; const dir: string = `${ResourceManager.resourceRoot(ctx)}/photos`; + // downloadOne 自身吞错并回空 photourl(不 reject),故 Promise.all 必定 resolve const tasks: Promise[] = list.map((item: SavephotoURLBean) => this.downloadOne(item, dir)); Promise.all(tasks).then((results: SavephotoURLBean[]) => { this.bridge?.callHandler(OutboundHandlers.GetPhoto, JSON.stringify(results)); - }).catch((e: Error) => { - this.log.w(`getphoto failed: ${e.message}`); }); } diff --git a/feature_capabilities/src/main/ets/providers/ShakeProvider.ets b/feature_capabilities/src/main/ets/providers/ShakeProvider.ets index d0a3187..c83f54e 100644 --- a/feature_capabilities/src/main/ets/providers/ShakeProvider.ets +++ b/feature_capabilities/src/main/ets/providers/ShakeProvider.ets @@ -27,6 +27,7 @@ export class ShakeProvider implements CapabilityProvider { private listening: boolean = false; private feedbackOn: boolean = false; private pending: boolean = false; + private endTimer: number = -1; private readonly onAccel: (data: sensor.AccelerometerResponse) => void; constructor() { @@ -72,6 +73,12 @@ export class ShakeProvider implements CapabilityProvider { this.log.w(`sensor.off failed: ${(e as BusinessError).message}`); } this.listening = false; + // 取消未决的 shakeEnd 计时并复位节流,避免停止后仍触发出站 / 重启后首次摇动被吞 + if (this.endTimer !== -1) { + clearTimeout(this.endTimer); + this.endTimer = -1; + } + this.pending = false; } private handleAccel(data: sensor.AccelerometerResponse): void { @@ -88,7 +95,8 @@ export class ShakeProvider implements CapabilityProvider { vibrator.startVibration({ type: 'time', duration: 200 }, { usage: 'notification' }) .catch((e: BusinessError) => this.log.w(`shake vibrate failed: ${e.message}`)); } - setTimeout(() => { + this.endTimer = setTimeout(() => { + this.endTimer = -1; this.bridge?.callHandler(OutboundHandlers.ShakeEnd, ''); this.pending = false; }, SHAKE_END_DELAY); diff --git a/feature_capabilities/src/main/ets/providers/VibrateProvider.ets b/feature_capabilities/src/main/ets/providers/VibrateProvider.ets index b23c70e..8553c67 100644 --- a/feature_capabilities/src/main/ets/providers/VibrateProvider.ets +++ b/feature_capabilities/src/main/ets/providers/VibrateProvider.ets @@ -53,6 +53,8 @@ export class VibrateProvider implements CapabilityProvider { } private startRepeat(): void { + // 先清旧链:避免重复 repeatvibrator('1') 叠加出无法取消的孤儿计时链 + this.stopRepeat(); this.repeating = true; this.loop(); }