修复子游戏/大厅每次启动都重复下载:version.xml 改用正则解析
现象:子游戏 zip 已下载解压、version.xml 落盘正常,但再次进入仍重新下载; 远程版本与本地一致本应跳过下载。真机诊断([VERXML-DIAG]/[SUBGAME-DIAG]): installed=true、version.xml 原文完整(<version value="155"/>)、gameId 正确解析为 a52y...,唯独 localVer 恒为 0 → 155<=0 判否 → 每次都判需更新而重下。大厅同理 (localVersion 恒 0,启动期每次重下整包)。 根因:VersionXml.parse 用 @kit.ArkTS xml.XmlPullParser,其 attributeValueCallbackFunction 对本文件 <version> 的 value 属性不稳定触发(同文件 <game> 的 id 却能取到),逻辑自相矛盾——value 无条件捕获却落空,说明该属性回调 未按预期触发。 修法:弃用 XmlPullParser,改确定性正则直接从原文抽 version/gameId/gameName,语义 等价 Android pullxml1,规避解析器回调时序/属性触发的不确定性。根 <game> 无属性 (紧跟 '>'),\s+ 自然跳过,只命中带属性的子 game。 验证:真机覆盖安装后再次进入三个老K,localVer=155、不再下载,直接进 H5。 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
db206068c0
commit
600378f723
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* version.xml 解析(契约 §5.3,框架 §8.3)。用 @kit.ArkTS xml.XmlPullParser。
|
||||
* version.xml 解析(契约 §5.3,框架 §8.3)。
|
||||
*
|
||||
* 结构:<game><agent id name/><game id name/><channel id name/><version value name/></game>
|
||||
* 用途:取 <version value="42"> 与远程 game_version 比较决定是否更新;取子 <game name="...">
|
||||
* 作 app_data 的 app_gamename。`value` 为 version 标签独有;`name` 多标签共有,故按
|
||||
* 所属标签(当前为子 game)区分抓取。
|
||||
* 作 app_data 的 app_gamename;取子 <game id="..."> 作大厅 gameid 为空时的回退匹配 key。
|
||||
*
|
||||
* 实现说明:早期用 @kit.ArkTS xml.XmlPullParser,实测其 attributeValueCallbackFunction 对
|
||||
* 本文件 `<version value="155">` 的 `value` 属性**不稳定触发**(同文件 `<game id>` 却能取到),
|
||||
* 导致 version 恒为 0、子游戏每次都误判需更新而重复下载。改用确定性正则直接从原文抽属性,
|
||||
* 语义等价于 Android `pullxml1`,规避解析器回调时序/属性触发的不确定性。
|
||||
*/
|
||||
import { xml, util } from '@kit.ArkTS';
|
||||
import { Logger } from 'common';
|
||||
|
||||
/** version.xml 解析结果。 */
|
||||
export interface VersionXmlInfo {
|
||||
/** <version value="N"> 整数(缺失/失败 → 0)。 */
|
||||
version: number;
|
||||
@@ -20,46 +20,36 @@ export interface VersionXmlInfo {
|
||||
}
|
||||
|
||||
export class VersionXml {
|
||||
private static readonly log: Logger = Logger.tag('VersionXml');
|
||||
// <version ... value="N" .../>:取版本号。允许属性间任意顺序/空白。
|
||||
private static readonly VERSION_RE: RegExp = new RegExp('<version\\b[^>]*\\bvalue\\s*=\\s*"([^"]*)"', 'i');
|
||||
// 子 <game <空白> ...>:根 <game> 无属性(其后紧跟 '>'),故 \s+ 自然跳过,只命中带属性的子 game。
|
||||
private static readonly GAME_TAG_RE: RegExp = new RegExp('<game\\s+[^>]*?>', 'i');
|
||||
private static readonly ID_RE: RegExp = new RegExp('\\bid\\s*=\\s*"([^"]*)"', 'i');
|
||||
private static readonly NAME_RE: RegExp = new RegExp('\\bname\\s*=\\s*"([^"]*)"', 'i');
|
||||
|
||||
/** 解析 version + gameName。属性回调紧随其开始标签,故用 currentTag 把 name 归属到 game 标签。 */
|
||||
/** 解析 version + gameName + gameId(任一缺失取默认值,绝不抛错)。 */
|
||||
static parse(xmlText: string): VersionXmlInfo {
|
||||
const info: VersionXmlInfo = { version: 0, gameName: '', gameId: '' };
|
||||
if (xmlText.length === 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;
|
||||
} else if (name === 'id' && currentTag === 'game') {
|
||||
// 子 <game id="..."> 的 id:大厅 gameid 回退匹配 key(根 <game> 无 id 属性)
|
||||
info.gameId = val;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
parser.parseXml(options);
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
VersionXml.log.w(`parse version.xml failed: ${err.message}`);
|
||||
return info;
|
||||
const verMatch: RegExpMatchArray | null = xmlText.match(VersionXml.VERSION_RE);
|
||||
if (verMatch !== null) {
|
||||
const n: number = parseInt(verMatch[1], 10);
|
||||
info.version = isNaN(n) ? 0 : n;
|
||||
}
|
||||
const gameMatch: RegExpMatchArray | null = xmlText.match(VersionXml.GAME_TAG_RE);
|
||||
if (gameMatch !== null) {
|
||||
const tag: string = gameMatch[0];
|
||||
const idMatch: RegExpMatchArray | null = tag.match(VersionXml.ID_RE);
|
||||
if (idMatch !== null) {
|
||||
info.gameId = idMatch[1];
|
||||
}
|
||||
const nameMatch: RegExpMatchArray | null = tag.match(VersionXml.NAME_RE);
|
||||
if (nameMatch !== null) {
|
||||
info.gameName = nameMatch[1];
|
||||
}
|
||||
}
|
||||
const n: number = parseInt(value, 10);
|
||||
info.version = isNaN(n) ? 0 : n;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user