简介:这份资源聚焦 Android 系统应用的 APK 与 ODEX 合并及深度定制,面向具备一定刷机与反编译基础、希望修改三星等闭源系统应用的中高级玩家。内容围绕工具准备、系统应用提取、反编译与重新打包、签名替换及汉化修改展开,重点讲解在无法获得厂商签名的情况下如何迂回打包并回推系统目录,同时提示修改系统应用可能带来的稳定性与兼容性风险。资源包共 1 个 docx 文档,约 23KB,以图文步骤说明为主,便于对照操作与查阅关键命令。目前已有 334 人学习,适合想系统掌握系统应用汉化、布局调整与资源替换流程的读者参考,也可作为排查打包报错、语言包删减等问题的思路补充。
1. 反编译报错“资源不存在”:先别急着删语言包
改三星系统 APK 的人,十个里有八个会在第一次打包时撞上同一堵墙:明明只是删了几个values-xx语言目录,apk manager执行 11 号打包,屏幕一闪,erro出来了。关掉窗口,重新来一遍,还是erro。这时候很多人开始怀疑工具坏了、Java 版本不对、系统环境有问题,甚至把整个工程删了重来。
我接触 Android 的时间比较早,从 G1 那个年代就开始倒腾 ROM,收集过不少有代表性的机器,也编译过中文版系统推给同好使用。这些年下来,最深的体会是:三星这类闭源系统的定制,难点从来不在“会不会用工具”,而在“看不看得懂它给你的反馈”。APK 和 ODEX 合并这件事本身不复杂,复杂的是合并前后那一连串资源引用、签名校验和 framework 依赖。这篇笔记就把我踩过的坑按故障场景拆开,从反编译报错一路讲到 ODEX 合并失败,每一步都落到能直接抄的命令和参数上。
适合谁看?如果你正卡在apktool反编译报错、apk manager打包失败、baksmali合并 ODEX 提示BOOTCLASSPATH找不到类,或者汉化完推回手机直接 FC,那基本都能在这里找到对应的排查路径。新手可以照着命令走,熟手可以重点看根因分析和避坑那几节。
2. 反编译就失败:framework 没装对,后面全是白费
2.1 现象:apktool 报“Could not decode attr”或资源 ID 找不到
最典型的一幕是这样的:拿到一个三星系统里的TouchWiz30Launcher.apk,直接apktool d反编译,终端刷出一片红字,大意是某个attr无法解析,或者resource id 0x0101xxxx is not found。换成apk manager的 9 号选项,同样在products目录里生成一堆残缺文件,res/values下的public.xml引用对不上。
这个报错的触发条件很明确:APK 里引用了厂商自定义 framework 中的资源,而你的反编译环境里没有安装对应的 framework 包。三星在标准 Android framework 之外多塞了一个twframework-res.apk,位置在/system/framework/下。系统应用在编译时会把对它的资源引用写进resources.arsc,反编译工具如果不知道这个 framework 的存在,自然解不出那些属性。
我一般会先确认两件事:目标 APK 是不是系统应用,以及它依赖哪个 framework。判断方法很直接,看它所在目录——/system/app下的基本都是,再看同目录下有没有twframework-res.apk这类厂商包。确认之后,安装 framework 是第一步,顺序不能反。
2.2 解决:先 if 安装 framework,再反编译
# 进入 framework 所在目录,先安装厂商自定义 framework apktool if twframework-res.apk # 如果目标 APK 还依赖标准 framework,一并安装 apktool if framework-res.apk # 确认安装成功,会输出到 ~/.local/share/apktool/framework/ 下 ls ~/.local/share/apktool/framework/ # 再执行反编译 apktool d TouchWiz30Launcher.apk -o launcher_outapktool if的if是 install-framework 的缩写,它把 framework 包解析后按资源包 ID 存到本地框架目录,后续反编译遇到对应 ID 就能查表解析。参数上没什么花活,关键是顺序:先装twframework-res.apk,再装标准framework-res.apk,因为部分厂商包会覆盖同名资源,装反了会出现属性解析到错误值的情况。
用apk manager的话,等价操作是在主目录下执行它自带的 framework 安装脚本,或者手动把twframework-res.apk放到工具指定的 framework 目录。apk manager的script.bat启动时会检测adb是否存在,如果环境变量没配好,它连第一步都过不去。常见做法是把adb直接拷到apk manager主目录下,省得折腾 PATH。
提示:反编译前先跑一遍
apktool if,比出错后再回头补装省事得多。我见过太多人反编译到一半报错,以为是 APK 损坏,其实是 framework 没装。
2.3 为什么“先合并 ODEX 再反编译”是个误区
网上不少教程会说,反编译系统 APK 之前必须先把 ODEX 合并进去,否则资源不全。这个说法在多数场景下是错的,至少对三星这套流程不成立。反编译阶段apktool读的是 APK 里的resources.arsc和classes.dex,ODEX 是 Dalvik 优化后的字节码,反编译资源根本用不到它。原文里也明确写了:安装 framework 时“至于是否要将 odex 和 apk 文件合并这里无所谓”。
真正需要合并 ODEX 的场景,是你要改classes.dex里的代码,或者要把系统应用做成可独立分发的包。如果只是汉化strings.xml、改布局 XML、替换图片,完全不需要碰 ODEX。这一点想清楚,能省掉一大半无效操作。我早期也在这上面绕过弯路,先把 ODEX 合并了再反编译,结果合并过程本身引入新的报错,排查方向直接被带偏。
所以这一章的顺序建议固定下来:确认系统应用 → 安装对应 framework → 反编译 → 改资源 → 打包。ODEX 合并放到后面单独处理,两件事不要搅在一起。
3. 打包报错“资源不存在”:public.xml 与语言包的连锁反应
3.1 现象:删了 values-xx,打包提示某行资源缺失
汉化时为了精简,很多人会把values-it、values-en-rUS这些非目标语言目录删掉,只留values、values-zh-rCN、values-zh-rTW。删完执行 11 号打包,erro如期而至。关掉apk manager,去主目录翻日志,能看到类似“public.xml第 xxxx 行资源不存在”的提示。
根因在于 Android 的资源打包机制。public.xml里为每个资源分配了固定 ID,某些字符串是特定语言独享的,只存在于values-it/strings.xml里,values/strings.xml中并没有对应条目。你把这个语言目录删了,public.xml仍然引用那个资源 ID,打包器找不到定义,直接报错。这不是工具的问题,是资源引用链断了。
还有一种更隐蔽的情况:删了values-xx但保留了values-land、values-port这类非语言目录,打包时不会报错,但运行起来某些界面文字变成空白。原因是布局目录里引用了被删语言包中的字符串,编译期没检查出来,运行期才暴露。
3.2 解决:按日志行号精准删 public.xml,别整段删
# 第一步:打包失败后,先看 apk manager 主目录下的日志 # 日志里会写明 public.xml 的行号和缺失的资源名 cat log.txt | grep -i "public.xml" # 第二步:打开 public.xml,定位到报错行 # 注意:删除时保留行号占位,避免后续行号错位 # 例如把整行内容删掉,但保留一个空行或注释占位 # 第三步:重新执行打包,重复“看日志 → 删对应行”直到不再报错这里的关键操作是保留行号。日志报的是“第 1200 行资源不存在”,你如果直接把第 1200 行删掉,原来的第 1201 行就顶上来变成第 1200 行,下一次报错的行号全部错位,你会误删正常资源。正确做法是把那一行的内容清空但保留行结构,或者用注释占位,让行号锚点不动。
参数上没什么可调的,public.xml是纯文本,用notepad2或任意编辑器打开即可。真正要练的是读日志的习惯。原文里有句话我特别认同:用apk manager从来不看日志文件,就如同从没入门过。日志会详细列举报错原因,比任何猜测都可靠。
3.3 打包后的“迂回替换”:为什么不能直接签名安装
三星系统应用的签名是厂商私钥签的,你拿不到。apk manager打包出来的 APK 带unsigner前缀,签名和原包不一致,直接adb install会被系统拒绝。所以流程要迂回:先用winrar或类似工具同时打开未签名的新包和原始 APK,把修改过的文件(比如resources.arsc、改过的 XML、替换的图片)拖进原始包里覆盖,保持原始包的签名结构不变,再推回系统。
# 把替换好的 APK 推回系统应用目录 adb push your.apk /system/app/your.apk # 推之前建议先 remount 为可写 adb shell mount -o rw,remount /system # 推完重启生效 adb rebootadb push的目标路径必须是/system/app/下原文件名,覆盖前最好把原文件备份到/sdcard/或电脑上。mount -o rw,remount /system这步在部分机型上需要 root 权限,没 root 的话推不进去。重启后如果应用直接消失或者反复 FC,说明替换的文件和原包资源不兼容,得回退备份。
注意:
keep文件夹的用法容易搞混。打包时工具会把原始 APK 的文件放进keep,如果你改了values下的语言包,要去keep里删掉旧的resources.arsc;如果改了layout或xml目录下的文件,同样要删掉keep里对应的旧 XML。不删的话,旧文件会被原样打回新包,你的修改等于没生效。
4. ODEX 合并失败:BOOTCLASSPATH 缺一个 jar 就全盘报错
4.1 现象:baksmali 反编译 odex 提示“class not found”
走到 ODEX 合并这一步,说明你要动classes.dex了。命令看起来很简单:
# 把 odex 分解成 smali 文件 java -jar baksmali.jar -x your.odex但实际执行时,终端可能刷出大量class not found或者Unable to resolve的报错,out目录里生成的 smali 文件残缺不全。换smali.jar往回打包,又提示某些类无法解析。这时候很多人以为是baksmali版本不对,换了好几个版本,问题依旧。
根因在BOOTCLASSPATH。ODEX 文件里的字节码引用了系统启动类路径下的类,baksmali反编译时需要知道这些类在哪里,才能正确解析引用。标准 AOSP 的启动类路径是固定的几个 jar,但厂商定制 ROM 会往里加自己的 jar。三星 I9000 就在标准之外多了一串:com.samsung.device.jar、seccamera.jar、sechardware.jar、twframework.jar、javax.obex.jar、com.google.android.maps.jar。少给一个,反编译到引用该 jar 的类时就断链。
4.2 解决:把完整 BOOTCLASSPATH 的 jar 全部备齐
# 标准 AOSP 需要的启动类 jar # core.jar ext.jar framework.jar android.policy.jar services.jar # 三星 I9000 额外需要的 jar # com.samsung.device.jar seccamera.jar sechardware.jar # twframework.jar javax.obex.jar com.google.android.maps.jar # 把这些 jar 从设备的 /system/framework/ 拉下来,放到 smali 工作目录 adb pull /system/framework/core.jar . adb pull /system/framework/framework.jar . adb pull /system/framework/twframework.jar . # ... 其余 jar 同理 # 反编译 odex,-d 指定启动类目录 java -jar baksmali.jar -x your.odex -d . # 反编译成功后,out 目录里是完整的 smali 文件 # 再打包回 classes.dex java -Xmx512M -jar smali.jar out -o classes.dex-d参数指定的是启动类所在目录,把 jar 都放在当前目录下,-d .即可。-Xmx512M是给 JVM 分配最大堆内存,处理大 ODEX 时内存不够会OutOfMemoryError,512M 对多数系统应用够用,特别大的包可以加到1024M。
这里有个实操细节:jar 的版本要和设备上的 ROM 匹配。从别的机型或者别的系统版本拉来的framework.jar,类结构可能对不上,反编译出来的 smali 引用会错乱。最稳的做法是从当前这台设备的/system/framework/直接adb pull,保证版本一致。
4.3 合并回 APK:classes.dex 放回原位再打包
smali.jar生成classes.dex之后,把它放进反编译出来的 APK 目录根部,再走正常的打包流程。如果原 APK 里本来没有classes.dex(纯资源包),直接放进去即可;如果原来有,覆盖掉。打包完成后同样走“拖回原始 APK 覆盖”的迂回路线,不要试图重新签名安装。
# 把生成的 classes.dex 拷进反编译目录 cp classes.dex launcher_out/ # 用 apktool 重新打包 apktool b launcher_out -o new.apk # 后续同样用原始 APK 做壳,替换修改过的文件apktool b的-o指定输出文件名。打包过程中如果报资源错误,回到第 3 章的处理逻辑,看日志删public.xml对应行。ODEX 合并和资源打包是两个独立的报错源,要分开排查,别混在一起猜。
5. 汉化后 FC 与 framework 修改:改错一个 map 参数就进不了系统
5.1 现象:推回手机重启,系统菜单直接 FC
汉化framework-res.apk是最容易翻车的环节。改完strings.xml,打包替换,重启,结果开机后设置菜单一点就 FC,甚至整个系统 UI 反复崩溃。这时候只能进 recovery 还原备份,否则手机基本没法用。
根因通常有两类。第一类是字符串长度越界。某些选项文字在布局里占固定宽度,汉化后字符变长,显示不全还是小事,极端情况下布局解析异常导致 FC。第二类是map相关参数没改对。framework-res.apk里有些资源跟地区配置绑定,比如地图、语言区域参数,直接照搬英文包的值,系统在初始化区域设置时读到不匹配的配置,运行期崩溃。
原文里提到一个很实在的做法:汉化framework是艰苦工作,需要毅力、时间和细心,有些选项和词语不能过长,否则越界显示不全。这不是吓唬人,是真实约束。我一般会先备份原始framework-res.apk并做成刷机包,改一版测一版,FC 就 recovery 还原,再改再测,直到稳定。
5.2 解决:保留三语,raw 和 values 目录按规则删
# framework-res.apk 反编译后,res 目录下的处理规则 # raw 目录:只保留 raw、raw-zh-rTW、raw-zh-rCN # 其余 raw-xx 全部删除 # values 目录:保留 values、values-land、values-port、values-hdpi # 以及 values-zh-rCN、values-zh-rTW # 还有 values-mcc数字-zh-rCN、values-mcc数字-zh-rTW # 其余 values-xx 删除 # 没有中文语言包时,从 values 拷贝 strings.xml 和 arrays.xml # 到新建的 values-zh-rCN 目录,再逐条翻译raw目录放的是音频等原始资源,按语言分目录,删多了不影响编译但可能缺提示音。values-mcc开头的目录是按移动国家码区分的配置,中文相关的要保留,其他删掉。values-land、values-port、values-hdpi这些是横竖屏和分辨率适配目录,不是语言包,不能删,删了会导致对应场景下资源找不到。
翻译arrays.xml时有个坑:带_values后缀的string-array是程序内部用的选项值,不是显示文本,翻译了会导致逻辑错乱。只翻译不带_values的显示项。strings.xml里<string name="xxx">的文本部分才是给人看的。
5.3 twframework-res.apk 不能当普通 APK 删资源
三星的twframework-res.apk是个特殊存在。它能被反编译,有 APK 的结构,但里面装的主要是系统级图片资源、菜单和按钮样式,被大量系统应用引用。你可以改它的图片和样式,但不能随意删资源,删一个按钮背景图,可能几十个界面跟着崩。
处理它的原则是:只替换、不删除。要改样式就覆盖对应图片,要调颜色就改values里的颜色定义,保持资源条目完整。它和framework-res.apk的修改方法类似,但容错空间更小,因为引用它的地方太多,一处缺失就是连锁反应。
6. 快速定位这类问题的排查习惯
改系统 APK 这件事,报错信息往往只有一行erro,真正的线索全在日志和资源引用链里。我这些年形成的习惯是:任何一步失败,先找日志,再看行号,最后才动文件。下面这张对照表是我常用的快速定位入口,按报错关键词查对应章节。
| 报错关键词 | 最可能根因 | 先查什么 | 对应处理 |
|---|---|---|---|
Could not decode attr | framework 未安装 | apktool if是否执行过 | 装twframework-res.apk再反编译 |
public.xml行号资源不存在 | 删语言包导致引用断裂 | 日志里的行号 | 按行号删public.xml,保留行占位 |
class not found/Unable to resolve | BOOTCLASSPATH 缺 jar | 厂商额外 jar 是否备齐 | 从设备 pull 全部启动类 jar,-d指定目录 |
| 打包成功但推回 FC | 资源不兼容或签名结构破坏 | 是否用原始 APK 做壳替换 | 回退备份,逐文件替换测试 |
| 汉化后菜单越界或 FC | 字符串过长或 map 参数错 | 改动过的strings.xml条目 | 缩短文本,对照原版改 map 参数 |
排查顺序上,我一般固定走三步。第一步,确认工具链和环境:adb能不能用、Java 版本对不对、framework 装没装。第二步,确认操作对象:目标 APK 是不是系统应用、依赖哪些 framework、有没有 ODEX 需要处理。第三步,才是改和打包。前两步没确认就动手,后面每个报错都会把你带偏。
还有一个验证技巧值得单独说:改完framework-res.apk之后,不要直接推回系统分区,先做成刷机包放 recovery 里备用。推回去重启,能进系统且菜单正常,才算过;一旦 FC,立刻进 recovery 刷回原包。这个“后悔药”机制救过我很多次,尤其是汉化初期文本长度没控制好的阶段。
ODEX 合并的验证则更直接:baksmali反编译后看out目录的文件数量是否和预期相符,smali回打包后看classes.dex大小是否合理,再放回 APK 打包、替换、重启,应用能正常启动且功能不丢,说明合并链路通了。任何一步文件大小异常偏小,基本就是启动类没备齐,回去补 jar。
从那以后我每次动系统 APK,都强制走一遍“备份原始包 → 装 framework → 反编译 → 改 → 看日志打包 → 原始包做壳替换 → recovery 兜底”的流程,一步不省。这套流程不保证一次成功,但保证每次失败都能快速回退、快速定位。希望帮到你。
本文还有配套的精品资源,点击获取