M3 补全:移除 EchoSample 样板 + getcompareCode 接版本决策 + app_gamename

- 移除 EchoSampleProvider(组装根/导出/文件):M3 全部 Provider 已就位,
  getTime 由 DeviceProvider 提供;样板回显仅 M1 测试页用,生产无依赖
- getcompareCode:启动期 StartupOrchestrator 把网络 App 版本(VersionDecision.appVersion)
  存 KvStore(KV_KEY_NET_APP_VERSION),DeviceProvider 同步读出与本地 config.appversion 比较,
  本地>网络→'1' 否则'0'(对齐 Android apputil.code 语义)
- app_gamename:VersionXml.parse 解析子 <game name>;ResourceManager.localGameName 暴露;
  AppDataInjector.buildValues 接 gameName 参数;大厅/子游戏两处注入均填入

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-26 07:33:13 +08:00
co-authored by Claude Opus 4.8
parent cc5db07ddd
commit 01e4c0e99e
10 changed files with 72 additions and 53 deletions
@@ -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 的 <game name>,当前 VersionXml 仅解析 version。
app_gamename: '',
// app_gamename 源为 version.xml 的 <game name>ResourceManager.localGameName 提供)
app_gamename: gameName,
app_getwifisignalLevel: '0',
};
}
@@ -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 子 <game name>;缺失返回 ''),写入 app_data 的 app_gamename。 */
localGameName(): string {
return this.readVersionXml().gameName;
}
/** 持久化路径到 KvStore(供容器/重启读取)。 */
@@ -2,29 +2,47 @@
* version.xml 解析(契约 §5.3,框架 §8.3)。用 @kit.ArkTS xml.XmlPullParser。
*
* 结构:<game><agent id name/><game id name/><channel id name/><version value name/></game>
* 用途:取 <version value="42"> 与远程 game_version 比较决定是否更新
* `value` 属性为 version 标签独有,故用 attributeValueCallbackFunction 即可稳定抓取。
* 用途:取 <version value="42"> 与远程 game_version 比较决定是否更新;取子 <game name="...">
* 作 app_data 的 app_gamename。`value` 为 version 标签独有`name` 多标签共有,故按
* 所属标签(当前为子 game)区分抓取。
*/
import { xml, util } from '@kit.ArkTS';
import { Logger } from 'common';
/** version.xml 解析结果。 */
export interface VersionXmlInfo {
/** <version value="N"> 整数(缺失/失败 → 0)。 */
version: number;
/** 子 <game name="..."> 游戏名(缺失 → '')。 */
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') {
// 根 <game> 无 name 属性;只有子 <game id 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;
}
}
@@ -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) 进大厅