本文主要讨论对apk文件的压缩和混淆中的细节问题以及分析在压缩混淆过程中遇到的问题的原因.
ProGuard压缩混淆Java代码
ProGuard的用法和自定义规则可以参考Android Proguard(混淆), 本文不作陈列说明.
压缩的意思是删除没有被直接使用的类和类成员(包括fields和methods).
混淆的意思是将类或者类成员重命名为不规则的名字, 通常是字母.
注意点
1. ProGuard仅可处理Java类文件
ProGuard针对的是Java类文件(Java class file
), 所以不能处理非Java类文件, 例如xml文件, 图片文件.
2. Android中ProGuard作用
ProGuard提供4个功能, 压缩(shrinker), 优化(optimizer), 混淆(obfuscator)和预校验(preverifier), 但是在Android中默认会关闭优化和预校验功能.
官方文档的解释是
Optimization is turned off by default. Dex does not like code runthrough the ProGuard optimize and preverify steps (and performs some of these optimizations on its own).
3. SDK相关的混淆处理
虽然没有找到相关的说明, 但是在Android Studio 2.2, Android Plugin是2.2.0的情况下, 开启minifyEnabled true
但是不使用proguardFiles
提供任何配置文件, 仍然会正常压缩混淆代码, 并且会保留Activity
, Keep
等SDK类. 但是如果你提供自己的配置文件, 那么记得加上getDefaultProguardFile('proguard-android.txt')
4. -injars
, -outjars
和-libraryjars
-injars
: 指定需要经过ProGuard处理的文件
-outjars
: 指定经过处理后输出的文件名
-libraryjars
: 指定不需要经过ProGuard处理的文件
这3个命令不会在Android中用到, 因为Andorid Plugin会自动将引用的库加入到injars
中.
5. -keep
指令
具体的用法还是建议看文档, 戳这里
关键格式
-keep [,modifier,...] class_specification
值得注意的点
-
modifier
中可以使用includedescriptorclasses
参数来保护在类成员提到的类不被混淆, 例如指定保护了方法void method(Param p)
, 如果不带这个参数,Param
是可以被混淆的(没有其他设置指明保护它的时候), 使用这个参数则可以防止Param
被混淆, 具体描述看文档 - 在
class_specification
中, 类和类成员是两种描述对象, 就是说可以仅仅保护类但是不保护其中的类成员, 例如-keep public class com.sample.A
, 这里仅仅指定了类, 所以类A不会被删除或者被混淆, 但是A里面的fields和methods则可以被删除和被混淆; 再例如-keep public class com.sample.A{*;}
, 这里不仅保护A类, 而且保护A类里面所有类成员(包括fields和methods)不被删除和混淆. -
class
关键字是包含了类和接口的 - 指明方法的时候具体的参数和返回值的类型是必须指定的, 可以使用
***
来匹配任意参数类型
6. -keep
和-keepnames
的区别
-keep
的意思是符合条件的类和类成员既不会被压缩也不会被混淆
-keepnames
是-keep,allowshrinking
的缩写, 而allowshrinking
的意思是允许符合条件的类和类成员被压缩(删除)
7. 使用@Keep
保护特定类和类成员
如果引入了android.support.annotation
库可以使用@Keep
来在代码中保护指定的类和类成员.
实践效果
- 放到类前, 会保护类和所有类成员, 相当于
-keep class A {*;}
- 放到类成员前, 会保护类和对应的类成员, 相当于
-keepclassmembers class A {fieldType fieldName;}
- 放在Method前时, 不会保护参数不被混淆
8. AndroidManifest.xml
中使用的类
不添加额外的配置, 仅使用默认的配置, 测试结果是会保护在AndroidManifest.xml
直接使用的类和继承过来的类成员, 但是不会保护添加的类成员
9. R文件
app的默认编译过程会把R文件的引用转成具体的值, 例如如果R.layout.activity_main = 1
, 那么所有用到R.layout.activity_main
的地方都会用1
代替, 然后R文件不会包含到apk中, 因此如果有通过字符串获取资源文件, 则需要手动保护R文件
NOTE: 从一些地方看到的说法是"R文件有可能不会被包含进apk"
ProGuard QA
Q: 在Android优化中使用-libraryjars
报错
Warning:Exception while processing task java.io.IOException: The same input jar some.jar is specified twice.
注: 支付宝移动支付sdk的混淆配置
A: 因为在build.gradle
中声明依赖关系的时候一般会通过compile
命令声明编译某个jar包, 如
compile fileTree(include: '*.jar', dir: 'libs')
而编译jar包相当于-injars
命令, 而这两个命令是冲突的, 所以只要在依赖关系中引入了某个jar包就不能再对该jar包使用-injars
或者-libraryjars
命令
Q: 报红色warning, 提示各种InnerClasses或者EnclosingMethod****
A: 网上所有建议都是添加-keepattributes InnerClasses,EnclosingMethod
, 但是添加之后可以消除一些, 还是会有, 不过不影响编译.
NOTE: 查阅一些资料之后我推测是因为SDK编译时候使用的JDK版本的原因导致这些问题, 但是不能确认
Q: ProGuard文档中的Entry Point的意思****
A: 使用-keep
可以使指定的类和类成员成为Entry Point, 其实就是扫描开始的地方, 即使没有其他人直接引用这个类或者类成员, 也保留它.
Android中ProGuard的优化结果
优化结果文件会输出到<module-name>/build/outputs/mapping/release/
目录.
-
dump.txt
: 描述APK中所有类文件的内部结构(internal structure) -
mapping.txt
: 提供混淆前后类(class)名, 方法(method)名和成员变量(field)名的对应关系 -
seeds.txt
: 列出没有被混淆的类和成员(classes and members) -
usage.txt
: 列出从APK中移除的代码(code)
分析混淆后的报错信息可以参考这篇文章 android-how-to-decode-proguards-obfuscated-code-from-stack-trace, 其实有个带界面的小工具来分析报错信息的.
Resource shrinking压缩资源文件
以下内容都来自Shrink Your Resources
ProGuard不能压缩资源文件, 所以在Android中是使用Gradle的Androidd插件中的Resource shrinking来移除没有被使用的资源文件的, 包括库文件内的资源文件. 它会在ProGuard压缩之后运行, 所以被没有使用的类引用的资源文件也会被删除.
通过
shrinkResource true
minifyEnabled true // 开启ProGuard是前提条件
开启压缩资源文件
NOTE: Resource shrinking暂时(2016/11/2)不会对res/values
内的文件进行压缩
自定义规则
创建一个包含<resources>
的xml文件放到资源目录, 通过tools:keep
指定保留的资源文件, 通过tools:discard
指定明确删除的资源文件. 指定资源文件时通过,
分隔, 通过*
匹配任意字符. 例如
xmltools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
NOTE: 官方文档中没有说该文件需要特定的文件名和路径, 仅举例res/raw/keep.xml
. 该文件不会被放进最后的apk文件中.
被动态使用的资源
Android中可以通过[Resources.getIdentifier()](https://developer.android.com/reference/android/content/res/Resources.html#getIdentifier(java.lang.String, java.lang.String, java.lang.String))来通过字符串匹配来动态获取某个资源文件id, 当使用了这个方法时(v7 appcompat library
中使用了), Resource shrinking不会压缩符合字符串规则的文件.
可以通过tools:shrinkMode="strict"
来关闭这个默认行为, 指明只有明确被引用的资源才保留, 代码中动态引用的资源则默认不保留.
Resource shrinking压缩结果
资源文件压缩后可以通过<module-name>/build/outputs/mapping/release/resources.txt
来查看所有资源文件的关系.
aar库包含混淆规则
参考生成带混淆配置的aar库
关键属性是consumerProguardFiles
库的build.gradle中配置类似
android {
defaultConfig {
minifyEnabled true
consumerProguardFiles 'consumer-proguard-rules.pro'
}
}
注意, 因为启用了
minifyEnabled
, 因此编译库时会混淆整个lib, 这样会导致外部工程不能正常引用库中的类, 因为类名被混淆了, 所以需要添加混淆规则, 确保库暴露给外部的API相关类不被混淆
END
这边文章的主要目的是分析ProGuard优化Android代码时遇到的问题, 欢迎在讨论区指出不明白的地方或者提出你遇到的问题, 大家一起研究.