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:
co-authored by
Claude Opus 4.8
parent
cc5db07ddd
commit
01e4c0e99e
@@ -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';
|
||||
|
||||
@@ -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) 进大厅
|
||||
|
||||
@@ -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 ——
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user