对于Android Obfuscation,相信很多人都会感觉困惑或一支半解,有些混淆规则并不能按照字面上去理解。
比如以下这条规则的含义:
-keep class com.devnn.*
这个规则看上去是保留com.devnn包里的类不被混淆。如果这么简单的理解就说明你对混淆还没有足够的了解。
如果你能自信地回答以下几个问题说明你对混淆有一定的研究。
- com.devnn.model包下的类会被混淆吗?
- com.devnn这个包名会被混淆吗?
- com.devnn下的类的父类会被混淆吗?
- com.devnn下的类的成员变量和方法会被混淆吗?
-keep class com.devnn.*
、-keep class com.devnn.**
、
-keep class com.devnn.**{ *;}
三者区别是什么?- 如何只混淆包下的类不混淆包的路径?
- 如何只混淆包的路径不混淆包下的类?
- 如何保留类下的部分方法不被混淆?
-keepclasseswithmembers xxx{xxx;}
会保留"members"吗?
带着以上疑问,我们来做一个Demo试一试就知道了。实践才能出真知,凭空想象是站不住脚的。
先建一个名为ObfuscationDemo的工程,随便建两个简单的类:Engineer和Student,它们共同的接口是Person。
目录结构如下:
Student类:
package com.devnn.model;
import com.devnn.interfaces.Person;
/**
* create by devnn on 2019-10-14
*/
public class Student implements Person {
@Override
public String say() {
return "I am a student!";
}
@Override
public String work() {
return "I can wash dishes!";
}
@Override
public String study() {
return "That's what I am doing!";
}
}
Engineer类:
package com.devnn.model;
import com.devnn.interfaces.Person;
/**
* create by devnn on 2019-10-14
*/
public class Engineer implements Person {
@Override
public String say() {
return "I am en engineer!";
}
@Override
public String work() {
return "I work very hard!";
}
@Override
public String study() {
return "I do study sometimes!";
}
}
在Activity里调用一下自定义类,否则会被视为无效代码在打包时被删除:
package com.devnn.obfuscationdemo;
import android.app.Activity;
import android.os.Bundle;
import com.devnn.model.Engineer;
import com.devnn.model.Student;
import com.devnn.test.Test;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
public void test(){
System.out.println("test");
Engineer engineer=new Engineer();
engineer.say();
engineer.study();
engineer.work();
Student student=new Student();
student.say();
student.study();
student.work();
}
}
开启混淆:
buildTypes {
debug{
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
跑一下assembleDebug命令,生成debug包。
反编译看一下源码:
发现混淆生效了,使用的是默认的混淆规则。我们发现默认的混淆规则就是按照小写字母表的顺序给包、类、成员重新命名。Activity类名及路径默认不被混淆,Actiivty自定义的成员默认是会被混淆的。
一、混淆规则:-keep class xxx.xxx.*
现在加上第一条混淆规则:-keep class com.devnn.*
重新assembleDebug一下。反编译后:
发现跟之前是一样的。
似乎不起作用?
将混淆规则改成-keep class com.devnn.model.*
试试。
这次的结果如下:
这下子就一目了然了。
-keep class com.devnn.model.*
的作用是保留com.devnn.mode包下的类名(包括路径)不被混淆,类成员和子包还是会被混淆的
-keep class com.devnn.*
因为com.devnn下没有类所以这条规则没有作用。
二、混淆规则:-keep class xxx.xxx.**
下面将混淆代码改成-keep class com.devnn.**
看看是什么效果。
结果如下:
可以看到两颗**表示对com.devnn下的子包的类都保留不被混淆,同时也仅仅是针对类名。
那么我们可以这样理解:
混淆规则的关键字-keep class xxx表示保留xxx这个类(包括路径)不被混淆,类成员和子路径的的类都不影响。
三、混淆规则:-keep class xxx.xxx.**{xxx;}
下面将混淆规则改成:-keep class com.devnn.**{*;}
结果如下:
我们发现,加上{*;}
后表示类成员也保留不被混淆。
有时候我们会看到这样的规则:
-keep class com.xxx.xxx.*{
public <fields>;
public <methods>;
*** set*(***);
*** get*();
}
那么这个规则也自然很好理解了,就是保留com.xxx.xxx下的类及其public成员和set、get方法不被混淆。
那么如何只保留包名不被混淆,包下的类被混淆呢?
四、混淆规则:-keeppackagenames xxx.xxx.xxx
我们试试:-keeppackagenames com.devnn.model
这条规则。
结果如下:
我们发现com.devnn.model这个包名确实是被保留了,其它都不受影响。
弄懂了上面的规则,-keeppackagenames com.devnn.*
也就很好理解了,它表示保留com.devnn下的一级包名不被混淆,-keeppackagenames com.devnn.**
表示保留com.devnn下的所有包名不被混淆。
验证一下-keeppackagenames com.*
的结果:
因为没有com.*这样的包,所以以上规则无效。
试一试-keeppackagenames com.**
:
我们发现com开头的包都保留了。
再试试一下-keeppackagenames com.devnn.*
:
我们发现,com.devnn下的两个包都保留了。
五、混淆规则:-keepclasseswithmembers class xxx.xxx.xxx{xxx;}
像-keepclasseswithmembers
这样的规则使用得比较少,字面意思是保留持有某种成员的类不被混淆,比如:
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
它表示main方法所在的类不被混淆,那么main方法会被混淆吗?不知道,试试。
我们在新建一个包名com.devnn.test并在里面建一个MainTest.java的类,完整内容如下:
我们在混淆文件中只设置一条规则:
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
打包并查看源码:
发现main方法以及所在类路径保留了。
为什么main方法保留了呢?难道是因为main方法被特殊处理了?
我们不用mina方法,换成其它名字:
在MainActivity里面调一下Test.test1方法:
Test.test1("aaa");
修改一下混淆规则:
-keepclasseswithmembers public class * {
public static void test1(java.lang.String);
}
打包并查看源码:
我们发现-keepclasseswithmembers
确实是保留了"classes"和"members"。
是不是似乎有某种相似?那其实以下两种规则的作用是一样的?
-keepclasseswithmembers public class * {
public static void test1(java.lang.String);
}
-keep class com.** {
public static void test1(java.lang.String);
}
实践一下,我们试试将混淆规则改成上面第二种,查看结果:
我们发现这两种规则的作用并不是一样的。
-keepclasseswithmembers public class * {
public static void test1(java.lang.String);
}
上面这条规则是必须匹配到包含有public static void test1(java.lang.String);
这个方法的类,保留保含此方法的类和此方法不被混淆。
-keep class com.** {
public static void test1(java.lang.String);
}
这条规则是即使没有找到public static void test1(java.lang.String);
这个方法,也照样保留类不被混淆(类成员还是会被混淆)。
六、混淆规则:-keepclassmembers class xxx.xxx.xxx{xxx;}
下面将混淆规则改成:
-keepclassmembers class com.** {
public java.lang.String say();
}
打包并查看源码:
发现除了say()方法保留了,其它内容都被混淆了。-keepclassmembers xxx.xxx.xxx(xxx)作用就是保留某些成员不被混淆,其它都被混淆。
经过以上实践,我们应该对
-keep class com.xxx.*
、
-keep class com.xxx.**
、
-keep class com.xxx.**{xxx;}
、
-keeppackagenames xxx.*
、
-keeppackagenames xxx.**
、
-keepclasseswithmembers public class * {xxx;}
、
-keepclassmembers xxx.xxx.xxx{xxx;}
这些混淆规则有深刻的认识了。