前言
代码混淆的内容很多,下面仅介绍免费混淆工具:ProGuard。如果想了解更多关于混淆的知识,建议在实现代码混淆后再根据需求在网上找解决方案。
一、ProGuard是什么?
ProGuard是一款免费的混淆java字节码的工具,除了将包名、类名、方法名和属性等替换成abcd等无含义的字符,还可以通过收缩字节码来减小应用程序的大小。
二、ProGuard使用
1. 开启混淆
ProGuard已经集成在Android Studio中,可以直接使用。使用方法也十分简单,只需要在app目录下build.gradle中将混淆开关minifyEnabled设置为true。如下所示:
android{
......
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
......
}
由于混淆的主要目的是防止他人通过反编译查看代码,所以一般只需要在正式打包release模式下设置为true,debug模式一般都是自己使用的,没有混淆的必要。
开启混淆前后对比,如下所示:(打包apk查看classes.dex)
可以看到混淆并没有将所有包名、类名等都换成无含义的数字和字母。这是因为ProGuard默认不会混淆4大组件和带有native关键字的方法。4大组件是安卓的基础混淆会导致报错;native是调用.so库方法时使用的关键字,由于java调用so库方法需要对应包名和类名,混淆后改变包名和类名会使其无法找到对应的方法,上图callJniMethod方法就是带有native关键字的。
对so库的知识不了解不影响阅读本文章,只需要知道并不是所有代码都适合混淆就可以了。
2. 混淆配置
在上面有提到存在不能混淆的代码,如何防止这部分代码混淆呢?在项目app目录下有个文件:proguard-rules.pro,这个就是混淆的配置文件(如果只混淆module,则在module下寻找proguard-rules.pro)。下面是我在网上找到的相关配置,可以用于参考,根据自己的需要配置。
保留类名
#一颗星表示只保持该包下的类名,而子包下的类名还是会被混淆(类中的成员都会被混淆)
-keep class com.habit.jnitest.*
#两颗星表示把本包和所含子包下的类名都保持
-keep class com.habit.jnitest.**
保留类名及类的成员
#保留 com.habit.jnitest包下的类及类的成员
-keep class com.habit.jnitest.*{*;}
#保留具体的某个类及类的成员
-keep class com.habit.jnitest.JniTest{*;}
如果不希望保留类的所有成员,可以使用:
<init>; //匹配所有构造器
<fields>; //匹配所有字段
<methods>; //匹配所有方法
# 如保留方法
-keep class com.habit.jnitest.ProGuardTest{
<methods>;
}
# 还可以在<init><fields><methods>前面加上private 、public等来进一步指定不被混淆的内容。
#如保留public方法
-keep class com.habit.jnitest.ProGuardTest{
public <methods>;
}
保留指定类的所有子类(implement/extends)
# 保留Activity的所有子类
-keep public class * extends android.app.Activity
压缩(Shrink)
#关闭压缩,在压缩处理中,用于检测和删除没有使用的类,字段,方法和属性。
-dontshrink
优化(Optimize)
#关闭优化,在优化处理中,对字节码进行优化,并且移除无用指令。
-dontoptimize
#迭代优化,n表示proguard对代码进行迭代优化的次数,Android一般为5
-optimizationpasses n
混淆(Obfuscate)
#关闭混淆,在混淆处理中,使用 a、b、c 等无意义的名称,对类,字段和方法进行重命名。
-dontobfuscate
预检(Preveirfy)
#禁用预校验,在预检中,主要是在 Java 平台上对处理后的代码进行预检。
-dontpreverify
3. 拓展了解
ProGuard的输出文件说明
混淆后,会在/build/outputs/mapping/release/目录下输出下方文件
dump.txt 说明 APK 中所有类文件的内部结构。
mapping.txt 提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt 列出未进行混淆的类和成员。
usage.txt 列出从 APK 移除的代码。
如果仔细观察“1. 开启混淆”中混淆前后的图,还会发现JniTest类有两个方法不见了,或许你会认为staticMethodCalledByJni和methodCalledByJni方法并没有消失,只是被混淆后,放在了混淆后的包名目录下。它们所在位置可以通过ProGuard的输出文件mapping.txt查询。
如下图所示,并没有找到staticMethodCalledByJni和methodCalledByJni方法。
如果这两个方法在java代码中并没有被调用,在混淆过程中将它们视为多余的方法会被删除,减少无用代码占用空间。但实际上,它们被so库调用了,混淆后会导致so库找不到这两个方法而报错。为防止这种状况,在混淆配置中保留这部分代码不被混淆。
#保留具体的某个类及类的成员
-keep class com.habit.jnitest.JniTest{*;}
ProGuard对代码的压缩
像上面提到的没有被java代码调用的方法会被删除,但没想到的是即使被java代码调用了也是会被删除的(注:ProGuard只是删除了方法的壳,方法的内容被集成到最终调用它的类中)。混淆效果大致如下:
混淆前,在MainActivity中调用ProGuardTest类的publicMethod方法。
public class ProGuardTest {
public void publicMethod() {
Log.d("test","publicMethod");
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ProGuardTest proGuardTest = new ProGuardTest();
proGuardTest.publicMethod();
}
}
混淆后,ProGuardTest类被删除,publicMethod()方法的内容被直接搬到了MainActivity中
public class MainActivity extends h {
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
Log.d("test", "publicMethod");
}
}