背景
由于拿到一个第三方的 app 安装到我们车机上不能显示完整的 UI 界面,发现此 app 的 targetSdk=18,而我们车机 =27,解决方案是修改 app 的 targetSdk=27
可能的解决方案
由于 apk 中的 xml 文件都是经过编译后二进制文件,不能够直接修改,于是凭我肤浅的水平,认为可能的操作方案如下:
- 找到二进制中代表 targetSdk 值的位置,直接修改此处二进制值
- 将 apk 中反编译的代码开到一个新项目里重新编译
1. 直接修改二进制 AndroidManifest
要搞懂二进制数据的结构才能被有效读取,我在查找 targetSdk 时参考了以下这篇文章
Android逆向之旅—解析编译之后的AndroidManifest文件格式
研究了一下结构以后,发现直接修改 Manifest 的办法并不是很好,是一个看起来简单,但实际复杂的方式,而且背后一定还藏着别的什么不知道的坑,于是放弃了这种办法
2. 将 apk 反编译的代码开一个新项目编译
反编译 classes.dex 用到的工具:
dex2jar
命令:d2j-dex2jar --force classes.dex
反编译 AndroidManifest 的工具:
Apktool
命令:apktool d xxx.apk
新开一个 Android Studio Application 工程,将反编译的 AndroidManifest 文件替换工程下的 AndroidManifest,并将反编出来的 classes.jar 放到 libs 下进行依赖,同时删除 build.gradle 中多余的 implementation,只保留我们用到的 jar
再把 src/main 目录下除 AndroidManifest 外的内容删除(包括 res 目录)
这样,基本上结构就搭起来了。
另外,由于我们删除了 res,会导致 AndroidManifest 里面的有关 Theme 的属性会报错找不到资源,如果对 UI 背景没有要求的话,最快的办法就是直接删除这些属性就好了。
最重要的事别忘了,修改 build.gradle 中的 targetSdk=27
所以你以为到这里就可以了吗,No,还有很关键的几点要做,这也是我踩完坑后的心血。
很关键的几点
-
删除 jar 里的 R.class 和 BuildConfig.class
我们要手动把 classes.jar 中的 R 和 BuildConfig 删除,避免打包时发生异常:Program type already present
注意只是删除 R.class,其他的什么 R$id.class,R$drawable.class 不要动 -
使用 assembleRelease 来打包一个未签名的 apk
为什么要未签名的?因为我们要自己加签。由于签名时会对资源文件做处理,我们无法使用原 apk 中的签名文件 -
替换资源
将原 apk 中的 resources.arsc, res 目录替换到我们编译的 apk -
签名
我们用 jdk 的签名工具 jarsigner 来做签名,首先制作一个签名文件 .jks,再使用命令
// sign_file.jks 签名文件路径
// signed.apk 签名后新的 apk 路径
// no-sign.apk 未签名的 apk 路径
// test 签名别名,制作签名文件时设置的 alias
jarsigner -verbose -keystore sign_file.jks -signedjar no-sign.apk signed.apk test
至此,一个只修改了 AndroidManifest 再重编译的 apk 就实现好了
延伸思考:当我们想快速将一个在较低平台上实现的 apk 应用到较高版本的平台,此办法是一个非常有效率的方法,能够达到快速编译部署并开始使用。