diff --git a/domain_resource/Index.ets b/domain_resource/Index.ets index 441c5e4..bbfad02 100644 --- a/domain_resource/Index.ets +++ b/domain_resource/Index.ets @@ -10,10 +10,10 @@ export { RemoteConfig, AgentNode, ChannelNode, MarketNode, GameOverrideNode, Gam export { VersionResolver } from './src/main/ets/version/VersionResolver'; // 资源 -export { VersionXml } from './src/main/ets/resource/VersionXml'; +export { VersionXml, VersionXmlInfo } from './src/main/ets/resource/VersionXml'; export { ResourceManager, ResourcePaths } from './src/main/ets/resource/ResourceManager'; export { AppDataInjector, AppDataValues } from './src/main/ets/resource/AppDataInjector'; // 启动编排 -export { StartupOrchestrator, StartupStage, StartupResult, StageCallback } +export { StartupOrchestrator, StartupStage, StartupResult, StageCallback, KV_KEY_NET_APP_VERSION } from './src/main/ets/startup/StartupOrchestrator'; diff --git a/domain_resource/src/main/ets/resource/AppDataInjector.ets b/domain_resource/src/main/ets/resource/AppDataInjector.ets index 101d6c4..af055b7 100644 --- a/domain_resource/src/main/ets/resource/AppDataInjector.ets +++ b/domain_resource/src/main/ets/resource/AppDataInjector.ets @@ -42,8 +42,8 @@ export interface AppDataValues { export class AppDataInjector { private static readonly log: Logger = Logger.tag('AppDataInjector'); - /** 由本地配置 + 资源版本 + 启动类型构建 app_data 值(大厅 launchtype='0' / 子游戏 '1')。 */ - static buildValues(config: AppConfig, version: string, launchtype: string): AppDataValues { + /** 由本地配置 + 资源版本 + 启动类型 + 游戏名构建 app_data 值(大厅 launchtype='0' / 子游戏 '1')。 */ + static buildValues(config: AppConfig, version: string, launchtype: string, gameName: string = ''): AppDataValues { return { app_version: version, app_gameconfig: config.gameconfig, @@ -55,8 +55,8 @@ export class AppDataInjector { app_channel: config.channel, app_invitationcode: config.tuiguang, app_Launchtype: launchtype, - // TODO(M3):app_gamename 源为 version.xml 的 ,当前 VersionXml 仅解析 version。 - app_gamename: '', + // app_gamename 源为 version.xml 的子 (ResourceManager.localGameName 提供) + app_gamename: gameName, app_getwifisignalLevel: '0', }; } diff --git a/domain_resource/src/main/ets/resource/ResourceManager.ets b/domain_resource/src/main/ets/resource/ResourceManager.ets index 9dfdd71..9b2b3e2 100644 --- a/domain_resource/src/main/ets/resource/ResourceManager.ets +++ b/domain_resource/src/main/ets/resource/ResourceManager.ets @@ -13,7 +13,7 @@ import { common } from '@kit.AbilityKit'; import { Downloader, FileSystem, KvStore, ProgressCallback, Unzipper } from 'platform'; import { Logger } from 'common'; import { AppConfig } from '../config/AppConfig'; -import { VersionXml } from './VersionXml'; +import { VersionXml, VersionXmlInfo } from './VersionXml'; /** 资源路径集合。 */ export interface ResourcePaths { @@ -73,18 +73,28 @@ export class ResourceManager { return FileSystem.exists(this.cachedPaths.indexPath); } - /** 本地资源版本(version.xml 的 version;缺失返回 0)。 */ - localVersion(): number { + /** 解析本地 version.xml(取首个存在的候选;缺失 → 默认空信息)。 */ + private readVersionXml(): VersionXmlInfo { const candidates: string[] = [ `${this.cachedPaths.gameDir}/version.xml`, `${this.cachedPaths.urlpath}/version.xml`, ]; for (const p of candidates) { if (FileSystem.exists(p)) { - return VersionXml.parseVersion(FileSystem.readText(p)); + return VersionXml.parse(FileSystem.readText(p)); } } - return 0; + return { version: 0, gameName: '' }; + } + + /** 本地资源版本(version.xml 的 version;缺失返回 0)。 */ + localVersion(): number { + return this.readVersionXml().version; + } + + /** 本地游戏名(version.xml 子 ;缺失返回 ''),写入 app_data 的 app_gamename。 */ + localGameName(): string { + return this.readVersionXml().gameName; } /** 持久化路径到 KvStore(供容器/重启读取)。 */ diff --git a/domain_resource/src/main/ets/resource/VersionXml.ets b/domain_resource/src/main/ets/resource/VersionXml.ets index 9ad5bd3..f4d2b30 100644 --- a/domain_resource/src/main/ets/resource/VersionXml.ets +++ b/domain_resource/src/main/ets/resource/VersionXml.ets @@ -2,29 +2,47 @@ * version.xml 解析(契约 §5.3,框架 §8.3)。用 @kit.ArkTS xml.XmlPullParser。 * * 结构: - * 主用途:取 与远程 game_version 比较决定是否更新。 - * `value` 属性为 version 标签独有,故用 attributeValueCallbackFunction 即可稳定抓取。 + * 用途:取 与远程 game_version 比较决定是否更新;取子 + * 作 app_data 的 app_gamename。`value` 为 version 标签独有;`name` 多标签共有,故按 + * 所属标签(当前为子 game)区分抓取。 */ import { xml, util } from '@kit.ArkTS'; import { Logger } from 'common'; +/** version.xml 解析结果。 */ +export interface VersionXmlInfo { + /** 整数(缺失/失败 → 0)。 */ + version: number; + /** 子 游戏名(缺失 → '')。 */ + gameName: string; +} + export class VersionXml { private static readonly log: Logger = Logger.tag('VersionXml'); - /** 解析 version 整数(解析失败/缺失 → 0)。 */ - static parseVersion(xmlText: string): number { + /** 解析 version + gameName。属性回调紧随其开始标签,故用 currentTag 把 name 归属到 game 标签。 */ + static parse(xmlText: string): VersionXmlInfo { + const info: VersionXmlInfo = { version: 0, gameName: '' }; if (xmlText.length === 0) { - return 0; + return info; } let value: string = ''; + let currentTag: string = ''; try { const buf: Uint8Array = new util.TextEncoder().encodeInto(xmlText); const parser: xml.XmlPullParser = new xml.XmlPullParser(buf.buffer as object as ArrayBuffer, 'UTF-8'); const options: xml.ParseOptions = { ignoreNameSpace: true, + tagValueCallbackFunction: (name: string, _val: string): boolean => { + currentTag = name; + return true; + }, attributeValueCallbackFunction: (name: string, val: string): boolean => { if (name === 'value') { value = val; + } else if (name === 'name' && currentTag === 'game') { + // 根 无 name 属性;只有子 触发,正是所需游戏名 + info.gameName = val; } return true; }, @@ -33,9 +51,15 @@ export class VersionXml { } catch (e) { const err = e as Error; VersionXml.log.w(`parse version.xml failed: ${err.message}`); - return 0; + return info; } const n: number = parseInt(value, 10); - return isNaN(n) ? 0 : n; + info.version = isNaN(n) ? 0 : n; + return info; + } + + /** 解析 version 整数(解析失败/缺失 → 0)。 */ + static parseVersion(xmlText: string): number { + return VersionXml.parse(xmlText).version; } } diff --git a/domain_resource/src/main/ets/startup/StartupOrchestrator.ets b/domain_resource/src/main/ets/startup/StartupOrchestrator.ets index 5c334f5..472d6ef 100644 --- a/domain_resource/src/main/ets/startup/StartupOrchestrator.ets +++ b/domain_resource/src/main/ets/startup/StartupOrchestrator.ets @@ -16,6 +16,9 @@ import { VersionDecision } from '../config/RemoteConfig'; import { ResourceManager } from '../resource/ResourceManager'; import { AppDataInjector, AppDataValues } from '../resource/AppDataInjector'; +/** KvStore 键:启动期算出的网络 App 版本(供 DeviceProvider getcompareCode 同步比较)。 */ +export const KV_KEY_NET_APP_VERSION: string = 'net_app_version'; + /** 启动阶段(用于进度展示/可观测)。 */ export enum StartupStage { INIT = 'INIT', @@ -122,6 +125,10 @@ export class StartupOrchestrator { StartupOrchestrator.log.w(`blocked by showmessage: ${decision.showmessage}`); return { enter: false, entryUrl: '', message: decision.showmessage }; } + // 缓存网络 App 版本,供 DeviceProvider getcompareCode 同步比较(离线 decision 缺失则保留上次值) + if (decision !== undefined) { + kv.putNumber(KV_KEY_NET_APP_VERSION, decision.appVersion); + } // 5) 版本更新(依赖内置资源已就绪 + 远程决策) const localVer: number = resource.localVersion(); @@ -144,7 +151,7 @@ export class StartupOrchestrator { // 6) 注入 app_data.js(务必在进大厅之前)。大厅 launchtype='0' this.stage(StartupStage.INJECT_APP_DATA, 92); - const values: AppDataValues = AppDataInjector.buildValues(local, `${resource.localVersion()}`, '0'); + const values: AppDataValues = AppDataInjector.buildValues(local, `${resource.localVersion()}`, '0', resource.localGameName()); AppDataInjector.inject(resource.paths().gameDir, values); // 7) 进大厅 diff --git a/entry/src/main/ets/di/AppModule.ets b/entry/src/main/ets/di/AppModule.ets index dcc8513..c4818ad 100644 --- a/entry/src/main/ets/di/AppModule.ets +++ b/entry/src/main/ets/di/AppModule.ets @@ -5,14 +5,13 @@ * M2:仅示例能力(验证插件模式)。M3:替换为全部真实 Provider + 占位桩 * (RoomStubProvider/PayStubProvider,§6.5),契约 handler 名/数量不变。 */ -import { CapabilityProvider, EchoSampleProvider, RoomStubProvider, PayStubProvider, +import { CapabilityProvider, RoomStubProvider, PayStubProvider, VibrateProvider, ClipboardProvider, AppSystemProvider, DeviceProvider, NetworkProvider, ShareProvider, LoginProvider, PhotoProvider, ShakeProvider, ScanProvider, CameraProvider, LocationProvider, AudioProvider, NavProvider } from 'feature_capabilities'; export function buildCapabilities(): CapabilityProvider[] { return [ - new EchoSampleProvider(), // M2 回显样板(M3 全部 Provider 就位后可移除) // —— M3 已落地能力(顺序无关,互不依赖)—— new AppSystemProvider(), // orientation/browser/finsh/openApplyDownloadpath/notification new VibrateProvider(), // vibrator/repeatvibrator/canclevibrator @@ -31,7 +30,5 @@ export function buildCapabilities(): CapabilityProvider[] { // —— 暂缓能力占位桩(§6.5)—— new RoomStubProvider(), new PayStubProvider(), - // —— M3 待落地:DeviceProvider/NetworkProvider/ShakeProvider/LocationProvider/AudioProvider/ - // ScanProvider/CameraProvider/PhotoProvider/NavProvider/ShareProvider/LoginProvider —— ]; } diff --git a/entry/src/main/ets/pages/BridgeGameContainer.ets b/entry/src/main/ets/pages/BridgeGameContainer.ets index e07f29c..018de79 100644 --- a/entry/src/main/ets/pages/BridgeGameContainer.ets +++ b/entry/src/main/ets/pages/BridgeGameContainer.ets @@ -133,7 +133,7 @@ export struct BridgeGameContainer { } const subDir: string = `${res.paths().urlpath}/${d.dir}`; // 子游戏目录自带 app_data.js(H5 同步读),launchtype='1' - const values: AppDataValues = AppDataInjector.buildValues(cfg.getLocal(), `${res.localVersion()}`, '1'); + const values: AppDataValues = AppDataInjector.buildValues(cfg.getLocal(), `${res.localVersion()}`, '1', res.localGameName()); AppDataInjector.inject(subDir, values); this.slotL?.deactivate(); this.slotS = new WebSlot('subgame', new webview.WebviewController(), true, this.uploadServer); diff --git a/feature_capabilities/Index.ets b/feature_capabilities/Index.ets index e0a5157..99b9f51 100644 --- a/feature_capabilities/Index.ets +++ b/feature_capabilities/Index.ets @@ -4,9 +4,6 @@ export { CapabilityProvider, CapabilityContext } from './src/main/ets/core/CapabilityProvider'; export { CapabilityRegistrar } from './src/main/ets/core/CapabilityRegistrar'; -// 示例能力(M2 脚手架,M3 逐步替换为真实 Provider) -export { EchoSampleProvider } from './src/main/ets/providers/EchoSampleProvider'; - // —— M3 能力 Provider —— export { RoomStubProvider, PayStubProvider } from './src/main/ets/providers/StubProviders'; export { VibrateProvider } from './src/main/ets/providers/VibrateProvider'; diff --git a/feature_capabilities/src/main/ets/providers/DeviceProvider.ets b/feature_capabilities/src/main/ets/providers/DeviceProvider.ets index 9ddd536..45ef785 100644 --- a/feature_capabilities/src/main/ets/providers/DeviceProvider.ets +++ b/feature_capabilities/src/main/ets/providers/DeviceProvider.ets @@ -13,7 +13,8 @@ import { wifiManager } from '@kit.ConnectivityKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { BridgeController } from 'feature_bridge'; import { InboundHandlers, OutboundHandlers, PhoneInfoBean, WifiLevelResp } from 'contracts'; -import { ConfigManager, AppConfig } from 'domain_resource'; +import { ConfigManager, AppConfig, KV_KEY_NET_APP_VERSION } from 'domain_resource'; +import { KvStore } from 'platform'; import { Logger } from 'common'; import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider'; @@ -22,10 +23,12 @@ export class DeviceProvider implements CapabilityProvider { private readonly log: Logger = Logger.tag('DeviceProvider'); private bridge: BridgeController | undefined = undefined; private config: ConfigManager | undefined = undefined; + private kv: KvStore | undefined = undefined; register(bridge: BridgeController, ctx: CapabilityContext): void { this.bridge = bridge; this.config = ctx.config; + this.kv = ctx.kv; bridge.registerHandler(InboundHandlers.GetTime, (_d: string, cb: (resp: string) => void) => { cb(Date.now().toString()); @@ -43,8 +46,12 @@ export class DeviceProvider implements CapabilityProvider { cb(this.lookupConfig(data)); }); bridge.registerHandler(InboundHandlers.GetCompareCode, (_d: string, cb: (resp: string) => void) => { - // TODO(M3):1=本地 appversion>网络 app_version/0=否,需接 StartupOrchestrator 的版本决策;暂安全返回 '0' - cb('0'); + // 契约:本地 App 版本 > 网络版本 → '1',否则 '0'(对齐 Android apputil.code)。 + // 网络版本由 StartupOrchestrator 启动期算出存入 KvStore;本地版本为 config.appversion。 + const localVer: number = parseInt(this.local().appversion, 10); + const local: number = isNaN(localVer) ? 0 : localVer; + const net: number = this.kv?.getNumber(KV_KEY_NET_APP_VERSION, 0) ?? 0; + cb(local > net ? '1' : '0'); }); bridge.registerHandler(InboundHandlers.GetBattery, (_d: string, _cb: (resp: string) => void) => { this.pushBattery(); diff --git a/feature_capabilities/src/main/ets/providers/EchoSampleProvider.ets b/feature_capabilities/src/main/ets/providers/EchoSampleProvider.ets deleted file mode 100644 index 7053736..0000000 --- a/feature_capabilities/src/main/ets/providers/EchoSampleProvider.ets +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 示例能力(M2 脚手架)。验证 CapabilityProvider 插件模式可注册并被桥分发, - * 对接最小回显 H5(test_echo.html 用 getTime/nativeEcho)。 - * - * ⚠ M3 用真实 Provider(DeviceProvider 等)替换:buildCapabilities() 删除本行即可, - * 桥核心与容器零改动。 - */ -import { BridgeController } from 'feature_bridge'; -import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider'; - -export class EchoSampleProvider implements CapabilityProvider { - readonly name: string = 'echo(sample)'; - - register(bridge: BridgeController, ctx: CapabilityContext): void { - bridge.registerHandler('getTime', (_data: string, cb: (resp: string) => void) => { - cb(Date.now().toString()); - }); - bridge.registerHandler('nativeEcho', (data: string, cb: (resp: string) => void) => { - cb(data); - }); - ctx.log.i('echo sample registered (getTime/nativeEcho)'); - } -}