应生成与strings.xml严格对齐的strings_hash.json映射文件,含多语言键值对及基于UTF-8字节排序的SHA-256校验哈希,用于CI校验、native直读和Unity导入,生成时机须在lintVital之后、package之前。

直接在构建阶段生成映射哈希表是可行的,但关键不在“生成哈希表”本身,而在于**让哈希表内容可复用、可验证、且不破坏 Android 资源编译流程**。硬编码或运行时构建 Hashtable 会导致资源 ID 绑定失效、混淆异常、增量编译失败——这不是性能问题,而是构建链路断裂。
为什么不能在 build.gradle 里 new Hashtable() 并塞进 assets?
Android 构建系统(AGP)在 mergeResources 和 processResources 阶段已锁定资源索引,此时写入任意 Hashtable 或 HashMap 实例到 APK 的 assets/ 下,会导致:
- 资源引用仍走
R.string.xxx编译期 ID,你塞进去的哈希表完全不参与资源解析 - 若尝试用该哈希表替代
Context.getString(),会绕过系统本地化逻辑(如配置变更监听、字体适配、Plurals 支持) -
Hashtable是 .NET 类型,Android Gradle 插件默认不支持 C# 运行时;Java 的Hashtable又无法序列化为紧凑二进制供 native 层快速加载
真正该生成的是语言键值对的编译期哈希快照
目标不是运行时哈希结构,而是**一份与 strings.xml 严格对齐、带校验、可 diff 的轻量级映射文件**,用于后续自动化校验或 native 模块直读。推荐生成 strings_hash.json:
- 路径固定为
src/main/assets/strings_hash.json,由插件在generate后触发ResValues - 内容为扁平 JSON:
{"zh-CN":{"login":"登录","error_network":"网络错误"},"en-US":{"login":"Login","error_network":"Network error"}} - 哈希值字段(如
"_hash": "a1b2c3...")应基于所有语言 value 的 UTF-8 字节排序后计算 SHA-256,确保语义一致即哈希一致 - 避免嵌套结构或动态 key,否则无法被 C/C++ 的
rapidjson或 Rust 的serde_json零拷贝解析
Gradle 插件中如何安全注入该生成逻辑
必须避开 tasks.withType(GenerateLibraryRFileTask) 等 AGP 内部任务,改用生命周期感知方式:
- 监听
android.sourceSets.main.res.srcDirs变更,在afterEvaluate阶段注册一个Sync任务,依赖generateResValues - 读取
build/intermediates/incremental/下已合并的 XML(含所有语言),而非原始/mergeResources/merged.dir/values/ src/main/res/values-xx/ - 用
XmlParser解析每个values-xx/strings.xml,提取,按 name 归一化 key,过滤掉yyy translatable="false"条目 - 输出前做一致性检查:所有语言中缺失的 key 记为警告,不中断构建;但若某 key 在基准语言(如 zh-CN)存在而在 en-US 缺失,则标记为 error 并 failBuild = true
生成后的哈希表怎么用才不踩坑
这份 JSON 不是用来“查字符串”的,而是用来:
- CI 流程中比对上一版
strings_hash.json,自动识别新增/删除的 key,触发翻译平台同步 - 车载系统 native 层(如 HAL 模块)通过 mmap 直接读取,避免 JNI 调用开销——此时 key 必须是 ASCII,value 必须是 UTF-8,且禁止任何转义字符(如
\n) - Unity 的
Localization插件导入时,可将该 JSON 作为 source 映射表,自动补全缺失语言的空值,而不是靠人工 Excel 表格来回导出导入
最易被忽略的一点:生成时机必须在 lintVital 之后、package 之前。否则 Lint 报出的字符串未翻译警告,会和你刚生成的哈希表内容对不上——因为 Lint 读的是中间产物,而你的插件读的是最终合并结果。











