1.背景
为了实现apk字节码加密技术,我在CI上通过插入了脚本重新打包任务。然而,这改动导致apktool回编译apk包变大,文件经调研和分析发现:在apktool 2.0.3之后为了快速解压和打包,加入了反编译文件回编译不压缩
机制。该配置文件位于apktool.yml文件中。
![img_3465350eb0f6545ffcbe05c6f5ccfba6.png](https://i-blog.csdnimg.cn/blog_migrate/29f13cc2c026cbff86910af5bc8fb6c7.png)
![img_1ff33365d629acc19f14d52834b2de2b.png](https://i-blog.csdnimg.cn/blog_migrate/faca69b1f8241f6c4d38c555eb33eb8e.png)
几乎所有的二进制和非二进制文件打包时都未采用压缩,导致生成的apk变大(即使未签名依然比原apk大),最后在回编过程进行分析。
在反编译文件中查看yml文件,该文件记录了apk反编译信息(见apktool
源码brut/androlib/meta/MetaInfo
)compressionType
:记录arsc的是否压缩doNotCompress
:记录不压缩的文件类型和路径
2.解法
由于最新版本的apktool回编采用了不压缩的策略,导致我们的重编译apk变大。
可以想到的有两种方法:
1.通过apktool高版本来释放1.apk(framworks apk见上次分享),低版本处理反编译和打包的问题。
2.通过脚本将apktool.yml文件进行修改和定制,可以使得我们的release包更小。
3.YAML格式
我们需要对yaml格式的文件进行解析,有一个支持yaml格式的著名开源库snakeyaml
(用java实现)。
我采用jar的形式对这一部分代码(解析YAML和写入文件)进行实现。通过shell脚本插入该任务,从而达到了自动化过程。snakeyaml
的使用参考:
snakeyaml项目地址:https://bitbucket.org/asomov/snakeyaml
snakeyaml java使用:https://www.jianshu.com/p/d8136c913e52
4.CI上打包结果
通过对反编译脚本的优化使得回编译签名包和zipalign包显著减小。
原始apk | 反编译签名apk | 反编译zipalign-apk |
---|---|---|
23.90M | 22.48M | 22.48M |
![img_3d314cf54f69b54e6fd12e3fb9c10451.png](https://i-blog.csdnimg.cn/blog_migrate/cf5b059471566f0f4ba42eb4aaae2103.png)
5.脚本实现
#定义out文件的路径
rebApkFile=$cache_dir/rebuildApp.apk
echo "rebuildTools> 修改yml打包的配置文件"
#定义jar的路径,用来提取和修改apktool.yml文件
yamlFileCovert_jar=./yamlFileCovert.jar
#执行yamlFileCovert_jar
java -jar $yamlFileCovert_jar ${apkDecodeDir}/apktool.yml ${apkDecodeDir}/apktool.yml
echo "rebuildTools> 开始回编译apk并替换目标文件..."
apktool b -o ${rebApkFile} ${apkDecodeDir}
echo "rebuildTools> 开始签名新apk..."
...
java部分:
public class YamlFileParse {
public static void main(String[] args) {
String fileName;
String outFile;
if (args != null && args.length >= 2) {
fileName = args[0];
outFile = args[1];
} else {
System.out.print("请输入yml路径!\n" +
"java -jar yamlFileCovert.jar [in] [out] ");
return;
}
try {
MetaInfo metaInfo = loadYml(fileName);
saveYml(outFile, metaInfo);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void saveYml(String outFile, MetaInfo ret) throws IOException {
FileOutputStream fos = new FileOutputStream(new File(outFile));
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
Writer writer = new BufferedWriter(outputStreamWriter);
save(ret, writer);
outputStreamWriter.close();
}
private static MetaInfo loadYml(String fileName) throws IOException {
FileInputStream yamlInput = new FileInputStream(new File(fileName));
MetaInfo ret = new Yaml().loadAs(yamlInput, MetaInfo.class);
yamlInput.close();
ret.doNotCompress = null;
return ret;
}
private static void save(MetaInfo info, Writer output) {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
new Yaml().dump(info, output);
}
}
java的源码部分包括:yaml
格式文件的读写和MetaInfo
(用于apktool.yml序列化指定,原因见apktool.yml首行)类。
![img_0041def3f4486ee764e72f9163fc4c79.png](https://i-blog.csdnimg.cn/blog_migrate/6ebdf5665305c8eaf552750e4b4e7058.png)
以下是apktool.yml文件,由于文件中包含了MetaInfo类,所以必须指定yml序列化需要的类(这一部分拷贝了apktool源码中部分MetaInfo部分)。
!!brut.androlib.meta.MetaInfo
apkFileName: Rong360-release.apk
compressionType: false
doNotCompress: null
isFrameworkApk: false
packageInfo: {forcedPackageId: '127', renameManifestPackage: null}
sdkInfo: {minSdkVersion: '14', targetSdkVersion: '21'}
sharedLibrary: false
sparseResources: false
usesFramework:
ids: [1]
tag: null
version: 2.3.3
versionInfo: {versionCode: '316', versionName: 3.1.6}
运行java -jar $yamlFileCovert_jar ${apkDecodeDir}/apktool.yml
对yml进行修改后,回编apk,从而产物的apk大小达到了预期。