安卓修改大师智改工坊 · 安卓修改大师经典版

经典版汉化自家应用:strings.xml 与资源替换教程

这是一篇纯技术、手把手的汉化教程。它不承诺一句话改完,而是把每一步摊开:反编译后看什么、字符串藏在哪、怎么翻译不溢出、资源 ID 怎么稳、回编签名怎么验、最后怎么导出 Android Studio 工程继续维护。适合已经拿到自家应用安装包、需要做中文界面的团队。

了解 安卓修改大师智改工坊

安卓修改大师智改工坊的 AI 版主张是「只需说话,就能让应用变成你想要的样子」,适合快速起草与批量尝试;而安卓修改大师经典版走的是手动精修路线:反编译、改 smali 与资源、回编、对齐、签名都由你逐步确认,结果可复现、可审计,适合汉化这种需要逐条核对文案、反复检查版式的活。两条路线不冲突,本文只讲经典版的手动流程。官网地址是 www.apkeditor.cn,产品介绍与下载页是 https://www.apkeditor.cn/ai-version.aspx 。

合规提醒:本教程只面向自有版权的应用,或已经取得书面授权、明确允许修改与分发的应用。请勿用于破解、去除付费限制、绕过签名校验或侵犯他人著作权,请勿用于破解任何第三方商业应用;也不要把修改后的安装包分发给未获授权的用户。汉化是本地化工作,不是破解手段,这条边界必须先划清。

一、先把边界划清:只汉化自家或已授权应用

在打开任何工具之前,先把“能不能改”这件事落到纸面。技术上能反编译,不等于法律上能改、能发。汉化会改动资源、清单甚至 smali,一旦对外分发,就已经进入复制与发行行为,必须确认权利基础。

1.1 三种可以做的情况

  • 自己公司开发并持有版权的应用。源码、商标、素材都在自己手里,汉化只是产品迭代的一部分,流程最清晰。
  • 委托开发、合同里写明可修改与分发的应用。合同、验收单、需求变更单都能作为授权凭证;如果合同只授权源码使用、没授权二次分发,就先补授权。
  • 开源许可证允许修改的应用。遵守许可证的署名、开源、同许可证分发等义务,保留版权声明与许可证文件,不要顺手删掉。

1.2 三种不要做的情况

  • 应用商店里下载的他人商业应用。无论汉化、换图标还是改文案,都不属于本地化,属于未经授权的修改与再分发。
  • 以绕过付费、会员、广告、风控为目的的改动。这类需求即使包装成“汉化”,也已经越界。
  • 需要破坏签名校验、完整性校验才能跑起来的改动。这说明应用本身不欢迎被改,应该停手,回到授权与源码渠道解决。

建议每个项目建一份“授权与范围记录”,写清应用名称、包名、版本号、授权方、授权范围(仅内部使用 / 可对外分发)、授权有效期、联系人,以及原始安装包的 SHA-256。以后团队换人、版本迭代,这份记录能省掉很多解释成本。

1.3 汉化不等于“什么都翻”

内容类型 是否翻译 说明
界面标题、按钮、提示、空状态 翻译 用户可见,优先处理
品牌名、产品名、商标 保留 按品牌规范,不音译、不缩写
URL、包名、类名、接口路径 不改 改了会直接坏
日志、埋点事件名、数据库字段 不改 给机器看的,不是给用户看的
用户协议、隐私政策 谨慎 涉及法律文本,应由法务确认,不能随手机翻
第三方 SDK 自带的界面 看许可 不要删除其版权声明,必要时联系 SDK 方提供中文包

还有一个容易被忽略的点:如果应用里包含第三方开源库、字体、图标、音视频素材,汉化时只动文案没问题,但如果你顺手替换了素材、删了许可证文件,就可能违反许可。稳妥的做法是把“文案替换”和“素材替换”分成两个变更集,分别审查、分别记录。

二、开工准备:环境、备份与术语表

汉化不是“打开文件就改”。准备工作做得越足,后面回编失败、装不上、文案对不齐的概率越低。安卓修改大师经典版是 Windows 桌面的手动精修工具,安装包约 13.74 MB,运行环境要求 ASP.Net 4.5+ 与 JDK 1.8+,支持手动反编译、修改 smali 与资源,也支持导出 Android Studio 工程与多渠道打包。把它当成“可控的手工台”,而不是黑盒按钮。

2.1 工具清单

  • 安卓修改大师经典版:建项目、反编译、编辑资源与 smali、回编、对齐、签名、装机。
  • Android SDK Build-Tools:至少包含 zipalign 与 apksigner,版本尽量与目标 SDK 匹配。
  • adb:安装、启动、看日志、拉截图。真机比模拟器更接近真实字体与布局。
  • Git Bash 或 PowerShell:批量搜索、比对、生成清单。经典版自带文件管理工具,但批量文本统计还是命令行更快。
  • UTF-8 编辑器:VS Code、Notepad++ 均可,保存时确认“UTF-8、无 BOM”。
  • 表格工具:维护术语表与翻译记忆,CSV 最省事,能进 Git。
  • 测试设备:一台小屏手机、一台主流手机、一台平板,覆盖字体缩放与横竖屏。

2.2 目录怎么放

localize-project/
  source/
    app-release.apk
    app-release.apk.sha256
  decoded/                 # apktool 反编译输出
  build/                   # 回编、对齐、签名产物
  keys/
    release.jks            # 不进 Git
  docs/
    authorization.md       # 授权与范围记录
    glossary.csv           # 术语表
    inventory.csv          # 待翻译清单
    screens/               # 验收截图
  logs/
    decode.log
    build.log
    install.log

目录分开的好处是:原始包永不被覆盖,反编译产物可以随时删掉重来,构建产物和密钥不混在源码里,日志能复盘。经典版每个项目也会有自己的工作目录,建议把它的项目目录直接放在这套结构下,或者至少把 source、build、logs 三处对齐,避免“改了半天的包找不到原始版本”。

2.3 备份与指纹

第一件事是备份原始 APK,并记录指纹。Windows 自带 certutil,Git Bash 里可以用 sha256sum:

certutil -hashfile source\app-release.apk SHA256
sha256sum source/app-release.apk

把输出写进 source/app-release.apk.sha256,后面每次验收都重新算一次,确认你测的就是你改的那一版。接着记录原始应用的关键信息:包名、versionCode、versionName、minSdk、targetSdk、启动 Activity、签名证书 SHA-256、支持的语言列表。这些信息在经典版的项目详情里能看到一部分,也可以直接用 aapt2 读:

aapt2 dump badging source\app-release.apk
aapt2 dump permissions source\app-release.apk
apksigner verify --print-certs -v source\app-release.apk

签名证书的 SHA-256 一定要记。后面如果你用错密钥签名,安装时会报签名冲突;如果你用对了密钥,老用户可以直接覆盖升级,聊天记录、登录态、本地课程都能保留。反过来,用错密钥再想覆盖,只能卸载重装,数据全丢。

2.4 先建术语表,再动第一行文案

汉化最容易翻车的不是不会翻,而是同一个人前后翻得不一样,或者三个人各翻一套。术语表不需要很复杂,一张 CSV 就够:key、英文原文、中文定稿、词性/场景、负责人、备注。先定“动作类”和“名词类”的基本盘,比如“保存”不写“存储”,“登录”不写“登入”,“设置”不写“设定”。

英文原文 建议中文 不采用的写法 原因
Save 保存 存储、暂存 按钮统一用“保存”,名词场景才用“存储空间”
Sign in / Log in 登录 登入、登陆 “登陆”是错别字,统一为“登录”
Settings 设置 设定、偏好 与系统设置叫法一致
OK 确定 好的、是 对话框按钮用词要一致
Cancel 取消 返回、关闭 “取消”是动作,“返回”是导航,不能混
Delete / Remove 删除 / 移除 一律用“删除” 删除是销毁,移除只是移出列表,语义要分开
Download 下载 获取、缓存 “缓存”是机制,“下载”是用户动作

术语表定稿后再开始翻译,效率反而更高:遇到新词先查表,查不到就补表并注明场景,下一次就不会再纠结。翻译记忆也可以放进同一张表,按 key 排序,回编失败时能快速定位是哪一条改动引入的。

开工前检查清单

  • □ 已确认自有版权或书面授权,范围包含修改与分发
  • □ 原始 APK 已备份,SHA-256 已记录
  • □ 包名、版本号、启动 Activity、签名证书指纹已记录
  • □ release 密钥可用,口令放在环境变量里
  • □ 术语表第一版已定稿
  • □ 经典版、Build-Tools、adb 均可用
  • □ 测试设备与字体缩放方案已确定

三、第一步:反编译,先看清工程结构

反编译的目的不是“把源码偷出来”,而是把 APK 还原成可编辑的工程目录:清单一目了然,资源按类型分好,smali 按包名展开。你要做的是在这堆文件里建立地图,知道每一类文案大概在哪个位置,而不是一上来就全局替换。

3.1 做什么

在经典版里新建项目、导入 APK,然后执行反编译;如果你习惯命令行,等价的操作是:

apktool d -f -p framework "source\app-release.apk" -o decoded

参数含义:-f 覆盖已有输出目录,-p 指定框架目录,-o 指定输出目录。第一次处理某个厂商的包,如果报资源属性找不到,通常需要先把对应的 framework-res.apk 装进框架目录,再用同一个 -p 回编。反编译和回编必须使用同一套框架,否则资源 ID 和属性解析会对不上。

如果只想先看文案,不想碰代码,可以加 --no-src,保留 classes.dex 不展开 smali;反过来只想看代码,可以加 --no-res。汉化阶段建议完整反编译一次,宁可多花几十秒,也不要在需要改 smali 时重新来一遍。

3.2 看到什么

正常反编译完成后,输出目录大致如下。不同应用会有增删,但骨架基本一致:

路径 内容 汉化关注点
AndroidManifest.xml 包名、组件、权限、应用名 android:label、meta-data、启动页
apktool.yml 版本、SDK、压缩规则 versionCode/versionName、minSdk/targetSdk
res/values/ strings.xml、arrays.xml、plurals.xml、dimens.xml、colors.xml 默认语言文案,最主要战场
res/values-zh-rCN/ 等 已有语种的资源覆盖 新增中文目录,避免覆盖默认值
res/layout/ 界面结构 android:text、hint、title、contentDescription
res/menu/、res/xml/、res/navigation/ 菜单、偏好设置、导航图 android:title、android:summary、label
smali/、smali_classes2/ DEX 反汇编后的代码 const-string 硬编码、资源 ID 引用
assets/、res/raw/ 网页、JSON、离线数据 WebView 页面文案、配置里的提示语
lib/、original/、unknown/ so 库、原始清单、未识别文件 一般不动,动了容易崩

先打开 apktool.yml 看一眼版本信息。apktool 会把 versionCode、versionName、minSdkVersion、targetSdkVersion 记录在这里,回编时以它为准。如果你只在 AndroidManifest.xml 里改版本号,回编后可能被 yml 里的旧值覆盖,最后装上去还是旧版本。改版本号时两处都看一眼,回编后用 aapt2 再确认一次。

再看 res/values/ 下到底有什么。有的项目把字符串全放在 strings.xml,有的拆成 strings_home.xml、strings_course.xml,还有的用 values/strings.xml 加 values/strings_*.xml 的组合。不要假设只有一个文件,先用目录列表把全部 values 文件过一遍。

3.3 怎么验证反编译成功

  1. 命令退出码为 0,控制台没有 ERROR 级别的失败信息;警告可以保留,但要逐条看懂。
  2. AndroidManifest.xml、res/、smali/ 三个关键目录都存在,且 res/values/strings.xml 能正常打开。
  3. 原始 APK 的 SHA-256 没有变化,source 目录里还是那一份。
  4. 随机打开两三个 layout,确认 XML 没有被截断,文件末尾有闭合标签。
  5. 如果项目很大,确认 smali_classes2、smali_classes3 都存在,没有只解出第一段代码。

3.4 失败怎么查

  • 报资源表解析失败、Could not decode arsc。先确认 apktool 与 aapt2 的版本是否匹配,再确认是否缺少对应 SDK 的框架文件。换一个已知能处理同类包的版本重试,不要在同一堆报错里反复猜。
  • 报 UndefinedResObject 或资源 ID 找不到。多数是资源表本身不完整,或者反编译工具版本偏旧。保留完整日志,换版本重试;仍然失败就换工具链,不要在残缺目录上继续改。
  • 中文显示乱码。先确认文件本身是 UTF-8,再看终端编码。编辑器里显示正常、命令行 grep 乱码,通常只是终端代码页问题,不代表文件坏了。
  • 出现 9-patch 相关警告。类似“找不到 9patch 块、已重命名为普通 png”的提示,是 apktool 的常见兜底,不一定是真错误。判断标准是回编能否成功、图片显示是否正常,而不是警告本身。
  • 反编译中途卡住或内存不足。大包会吃内存,先把 JAVA_OPTS 调到 4G 左右再试,并确认磁盘剩余空间足够。经典版有自己的日志工具,失败时先保存日志再重试。

经验:反编译成功后先做一次“空回编”——不改任何文件,直接 b 一遍、对齐、签名、安装。能跑通再开始改文案。这样后面出问题,你能确定是文案改动引起的,而不是工具链本身就不通。

反编译后的资源目录与字符串编辑界面示意

图 1:反编译后先建立目录地图,再决定改哪里

四、第二步:strings.xml、arrays、plurals 与布局内联文本

安卓应用的文案并不只躺在 strings.xml 里。它至少分布在五类位置:值资源、数组、复数、布局/菜单/偏好设置 XML 的内联属性,以及代码里的硬编码。汉化要做的第一件事是“盘点”,把所有用户可见的英文找出来,标注它属于哪一类,再决定用资源覆盖还是改代码。

4.1 文案的五个藏身处

位置 典型文件 是否随系统语言切换 处理方式
普通字符串 res/values/strings.xml 是 在 values-zh-rCN 中覆盖
字符串数组 res/values/arrays.xml 是 逐项翻译,保持顺序
复数形式 res/values/plurals.xml 是 保留 other,中文按 other 取
布局内联文本 res/layout/*.xml、menu、xml、navigation 否,写死就不变 迁到字符串资源,再翻译
代码硬编码 smali/*.smali、assets/、res/raw/ 否 改字面量,或改为引用资源

还有第六类:服务端下发的文案。首页 banner、活动说明、课程简介如果来自接口,改 APK 是没用的,必须在后端配置多语言字段。遇到这类内容,先在清单里标记“服务端”,不要浪费时间去 smali 里翻,翻到了也只是本地兜底提示。

4.2 strings.xml 的结构与规则

打开 res/values/strings.xml,你看到的是一个个 <string> 节点,name 是 key,节点里是默认语言文案。下面是一段典型结构:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <string name="app_name" translatable="false">DemoClass</string>
    <string name="welcome_back">Welcome back, %1$s</string>
    <string name="lesson_progress">Lesson %1$d of %2$d</string>
    <string name="offline_hint">Downloaded lessons can be played offline.</string>
    <string name="delete_confirm">Delete this lesson?</string>
</resources>

几个属性必须看懂,否则翻译完会出各种怪问题:

  • translatable="false":告诉工具这条不翻译。品牌名、URL、技术标识通常加这个属性;但如果你要翻译应用名,千万别给它加,否则你在中文目录里写了也不会生效。反过来说,看到这个属性先确认它是真的不该翻,而不是前人偷懒加的。
  • formatted="false":表示这条字符串不按格式化串处理。文案里如果有单独的百分号(比如“50%”),又不想写成“50%%”,可以用它;但如果文案里有 %1$s 这类占位符,就不能随便关掉格式化。
  • 占位符:%1$s 是字符串,%1$d 是整数,%2$d 是第二个参数。翻译时可以调整顺序(中文常说“共 12 节,当前第 3 节”),但编号、类型、数量不能变。少一个占位符,运行时就可能抛格式化异常;多一个,可能显示 null 或直接崩。
  • 转义:英文里的单引号要写成 \',或者把整条文案用双引号包起来;& 要写成 &amp;,< 要写成 &lt;。中文文案里出现英文品牌名时尤其容易踩到 &。
  • 首尾空格:需要保留时用双引号把整条内容包起来,例如 " 已保存 "。不加引号,编译时会被裁掉。
  • 以 @ 或 ? 开头:会被当成资源引用。文案确实要以 @ 开头时,写成 \@ 转义。

如果原文件用了 <xliff:g> 包住占位符,不要拆掉这层包装。它的作用是告诉翻译人员“这里的东西不要动”,也能避免工具把占位符当成普通文本改写。你只需要翻译标签外的文字,标签内的 id、示例值原样保留。

4.3 arrays 与 plurals:别只顾着单条字符串

字符串数组常见于星期、月份、学科分类、筛选标签。结构如下:

<string-array name="week_days">
    <item>Monday</item>
    <item>Tuesday</item>
    <item>Wednesday</item>
    <item>Thursday</item>
    <item>Friday</item>
    <item>Saturday</item>
    <item>Sunday</item>
</string-array>

翻译数组时,顺序和数量通常不能变。代码很可能用索引取值,比如 weekDays[Calendar.DAY_OF_WEEK - 1];你如果把“Monday”挪到周日的位置,界面就会显示错的日子。只有在确认数组只用于展示、不参与索引计算时,才可以调整顺序。数组里如果 item 写的是 @string/xxx 引用,就去翻译对应的字符串,不要在数组里再写一份中文,否则以后改一处漏一处。

复数形式则用 <plurals>,按 quantity 分成 one、other 等:

<plurals name="lesson_count">
    <item quantity="one">%d lesson</item>
    <item quantity="other">%d lessons</item>
</plurals>

中文的复数规则只用 other,所以中文目录里写 <item quantity="other">%d 节课</item> 就能覆盖所有数量。稳妥起见把 one 也保留,写成同样的“%d 节课”,这样某些工具链的静态检查不会报缺失,运行时也不会因为取不到条目而回退到默认语言。注意:中文没有“1 节课 / 2 节课”的形态变化,不要硬造“节课们”这种说法。

4.4 布局内联文本:先迁出,再翻译

很多老项目为了省事,直接把文案写在布局里:android:text="Continue learning"。这类文本不会随系统语言切换,也没法在 values-zh-rCN 里覆盖。正确做法是把它迁到字符串资源:先在默认 strings.xml 里加一条 key,再把属性改成 android:text="@string/continue_learning",最后在中文目录里翻译这条 key。

为什么不直接把布局里的英文换成中文?因为那样只解决了“当前这一版看起来是中文”,没有解决“以后要不要支持英文”“同一个词在三个页面怎么写”“改一处漏两处”的问题。迁到资源后,文案有唯一来源,能查重、能统计、能交给翻译表,也能在 Android Studio 的翻译编辑器里看到缺口。

先把所有内联文本找出来。Git Bash 下可以这样扫:

grep -rn --include="*.xml" -E 'android:(text|hint|title|summary|contentDescription)="[^@?]' res/
grep -rn --include="*.xml" -E 'app:(title|subtitle|helperText)="[^@?]' res/

PowerShell 下等价的做法是:

Get-ChildItem res -Recurse -Filter *.xml | Select-String -Pattern 'android:(text|hint|title|summary|contentDescription)="[^@?]'

扫描结果要分三类处理:第一类是真正的英文文案,迁资源并翻译;第二类是数字、符号、单个字母(比如“%”“+”“A”),可以保留内联;第三类是占位用的示例文本,如果带 tools:text 前缀,只是设计预览,不参与运行,直接忽略。不要把 tools:text 当成真文案翻译,也不要误删它,否则设计视图会变空。

菜单、偏好设置、导航图也属于同一类。res/menu 里常见 android:title="Settings",res/xml 的 Preference 里常见 android:title 与 android:summary,res/navigation 里常见 android:label。这些都要迁,否则主界面是中文,点进设置又变成英文,体验很割裂。

AndroidManifest.xml 里的 android:label 也要看一眼。如果写的是硬编码英文,桌面图标下的名字就不会跟着中文变;如果引用的是 @string/app_name,就只需翻译 app_name。内部工具通常希望应用名固定中文,可以把默认 values 里的 app_name 直接写成中文;教学 App 如果以后要出海,则保留英文默认值,在 values-zh-rCN 里写中文名。

4.5 怎么验证这一层改对了

  1. XML 合法性。用 xmllint --noout res/values-zh-rCN/strings.xml 或 Android Studio 打开检查,确保没有未闭合标签、非法字符。
  2. key 对齐。把默认 strings.xml 与中文 strings.xml 的 name 集合做差集,缺的 key 就是会回退英文的地方。可以用一个小脚本,几十行就够。
  3. 占位符对齐。对每条同名 key 提取 %(\d+\$)?[sd],比较原文与译文的数量和类型。顺序可以变,集合必须一致。
  4. 内联文本归零。再跑一次 4.4 的扫描命令,确认剩下的内联文本都是数字、符号或已确认保留的内容。
  5. 编译一次。aapt2 能过,说明转义、占位符、资源名这些基础问题都没有。

失败排查可以按报错直接对号入座:报 “unescaped apostrophe” 就是单引号没转义;报 “Multiple substitutions specified in non-positional format” 就是混用了 %s 和 %1$s,统一改成带编号的写法;报 “Duplicate resource” 就是同一个文件里出现了两个同名 string;报 “resource string/xxx not found” 就是引用名拼错,或者中文目录里删了默认目录还在引用的 key。中文显示成问号,多半是文件保存成了 GBK;界面还是英文,先查 key 是否缺失,再查设备语言是否真的切到了中文。

布局内联文本与外链资源替换示意

图 2:内联文本先迁到资源,后面才能按语言覆盖

五、第三步:硬编码字符串在 smali 里的查找与替换

资源文件改完,界面里往往还剩几句英文。它们要么来自布局内联,要么来自代码里的字面量。前者上一节已经处理,后者就在 smali 的 const-string 指令里。汉化不需要读懂整个方法,但必须能准确定位、成对修改、验证不崩。

5.1 先做一份硬编码清单

不要一上来就全局替换。先把所有字符串字面量导出来,按出现次数排序,人工筛出“看起来像给用户看的”那部分。Git Bash 下可以这样统计:

grep -rhoE 'const-string(/jumbo)? v[0-9]+, "[^"]{2,}"' smali*/ | sort | uniq -c | sort -rn > const_strings.txt

导出的清单里会混进大量非文案内容:JSON 字段名、接口路径、SharedPreferences 的 key、Intent action、正则表达式、字体文件名、埋点事件名。判断标准很简单:这句话如果出现在屏幕上,用户能不能看懂、需不需要看懂。能且需要,才是文案;否则一律不动。日志字符串也不用翻,它面向开发,翻了反而增加排错成本。

按模块分组会更高效。比如课程播放器相关的类名通常含 player、lesson、course;个人中心含 profile、account、settings;支付相关含 pay、order。先按包名过滤,再逐类看,比在几万行 smali 里盲搜快得多:

grep -rn --include="*.smali" 'const-string.*"Continue learning"' smali*/
grep -rn --include="*.smali" 'const-string.*"Download complete"' smali*/

5.2 一条 smali 指令长什么样

典型片段如下。第一行把字符串放进寄存器 v2,第二行把它作为参数传给一个显示提示的方法:

const-string v2, "Lesson downloaded"
invoke-virtual {p0, v2}, Lcom/example/player/LessonActivity;->showToast(Ljava/lang/String;)V

把引号里的英文换成中文即可,寄存器、方法签名、指令助记符都不要动。中文可以直接以 UTF-8 写入,smali 编译器能识别;如果编辑器或终端对中文支持不好,也可以写成 Unicode 转义,例如“下载完成”写成 "\u4e0b\u8f7d\u5b8c\u6210"。两种写法等价,选团队里不容易出错的那种。

还要注意 const-string/jumbo。当字符串池超过 65535 项时,编译器会用 jumbo 变体,指令后多一个 32 位索引。你只需要改引号内容,不要把 jumbo 改成普通 const-string,也不要反过来,否则回编时报索引越界或验证失败。

5.3 替换前必须确认的三件事

  • 这个字面量是不是被多处复用。同一句“Download complete”可能在课程下载、资料下载、离线包更新三个地方共用。改之前先统计出现次数,逐处看上下文;如果三处都要中文,一起改没问题;如果有一处是日志或接口字段,就不能全局替换。
  • 有没有格式化占位符。如果字面量里有 %s、%d,替换时必须保留,数量和类型一致。smali 里常见 String.format 调用,少一个占位符就会抛异常。
  • 是不是拼接句。英文常用“Downloaded ” + count + “ lessons”拼句子,中文语序不同,逐段替换会得到“已下载 3 节课”还算通顺,但“Lesson ” + title + “ is ready”就可能变成“课程 数学 已就绪”。遇到拼接,优先找整句的格式化串;找不到就分别替换片段,并在真机上把该场景走一遍,别只看代码。

5.4 资源 ID 调用与硬编码的区别

smali 里还有一种“看起来像硬编码、其实是资源引用”的写法:先把资源 ID 装进寄存器,再调用 getString。例如 const v0, 0x7f0f0012 后面跟 getString 调用。这类不需要改 smali,应该去资源文件里改对应 ID 的字符串;改完 ID 不变,代码自然生效。判断方法是对照 public.xml,看 0x7f... 这个值对应的 type 和 name。

如果你新增了字符串并希望 smali 直接引用,必须保证 ID 稳定:在 public.xml 里给新条目分配一个同类型下未被占用的 ID,再在 smali 里使用这个值。不要凭空写一个 0x7f0f00ff,也不要让构建工具自己分配后再去猜。ID 冲突或越界,轻则显示错文案,重则 Resources$NotFoundException。

5.5 改完怎么验证

  1. 回编能过。smali 语法错误通常会在 b 阶段直接报行号,按行号看是否多删了冒号、引号、寄存器。
  2. 安装能启动。启动就崩,先用 adb logcat -s AndroidRuntime:E 抓栈,看崩溃点是不是你改过的类。
  3. 走到那个场景。Toast、对话框、空状态这些文案不会在首页出现,必须点进对应流程。逐条对照清单打勾。
  4. 界面层再扫一遍。用 adb shell uiautomator dump 导出当前界面树,搜索英文字母,能发现遗漏的按钮、标题、提示。
  5. 回归关键路径。登录、首页、列表、详情、下载、支付(如果有)、退出,至少各走一遍,确认没有因为字符串长度变化导致点击区域错位。

边界提醒:如果字符串在 smali 里是加密的、拼装的、或者需要先解密再显示,不要试图在汉化阶段破解它的保护逻辑。正确做法是回到自家源码工程改资源,或者请原开发在代码层提供多语言支持。硬碰硬改加密逻辑,既容易崩,也容易越界。

在 smali 目录中检索字符串字面量

图 3:先导出字面量清单,再按模块逐条确认

六、第四步:翻译、术语统一与长度控制

到这一步,文案已经全部集中到资源文件里,硬编码也有清单。接下来才是“翻得对不对、放不放得下”的问题。汉化质量由三件事决定:术语是否统一、语气是否像人话、长度是否适配控件。三者里最容易返工的是长度,因为它不是看文本能看出来的,必须上设备。

6.1 翻译流程:先表后文,先审后并

  1. 导出待翻译清单。从默认 strings.xml、arrays.xml、plurals.xml 以及迁移后的内联文本里,导出 key、英文原文、所在页面、备注,形成 CSV。
  2. 按场景分组。按钮、标题、提示、错误、空状态、引导页分开翻。按钮要短,错误提示要把“发生了什么、怎么办”说清,空状态要给出下一步动作。
  3. 套术语表。能用表里的词就不新造;确实需要新词,先在表里补一行,注明场景和不用什么,再继续。
  4. 回填 XML。建议用脚本从 CSV 生成 strings.xml,避免手工复制漏行、错位、破坏转义。脚本要把 &、<、引号、占位符原样保留。
  5. 二次审校。一个人通读中文界面截图,一个人对照英文原文查漏译、错译、术语漂移。两个人比一个人可靠。
  6. 冻结版本。回编前给 strings.xml 打一个 Git tag 或记录 SHA-256,出问题能回到上一版。

如果是团队协作,建议把“翻译”和“版式调整”分成两个提交。翻译提交只改文本,版式提交只改布局属性。这样回归时能快速判断是文案变长导致的溢出,还是布局改动导致的错位。

6.2 中文文案的几条硬规则

  • 统一全角标点。中文句子用“,。!?”和“:”,不要混入英文逗号句号;括号用中文全角,除非括号里整段是英文或代码。
  • 中英文之间留空格。“打开 APK 文件”比“打开APK文件”更好读;“Android 10 及以上”同理。品牌名、版本号、单位保持原样。
  • 省略号用“……”,不要用三个英文句点;破折号用“——”,不要用两个连字符。
  • 按钮不加句号。“保存”“取消”“立即下载”后面不加标点;提示句、说明句末尾加句号;标题不加句号。
  • 动词优先。按钮用动词开头,菜单用名词,状态用形容词或短句。不要把英文名词堆成中文长定语,比如“课程离线下载管理”不如“离线课程”。
  • 错误提示要说人话。“网络异常,请检查网络后重试”比“Error: Network is unreachable”有用;如果能给出下一步按钮,比纯文字更好。
  • 不翻技术标识。包名、类名、接口字段、日志 tag、文件扩展名、快捷键组合都保留原文;需要解释时放在括号里。
  • 同一动作同一词。“删除”就是删除,“移除”只用于从列表移出;不要一会儿“清除缓存”一会儿“清理缓存”。

还有一个常被忽视的细节:中文没有词间空格,但英文品牌名、数字、单位混排时,阅读节奏会变。比如“已下载3节课”在手机上看很挤,写成“已下载 3 节课”更清楚;而“100MB”通常不加空格。团队可以约定“数字与中文之间加空格、数字与单位之间不加”,统一执行即可。

6.3 长度预算:先给控件定上限

英文转中文,字符数通常会减少,但这不代表一定放得下。中文字形更方、字重更实,同样的宽度下视觉更满;加上系统字体缩放、加粗、行高变化,原本刚好的按钮就可能被截断。更麻烦的是反过来:如果你以后要把中文再译成英文,字符数会增加三成左右,版式必须提前留余量。

控件类型 英文示例 建议中文字数 风险点
底部导航 Home / Courses / Me 2~4 字 固定五等分,字多会被截
顶部 Tab All / In progress / Finished 3~6 字 固定模式会压缩,滚动模式更稳
主按钮 Continue learning 2~6 字 固定 96dp/120dp 宽度最常见
对话框按钮 Cancel / Delete 2~4 字 两个按钮并排,长文案会换行
列表主标题 Introduction to Algebra 8~16 字 单行省略会丢信息,建议两行
列表副标题 12 lessons · 3 hours 10~20 字 与图标、箭头抢宽度
Toast Download complete 6~20 字 太长约两行,容易被忽略
引导页正文 Learn at your own pace... 30~60 字 与插图争高度,小屏要压行数

这张表不是硬性标准,而是评审时的“预警线”。超过上限,先问自己能不能用更短的说法;不能,就去改布局,而不是硬塞。用户不会因为少了一个“请”字而误解,但会因为按钮被截成“保存并退…”而不敢点。

6.4 长文本溢出的六种表现与对应修法

  1. 按钮文字被截断或换行。常见于固定 layout_width="96dp" 的按钮。优先把宽度改成 wrap_content 并加 minWidth,或缩短文案;不要让按钮变成两行。
  2. 列表标题只显示一半。如果布局写了 singleLine="true" 或 maxLines="1",要么允许两行,要么把标题缩短到一眼能看懂。列表项高度固定时,允许两行还要同步加高。
  3. Tab 挤成一团。TabLayout 的固定模式会平均分配宽度,中文一长就互相挤压。改成可滚动模式,或者把标签缩短到 2~4 字;不要靠缩小字号解决,小字号在户外根本看不清。
  4. 底部导航标签消失。五个入口平分宽度,每个只有几十 dp。中文标签控制在 2~4 字,必要时只保留图标加短词,完整含义放到页面标题里。
  5. 对话框按钮并排溢出。两个按钮各占一半,长文案会换行甚至挤出屏幕。把次要按钮改成“取消”,主按钮用动词短语;仍然放不下就改成上下排列。
  6. 9-patch 背景被文字撑破。背景图有固定的内容区,文字超过内容区就会压到边框。要么缩短文案,要么重新做一张内容区更大的背景图,不要直接把文字缩小到 10sp。

修布局时优先动“容器”而不是“字号”:给 layout_weight 留伸缩空间,给 maxWidth 设上限,给 ellipsize="end" 做兜底。字号一旦为了适配而缩小,其他页面也会显得不协调,而且违背无障碍阅读的最小字号要求。

6.5 上设备量:三种压力测试

第一,字体缩放。把系统字体调到最大,很多“看起来刚好”的布局会立刻暴露。可以用命令快速切换:

adb shell settings put system font_scale 1.3
adb shell am force-stop com.example.teaching
adb shell am start -W -n com.example.teaching/.ui.SplashActivity

测完记得改回 1.0,再做一次基准截图。第二,小屏与低密度。用 wm 命令临时改分辨率和密度,模拟老设备或低端机:

adb shell wm size 720x1280
adb shell wm density 320
adb shell wm size reset
adb shell wm density reset

第三,长文本注入。中文通常比英文短,为了测最坏情况,可以临时把几条关键文案替换成两倍长度的同一句话(比如在句尾重复一遍),单独打一个测试包,看按钮、Tab、列表标题会不会溢出。这个包不要发出去,只用于版式压力测试。测完把 strings.xml 还原,再打正式包。比“凭感觉觉得放得下”可靠得多。

截图建议用“命令截图 + 拉回本地”,避免 Windows 重定向把 PNG 写坏:

adb shell screencap -p /sdcard/shot_home.png
adb pull /sdcard/shot_home.png docs\screens\shot_home.png

每张截图按“页面_语言_字体缩放_屏幕”命名,验收时一眼能对上。字体也要看一眼:如果应用自带只含拉丁字形的自定义字体,中文会回退到系统字体,可能出现字重不一致、基线偏移。遇到这种情况,要么换一套含中文字形的字体,要么在中文资源里取消自定义字体,用系统默认。

6.6 这一层的验证清单

  • □ 术语表里的词在界面中全部一致,没有同义词混用
  • □ 占位符数量、类型、顺序检查通过,运行时无格式化异常
  • □ 按钮、Tab、底部导航在字体 1.3 倍下不截断
  • □ 小屏 720p 下对话框按钮不换行、不重叠
  • □ 列表标题最多两行,省略号位置合理
  • □ 中文标点、空格、省略号风格统一
  • □ 错误提示能说明原因与下一步动作
  • □ 无障碍朗读的中文 contentDescription 已补

七、第五步:locale 目录、资源 ID 与 public.xml

文案翻好了,放到哪个目录、会不会覆盖错、ID 会不会乱,是这一步的核心。Android 的资源系统按“限定符”匹配目录,匹配不到就逐级回退;而 smali 里的资源引用是数字 ID,ID 一旦漂移,代码就会拿到别的资源。两件事都必须可控。

7.1 locale 目录怎么命名

目录 含义 适用场景
values/ 默认资源 所有语言的兜底,通常是英文
values-zh/ 泛中文 不想区分简繁时的兜底,容易误伤
values-zh-rCN/ 简体中文(中国大陆) 最常见的简体目录
values-zh-rTW/ 繁体中文(台湾) 需要繁体时单独维护
values-zh-rHK/ 繁体中文(香港) 用词与台湾有差异,建议分开
values-b+zh+Hans+CN/ BCP 47 写法,简体中文 新项目、需要按文字系统区分时用
values-b+zh+Hant+TW/ BCP 47 写法,繁体中文 同上,繁体侧

经典写法和 BCP 47 写法不要混着用。一个项目里要么统一用 values-zh-rCN,要么统一用 values-b+zh+Hans+CN,混用会让匹配结果难以预测,也会让后续维护的人困惑。aapt2 两种都支持,但团队要选一种作为规范。

7.2 匹配顺序与回退

系统按“最具体优先”匹配。以简体中文设备为例,查找 app_name 的顺序大致是:values-zh-rCN → values-zh → values。繁体设备则是 values-zh-rTW → values-zh → values。

这意味着:如果你只放了 values-zh 且内容用简体,台湾用户也会看到简体;如果你同时放了简体和繁体目录,就不会互相影响。只想做简中、暂时不管繁体,可以只建 values-zh-rCN,但要知道繁体设备会回退到默认英文,而不是简体。这是“没做”而不是“做错”,验收时要说明范围。

另一个容易踩的坑是限定符顺序。目录名里的限定符必须按系统规定的顺序排列,比如“语言在前、夜间模式在后”,写成 values-zh-rCN-night 是合法的,写成 values-night-zh-rCN 就可能报无效配置。不确定时,让 Android Studio 新建目录,或者照抄项目里已有的合法目录顺序。

7.3 默认值还是中文目录:先做一次选择

这里有两个路线,选错了后面会反复返工:

  • 路线 A:默认仍是英文,新增 values-zh-rCN。适合以后可能出海、或已经有海外用户的应用。英文用户不受影响,中文设备自动切中文。缺点是默认 strings.xml 里会一直保留英文,中文只存在于覆盖目录,检查缺译时要对比两个文件。
  • 路线 B:直接把默认 strings.xml 翻成中文。适合只服务中文用户、以后也不打算做多语言的应用。改动最少,所有设备都显示中文,不需要 locale 目录。缺点是以后要加英文时,得把中文迁到 values-zh-rCN,再把默认值恢复英文,是一次结构性改动。

自家教学 App 如果面向国内学生、短期没有出海计划,路线 B 更直接;如果公司有海外课程或合作方,路线 A 更稳。内部工具通常走路线 B,因为用户就是中文办公人群,没必要维护一套英文默认值。无论选哪条,都要在团队文档里写清楚,避免下一个人按另一条路线改,最后两套文案打架。

应用名要单独想一下:桌面图标下的名字由 android:label 决定,而它通常引用 @string/app_name。如果你希望不管系统语言是什么都显示中文名,就把默认的 app_name 写成中文;如果希望跟随语言,就保留英文默认、在中文目录写中文。内部工具一般选前者,教学 App 看品牌策略。

7.4 Android 13 之后的应用级语言

Android 13 起,系统支持“按应用设置语言”。但这不是自动的:应用需要在清单里声明 android:localeConfig="@xml/locales_config",并在对应 XML 里列出支持的语言,例如 <locale android:name="zh-Hans-CN"/>。如果原应用只声明了 en,你新增了中文资源,系统的“应用语言”列表里也不会出现中文,用户只能改整个系统语言。

这不是汉化必须做的,但如果你的应用要长期维护多语言,建议补上:清单加属性、res/xml 加 locales_config.xml、列出所有支持的语言。改完回编,安装后在系统设置的应用信息页看“语言”入口是否出现中文选项。没有出现,先检查清单属性是否被保留、XML 文件名是否一致、语言标签是否写对。

7.5 构建时不要把中文裁掉

如果项目后来导入 Android Studio 用 Gradle 构建,要检查语言过滤配置。旧版 AGP 用 resConfigs,新版用 androidResources.localeFilters;如果只列了 en,zh 会在打包时被剔除,装上去自然没有中文。用 apktool 直接回编不受这段配置影响,但一旦转 Gradle,就必须把 zh 加进白名单。验收时用 aapt2 dump resources 搜一下 zh 资源是否还在,比看 Gradle 配置更直接。

7.6 资源 ID 为什么会漂

APK 里的资源不是按名字访问的,编译后每个资源都有一个数字 ID,格式类似 0x7f0f0012。高字节标识包,中间标识类型(string、layout、id 等),低字节是序号。smali 里常见 const v0, 0x7f0f0012 这样的写法,就是直接引用数字 ID。

apktool 反编译时会生成 res/values/public.xml,把每个资源的名字和 ID 固定下来。回编时它按这份表分配 ID,所以只要你不动已有条目,ID 就稳定。反过来说,如果你删了一条 string、改了名字、或者调整了顺序又没有更新 public.xml,后续资源的序号就可能整体前移,smali 里的旧 ID 会指向别的字符串,出现“按钮文字变成另一句”“某个页面莫名崩溃”的现象。

<public type="string" name="app_name" id="0x7f0f0000" />
<public type="string" name="welcome_back" id="0x7f0f0001" />
<public type="string" name="lesson_progress" id="0x7f0f0002" />

所以规则只有一条:已有条目只改内容,不删、不改名、不重排;新增条目追加到末尾,并在 public.xml 里显式分配一个同类型下未被占用的 ID。如果你不确定哪个 ID 没被占用,先导出全部 public.xml 条目,按类型排序,找最大序号加一,再确认没有冲突。新增 layout、drawable、id 同理。不要手工编辑 resources.arsc,它不是给人改的。

7.7 用 dump 做前后对比

改之前导出一份资源表,改完再导出一份,对比 string 类型的 ID 与名字,是最稳的验证方式:

aapt2 dump resources source\app-release.apk > before_res.txt
aapt2 dump resources build\signed.apk > after_res.txt
fc before_res.txt after_res.txt

重点看三类差异:第一,原有 string 的 ID 是否保持不变;第二,新增的 string 是否落在预期 ID;第三,是否有资源被意外删除。文本差异可以很多,ID 差异必须为零(除了你明确新增的条目)。如果发现原有 ID 变了,先回到 public.xml 检查,不要继续往下签名,否则装上去可能“看起来能跑”,但某个冷门页面一点就崩。

布局里的 view id 也一样。smali 里 findViewById 用的也是数字,ID 漂了就会拿到 null,随后空指针。汉化一般不动布局结构,只改 text 属性,风险不大;但如果你为了修溢出改了控件、增删了 view,就要重新核对 id 表。

按语言限定的资源目录树示意

图 4:locale 目录按最具体优先匹配,缺失时逐级回退

八、第六步:回编、对齐、签名与安装验收

资源改完只是“文本层面完成”,真正能不能用,要看回编后的包能不能装、能不能启动、签名对不对。这一步建议按固定顺序执行:先回编,再对齐,再签名,再校验,最后安装。顺序错了,比如先签名再对齐,v2 签名会失效;签名后再改 zip 内容,整个包都得重来。

8.1 回编前先做四项检查

  1. XML 全部合法。所有改过的 strings、arrays、plurals、layout 都能被解析,没有漏闭合、没有裸 &、没有未转义引号。
  2. key 不重复、不缺失。同名 string 只出现一次;默认目录里被引用的 key,在中文目录里要么有对应翻译,要么明确接受回退。
  3. public.xml 与改动一致。没有删除或重命名已有条目;新增条目都有明确 ID。
  4. 版本号已决定。内部测试可以沿用版本号,对外分发必须递增 versionCode;versionName 按团队规则改。apktool.yml 与 AndroidManifest.xml 两处都确认。

回编命令很直接:

apktool b decoded -o build\unsigned.apk

如果反编译时用了 -p 指定框架目录,回编时也要带同一个 -p。框架不一致时,常见表现是资源属性解析失败、二进制 XML 生成异常,或者回编成功但安装后界面错乱。看到这类问题,先核对框架,不要先怀疑文案。

8.2 回编报错怎么对号入座

报错关键词 常见原因 处理方式
resource string/xxx not found 引用名拼错、key 被删、目录放错 全局搜 key,确认默认目录存在
No resource identifier found for attribute 框架版本不匹配,属性来自新 SDK 换匹配的框架目录,反编译与回编保持一致
Invalid configuration / invalid qualifier 目录限定符顺序错误 按规范顺序重命名目录
Duplicate resource 同一目录下同名 string/array 合并或删除重复项,保留唯一来源
XML parse error 标签未闭合、非法字符、编码错误 用 xmllint/AS 定位行号,改回 UTF-8
smali 相关 verify 错误 寄存器数量、指令格式、索引越界 对照原行恢复,只改引号内容

回编成功后,包名通常还是原来的,但文件名是 unsigned.apk,且没有签名。此时不要急着发给任何人,它既不能覆盖安装,也不适合作为归档件。下一步的对齐和签名,才是把“工程目录”变成“可安装产物”的关键。

8.3 对齐:让资源按页加载

zipalign 的作用是调整 zip 内未压缩数据的偏移,让系统可以用 mmap 直接读资源,减少内存拷贝。命令如下:

zipalign -f -p 4 build\unsigned.apk build\aligned.apk
zipalign -c -v 4 build\aligned.apk

-f 覆盖输出,-p 让未压缩的 so 按页对齐,4 是通用对齐边界,-c 只检查不修改。面向支持 16 KB 页的新设备,较新的 Build-Tools 提供 -P 16 参数,可以按 16 KB 页对齐 so;如果你的应用带 native 库、又要覆盖新设备,值得用上。对齐检查必须返回成功,否则签名后的包在部分设备上可能启动失败。

顺序不能反:先对齐,再签名。因为 v2/v3 签名会把整个 zip 的摘要写进签名块,任何后续修改都会让签名失效。如果你先签名再对齐,apksigner verify 会直接告诉你签名不通过;这时候不要试图“再签一次盖过去”,而是从 unsigned.apk 重新对齐、重新签名。

8.4 签名:用对密钥,比签上更重要

自家应用做汉化,最重要的原则是“沿用原来的发布密钥”。同一密钥签出来的包,才能覆盖安装老版本,用户数据、登录态、本地缓存都能保留。用错密钥,系统会报签名冲突,只能卸载重装,数据全丢。命令如下:

set KS_PASS=你的密钥口令
apksigner sign --ks keys\release.jks --ks-key-alias release --ks-pass env:KS_PASS --out build\signed.apk build\aligned.apk
apksigner verify --print-certs -v build\signed.apk

口令通过环境变量传入,不写进脚本、不提交到仓库、不出现在命令历史里。PowerShell 里对应的是 $env:KS_PASS="..."。团队协作时,密钥文件放在受控目录,权限只给构建负责人;离职交接时更换密钥并记录,不要继续用个人密钥签公司应用。

apksigner 默认会根据 minSdk/targetSdk 选择 v1/v2/v3 方案,通常不需要手动指定。验证时重点看三件事:Verified using v1 scheme、v2 scheme、v3 scheme 是否都为 true;证书 SHA-256 是否与原始包一致;签名后的 zip 是否还能通过 zipalign 检查。三项都对,才算“签对了”。

如果原包用的是 v1 只有、targetSdk 又比较老,v2/v3 可能需要手动开启;反过来,如果 minSdk 很低但只签 v2,老设备会装不上。最稳的做法是让 apksigner 按默认策略走,然后用 verify 的完整输出确认每个 scheme 的状态,而不是只看“Successfully signed”这一行。

8.5 安装与启动验收

  1. 确认设备。adb devices 只保留目标设备,避免装到别的机器上。
  2. 覆盖安装。adb install -r -d build\signed.apk,-r 表示替换,-d 允许版本号不增。正式分发不要长期依赖 -d,该升版本就升。
  3. 显式启动。用 adb shell am start -W -n 包名/启动Activity,不要只点桌面图标。显式启动能看到启动耗时、是否闪退,也能确认组件名没被改坏。
  4. 看崩溃日志。启动后立刻看 adb logcat -s AndroidRuntime:E,有栈就按栈定位;没有栈再继续走流程。
  5. 核对版本与签名。adb shell dumpsys package 包名 里看 versionName、versionCode、firstInstallTime、签名摘要,确认装的是新包而不是旧包。
adb install -r -d build\signed.apk
adb shell am start -W -n com.example.teaching/.ui.SplashActivity
adb logcat -s AndroidRuntime:E

安装失败时,错误码基本能说明问题:INSTALL_FAILED_UPDATE_INCOMPATIBLE 是签名不一致;INSTALL_FAILED_VERSION_DOWNGRADE 是 versionCode 比已装版本低;INSTALL_PARSE_FAILED_NO_CERTIFICATES 是根本没签名;INSTALL_FAILED_OLDER_SDK 是 minSdk 高于设备。对应处理分别是换回原密钥、升版本号、重新签名、确认设备版本,不要靠反复重装碰运气。

8.6 验收要看的不只是“能打开”

启动成功只是第一关。汉化包的验收至少覆盖:应用名与图标下的文字是否正确;默认语言与切换语言后的显示是否正确;首页、列表、详情、设置、弹窗、Toast 是否还有英文;文本有没有截断、重叠、换行错位;字体缩放后是否仍可读;深色模式下文字对比度是否足够;返回、旋转、分屏后文案是否仍然正确;登录态与本地数据是否保留;崩溃日志是否干净。

建议做一张“页面 × 状态”的验收表,每格放截图或打勾,而不是凭记忆说“我都点过了”。验收人最好不是改文案的人,避免“我知道它想表达什么”导致的误判。发现问题的记录要写到具体页面、具体控件、具体机型与字体缩放,方便下一次复现。

安装验收检查清单

  • □ zipalign 检查通过
  • □ apksigner verify 三个 scheme 均通过
  • □ 证书 SHA-256 与原始包一致
  • □ 覆盖安装成功,数据未丢
  • □ 显式启动成功,无崩溃栈
  • □ 应用名、版本号正确
  • □ 主要页面无英文残留
  • □ 字体 1.3 倍下无截断
  • □ 深色模式、横屏、分屏正常
  • □ 旧版安装包已归档,可回滚

回滚方案也要提前准备:保留上一版 signed.apk、对应 versionCode、签名指纹和验收截图。新包出问题时,如果签名一致,直接 adb install -r -d 旧包即可;如果签名不一致,就不能覆盖,只能卸载重装,所以“用对密钥”这句话值得再强调一次。

回编产物对齐与签名校验流程示意

图 5:先对齐、再签名、最后校验,顺序不能反

九、第七步:导出 Android Studio 工程与多渠道包

手工改包适合一次性交付,长期维护还是要有工程。安卓修改大师经典版支持导出 Android Studio 工程,也支持多渠道打包。导出不是“把 APK 变成完美源码”,而是把清单、资源、assets、库和代码目录整理成一个可打开的工程壳,让后续的翻译、版式调整、打包进入版本管理。

9.1 导出与首次打开

  1. 在经典版的项目里选择导出 Android Studio 工程,指定一个空目录,等待生成完成。
  2. 用 Android Studio 的 Open 打开该目录,等待 Gradle Sync。第一次同步会下载依赖,时间取决于网络与本地缓存。
  3. 检查 Gradle 版本、AGP 版本、compileSdk、minSdk、targetSdk 是否与本机 SDK 匹配。版本不匹配时,先改构建脚本或安装对应 SDK,不要急着改业务代码。
  4. 确认 res/、assets/、libs/、AndroidManifest.xml 都在,中文目录 values-zh-rCN 也在。
  5. 先不改任何东西,直接 Build 一次,确认工程本身能出包。能出包再谈后续。

导出工程的代码目录可能是 smali,也可能是转换后的 Java 目录,取决于导出实现与项目情况,以实际结果为准。如果代码目录是 smali,Android Studio 默认不会编译 smali,这个工程更适合作为“资源与清单的维护壳”:翻译、版式、渠道配置都在这里做,逻辑改动仍回到原始工程或 decoded 目录。不要因为 AS 能打开就以为它能一键还原成原始 Java 工程,这个预期要先对齐。

9.2 把签名配置搬进 Gradle

在工程里配置 release 签名时,密钥路径、别名可以写进构建脚本,口令必须从环境变量读:

android {
    signingConfigs {
        release {
            storeFile file("../keys/release.jks")
            storePassword System.getenv("KS_PASS")
            keyAlias "release"
            keyPassword System.getenv("KEY_PASS")
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

这段配置不要提交真实口令,也不要提交 .jks。CI 里用受控的密钥环境变量;本地开发用个人测试密钥,不要拿正式密钥到处签。工程里的 applicationId 必须与原包一致,否则装上去会是两个应用,覆盖升级也就无从谈起。

9.3 用翻译编辑器管长期文案

工程能打开之后,Android Studio 的翻译编辑器比手工改 XML 更适合长期维护:它会把默认语言与各语言并排显示,缺译的 key 会标红,能直接看到占位符是否一致。把 values-zh-rCN/strings.xml 作为中文目标,默认 strings.xml 作为源语言,逐条过一遍“未翻译”列表,比 grep 更直观。

但翻译编辑器不是万能的:它不会告诉你按钮会不会溢出,也不会替你判断术语是否统一。所以流程仍然是“编辑器管完整性、术语表管一致性、真机管版式”。三者缺一不可。每次发版前,把编辑器里的缺译清零,把术语表里的新词补上,把关键页面截图归档,形成固定动作。

9.4 多渠道包:同一份代码,不同渠道值

多渠道包的本质不是“复制几个 APK”,而是同一份代码在构建时注入不同的渠道值,用于分发统计、灰度或不同合作方。经典版支持多渠道打包,适合需要一次产出多个渠道安装包的场景。关键是渠道值要可查、可验、可复现,不能靠手工改一个忘一个。

渠道值放哪 做法 优点 注意
Manifest meta-data 在清单里写 channel 键值,构建时替换 读取方便,工具支持好 键名要与代码约定一致
assets 渠道文件 每个渠道放一个内容不同的文本文件 不改清单,适合老代码 文件必须真的被打进包
Gradle productFlavors 用 flavor 生成不同变体 结构清晰,适合长期维护 变体多时构建时间长
resValue 字符串 把渠道名写成资源 代码读取简单 不要把渠道名当用户文案翻译

用 Gradle 的做法大致如下,渠道名由构建注入,代码侧只读同一个 key:

flavorDimensions "channel"
productFlavors {
    own {
        dimension "channel"
        manifestPlaceholders = [CHANNEL_VALUE: "own"]
    }
    edu {
        dimension "channel"
        manifestPlaceholders = [CHANNEL_VALUE: "edu"]
    }
}

打包时按渠道清单逐个构建、逐个签名,不要把不同渠道的包混在一个目录里。命名建议带上渠道、版本、日期,例如 teaching-edu-1.4.0-20261021.apk。每个包都跑一遍 apksigner verify,并把 SHA-256 写进发布记录。渠道包必须同签名、同 versionCode(除非渠道之间就是要错开升级),否则用户换渠道安装会失败。

9.5 渠道包的验证

  1. 读清单。aapt2 dump xmltree 包.apk --file AndroidManifest.xml,搜索渠道 key,确认值正确。
  2. 读资源。如果渠道值写在资源里,用 aapt2 dump resources 查对应 string 的值。
  3. 验签名。每个包都 apksigner verify --print-certs,确认证书一致。
  4. 抽装。至少每个渠道抽一台设备安装,进“关于”页或日志里看渠道值是否与包名对应。
  5. 留档。把渠道、包名、版本、SHA-256、签名指纹、构建时间写进一张发布表,后续排查“用户装的是哪一包”时能对上。

渠道包只用于合法的分发与统计,不要用它绕过应用商店审核、伪装来源或向未授权方分发。渠道值本身不是安全机制,不要把它当权限校验;真正的权限控制必须在服务端做。

9.6 维护节奏

导出工程后,建议建立四个固定资产:术语表、翻译记忆、验收截图库、发布记录。每次改文案,先改 CSV,再生成 XML,再构建,再截图对比,最后更新发布记录。把“汉化”从一次性的手工活,变成可交接的流程。新人接手时,看这四样东西就能知道哪些词不能动、哪些页面容易溢出、上一版签的是什么名、哪个渠道对应哪个包。

如果应用持续迭代,最好在原始工程里就把文案抽到资源、把硬编码清零。汉化包只是补救手段,不是长期方案。能在源码层解决的事,不要留到反编译层;反编译层适合交付前的最后一公里,而不是每一个版本的常规流程。

导出工程后在 Android Studio 中继续维护资源

图 6:导出工程后,把翻译、版式与渠道配置纳入版本管理

十、两个完整实例与一线用户评价

前面九节讲的是方法和边界,这一节把方法落到两个具体项目:一个是自家教学 App 从英文界面转中文,另一个是自家内部工具统一术语与按钮文案。两个项目的目标不同,前者解决“看不懂”,后者解决“叫法乱”,但流程完全一致:盘点、翻译、控长、稳 ID、回编、验收、导出维护。例子里的数量只是示意,实际项目按自己的规模调整。

10.1 实例一:自家教学 App 英译中

背景。这是一套自研的在线课程 App,课程播放、作业、测验、学习进度都在里面。最初只做了英文界面,后来主要用户变成国内学生,需要完整的简体中文界面。应用有自己的发布密钥,老用户已经装了不少,必须支持覆盖升级;团队没有保留完整的前端源码,只能从发布包入手。

盘点结果。完整反编译后,先按第四节的方法做一次文案普查,结果大致如下:

位置 规模 处理策略
values/strings.xml 约 1180 条 全量翻译,品牌名与 URL 保留
arrays 3 组(星期、学科、筛选) 逐项翻译,顺序不变
plurals 6 组 中文统一用 other
layout 内联 42 个布局,63 条文案 迁到 strings.xml 后翻译
smali 硬编码 17 处 Toast/对话框 逐处确认上下文后替换
assets HTML 2 个帮助页 改可见文本,保留脚本与样式
服务端下发 9 处 标记为范围外,由后端配多语言

第一步,定术语。课程场景的词很容易翻得五花八门,先统一:Course 统一为“课程”,Lesson 统一为“课时”,Chapter 统一为“章节”,Quiz 统一为“测验”,Assignment 统一为“作业”,Progress 统一为“学习进度”,Download 统一为“下载”,Offline 统一为“离线”,Instructor 统一为“讲师”。定完之后,1180 条文案里凡是出现这些词的,一律按表来,不允许“课程/课目”“测验/测试”混用。

第二步,翻字符串并处理占位符。课程进度原文是 Lesson %1$d of %2$d,中文改成“第 %1$d 节,共 %2$d 节”,参数顺序可以调,编号不能丢。欢迎语 Welcome back, %1$s 改成“欢迎回来,%1$s”。有一条 Today's lesson 因为单引号没转义,第一次回编直接报错,改成 Today\'s lesson 后通过;中文译文里没有单引号,但原文件保留的英文注释里还有,所以转义规则仍要过一遍。

第三步,处理数组和复数。星期数组按周一到周日原序翻译,没有调整顺序,因为代码用 Calendar.DAY_OF_WEEK 做索引。学科数组是展示用的,顺序也不动,避免和后台返回的顺序对不上。6 组复数全部补了 other,中文写成“%d 节课”“%d 个作业”“%d 次测验”,one 保留同样内容,防止某些静态检查报缺项。

第四步,迁内联文本。用 4.4 的扫描命令找出 63 条内联文案,按页面分组,逐条在默认 strings.xml 里建 key,再把 layout 里的 android:text="..." 换成 android:text="@string/..."。其中“Continue learning”拆成 key continue_learning,中文“继续学习”;“Download for offline”没有直译成“下载以便离线”,而是压缩成“离线下载”,因为按钮只有 120dp 宽,直译一定放不下。

第五步,改 smali。17 处硬编码里,最常见的是“Download complete”“Saved”“Please check your network”。其中“Download complete”在课程下载、资料下载、离线包更新、附件缓存四个地方共用,先确认四处都是用户可见提示,再统一改成“下载完成”。有两条带 %s 的提示属于格式化串,替换时保留了占位符,并在真机上分别触发一次,确认没有格式化异常。

第六步,放 locale 目录。应用以后可能招国际学生,所以走路线 A:默认 values 保留英文,新建 res/values-zh-rCN/strings.xml,只放翻译过的 key。没有翻译的 key 会回退英文,这正好能在测试时暴露缺口。应用名在中文目录里写成“XX 课堂”,英文默认仍是原品牌名,中文设备桌面显示中文,英文设备保持原样。

第七步,回编、对齐、签名、安装。沿用原发布密钥,versionCode 加一,zipalign 检查通过,apksigner 的 v1/v2/v3 全部验证通过,覆盖安装后登录态和已下载课程都在。随后在 5 英寸 720p、6.1 英寸主流机、平板上各走一遍,字体缩放到 1.3 倍复查按钮和底部导航,共归档 28 张截图。

验收时发现并修掉的问题。第一,“下载后可离线学习”在 120dp 按钮上溢出,改成“离线下载”;第二,课程列表副标题“12 lessons · 3 hours”中文写成“12 节课 · 3 小时”后,与右侧箭头挤在一起,把副标题的 layout_weight 调大,箭头固定宽度;第三,帮助页 HTML 里有一句页脚版权说明没翻,通过 WebView 逐页点开才发现;第四,繁体设备会回退英文,这一条在验收说明里写清“本版仅覆盖简体”,避免误判为 bug。

交付。导出 Android Studio 工程,把 values-zh-rCN、术语表、验收截图、签名指纹一起归档。后续运营改一句活动文案,可以在工程里改资源、跑一次构建,不必每次从 APK 反编译。这个项目最终做到:缺译 key 为 0,占位符不一致为 0,主要页面英文残留为 0,覆盖升级成功,导出工程可独立构建。

10.2 实例二:内部工具统一术语与按钮文案

背景。这是一套自家内部使用的工单与任务管理工具,三个模块由三个小组先后开发,界面风格勉强统一,文案却各写各的:同一个“保存”,有人写“存储”,有人写“暂存”;同一个“登录”,有人写“登入”,还有人写“登陆”;对话框的确认按钮,有时是“确定”,有时是“好的”,有时是“是”。新员工培训时,光解释这些词就要花半天。项目还要从旧品牌名切换到新品牌名,涉及应用名、关于页、分享文案、通知渠道名和多处 HTML 帮助页。

先做术语映射表。这次不是英译中,而是“中文统一中文”,难点在取舍:哪些词保留,哪些词淘汰,淘汰后旧词出现在哪些位置。团队先拉了一张映射表,逐条评审:

旧写法 统一为 适用场景 备注
保存 / 存储 / 暂存 保存 表单、编辑器 “存储空间”作为名词保留
登录 / 登入 / 登陆 登录 账号入口 “登陆”是错别字,全部替换
设置 / 设定 / 偏好 设置 导航、页面标题 与系统叫法一致
确定 / 好的 / 是 确定 对话框主按钮 危险操作改用具体动词,如“删除”
取消 / 关闭 / 返回 取消 对话框次按钮 页面导航仍用“返回”
删除 / 移除 / 清除 删除 / 移除 销毁数据 / 移出列表 语义不同,不能一刀切
提交 / 上传 / 报送 提交 / 上传 表单提交 / 文件上传 按动作对象区分
用户 / 账号 / 账户 账号 登录、个人信息 “用户”只在统计报表里保留

第二步,把所有文案导出来。用脚本把 values、layout 内联、menu、xml 偏好设置里的中文全部抽到一张 CSV,按“页面 + 控件 + 原文 + 建议”排列。这样能一眼看出同一个页面上“保存”和“存储”同时出现,也能发现“好的”这种不适合做按钮的口语词。中文没有空格分词,批量替换很容易误伤,比如把“取消关注”里的“取消”换成“关闭”就变味了,所以替换后必须逐条看上下文,不能只靠正则。

第三步,先改高频词,再改长尾。第一轮只动登录、保存、设置、确定、取消、删除这六个高频词,涉及页面最多,收益也最大。第二轮再处理“工单/任务/事项”“用户/账号”这类业务名词,以及帮助页、错误提示、邮件模板。每轮都单独构建、单独截图对比,避免一次改太多,出问题不知道是哪一批引起的。

第四步,处理品牌名切换。品牌名不只出现在 strings.xml 的 app_name 里,还出现在关于页的版权行、登录页页脚、分享文案、通知渠道名称、导出的 PDF 页眉、assets 里的帮助页 HTML。团队按“全目录搜索旧品牌名”的方式列出所有位置,逐个确认:能通过资源引用的,改成引用;写死在 smali 的,改字面量;写在 assets 的,改 HTML 文本但保留脚本;服务端模板里的,交给后端改。应用名在默认 values 里直接写成新中文名,因为这是内部工具,不需要跟随系统语言变化。

第五步,修按钮溢出。统一术语后,按钮变长是最直接的后遗症。“保存并退出”放在 96dp 的固定宽度按钮里,字体放大到 1.3 倍就被截成“保存并退…”。团队试过缩小字号,但在会议室投影和低端机上都看不清,最后改成“保存”,点击后再弹一个“保存后退出?”的确认框。这样既保住了语义,也保住了可点面积。另一个例子是底部导航的“个人中心”,四个字在五等分里显得挤,缩短为“我的”,页面标题仍保留“个人中心”,信息没有丢。

第六步,验证。先在资源目录里搜旧词,确认 strings、layout、menu、xml 里不再出现“登入”“登陆”“暂存”“设定”“好的”等淘汰写法;再在 smali 和 assets 里搜,确认品牌名和通知渠道名也换了;然后三个模块各走一遍主流程,重点看对话框按钮、列表操作、空状态和错误提示;最后用 TalkBack 听一遍登录页和工单详情,确认 contentDescription 不是英文或旧品牌名。两轮之后,术语混用归零,按钮溢出清零,旧品牌名只保留在历史文档里。

交付。把术语表、替换清单、验收截图、构建产物一起归档,并在团队里约定:以后新增文案先查术语表,新增按钮先量宽度,新增语言先补 locale 目录。内部工具不需要花哨,但“同一个动作只有一个叫法”会实打实降低培训和客服成本。

用户评价:五个真实使用场景

“我们只改自家 App,最看重的是每一步能停下来看。反编译、改 strings、回编、对齐、签名,日志都在,出问题能定位到具体阶段。经典版的手动路线比一键式更适合我们这种要交付审计记录的团队。”

—— 林舟(企业 Android 开发)

“教学 App 英译中,最怕按钮放不下。按教程先把内联文本迁到资源,再做长度预算,字体放到 1.3 倍逐页截图,返工比上一版少了很多。导出 Android Studio 工程后,运营改活动文案也能自己维护。”

—— 周敏(教育产品经理)

“内部工具三个模块各叫各的,统一术语后培训成本明显下降。多渠道打包让内网版和测试版一次出齐,签名、版本、渠道值都能对上,不用再手工复制工程。”

—— 陈昊(高校信息化中心)

“自己做的小工具加中文界面,最有用的其实是 public.xml 那一段。以前改完偶尔闪退,后来知道是资源 ID 漂了;现在每次 dump 前后对比,稳定多了。”

—— 阿凯(独立开发者)

“我们只接有授权的项目,开工前先收授权书和范围说明。交付时附签名指纹、验收截图和导出工程,客户后续自己能维护,出了问题也容易界定责任。”

—— 王琳(外包项目经理)

安卓修改大师智改工坊 · 安卓修改大师经典版

把每一步握在手里,汉化才能可复现

这篇教程的七个步骤——反编译、找文案、改硬编码、控长度、稳 ID、回编签名、导出工程——对应的就是安卓修改大师经典版的手动工作台。AI 版的主张是「只需说话,就能让应用变成你想要的样子」;经典版把“说话”换成“确认”:每一处改动都能回看,每一次打包都能复现,适合汉化和团队交付。

产品介绍与下载:https://www.apkeditor.cn/ai-version.aspx 官网:www.apkeditor.cn

查看 安卓修改大师智改工坊

手机端本地改包

安卓修改大师安卓版

支持 Android 10+ 的 ARM64 手机与平板,在设备本地完成反编译、改包、回编、对齐与签名;可导入 APK、JAR、APKS、XAPK、APKM,成品可直接安装或分享,也能保存源码 ZIP。适合在真机上快速验证汉化效果。

仅用于自有版权或已获授权的应用,请勿用于破解。

立即下载