Android 混淆笔记

命令

命令 作用
-keep 防止类和成员被移除或者被重命名
-keepnames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除
-keepclassmembers 保留类中的成员,防止他们被混淆或移除
-keepclassmembernames 只保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除
-keepclasseswithmembers 保留类和类中的成员,防止它们被混淆或移除,前提这个类中的成员必须存在,如果不存在则还是会混淆
-keepclasseswithmembernames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是这个类中的成员必须存在,如果不存在则还是会混淆

通配符

通配符 描述
<field> 匹配类中的所有字段
<method> 匹配类中的所有方法
<init> 匹配类中的所有构造函数
* 匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.example.test.MyActivity,使用com.*,或者com.exmaple.*都是无法匹配的,因为*无法匹配包名中的分隔符,正确的匹配方式是com.exmaple.*.*,或者com.exmaple.test.*,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西
** 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包
*** 匹配任意参数类型。比如void set*(***)就能匹配任意传入的参数类型,*** get*()就能匹配任意返回值的类型
匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法

编写代码测试

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
private int field1;
private int field2;
public Test() {
System.out.println("this is test init method");
}
void test() {
System.out.println("this is test method");
}
public int getField1() {
return field1;
}
public void setField1(int field1) {
this.field1 = field1;
}
public int getField2() {
return field2;
}
public void setField2(int field2) {
this.field2 = field2;
}
}
  • 开启混淆,不保留Test类,并且Test类没有被引用,则Test类被移除,如果被引用则类会被混淆,被引用的方法被保留,如下:

调用方法

1
Test t = new Test();

混淆结果(构造方法被保留)

1
2
3
4
5
6
7
public class a
{
public a()
{
System.out.println("this is test init method");
}
}
  • 保留Test类,使用-keep保留,结果如下:

混淆语句:-keep class li.yohan.androidsample.bean.Test

是否初始化:是

混淆结果:类名和构造方法被保留了下来

1
2
3
4
5
6
7
public class Test
{
public Test()
{
System.out.println("this is test init method");
}
}

!如果没有引用这个类,类仍然会被保留,混淆的结果和上面一样,另外如果类中的其他方法被调用,则该方法会被保留,但是会被混淆


混淆语句:-keep class li.yohan.androidsample.bean.Test { *; }

是否初始化:-

混淆结果:类全部被保留了下来(内容和源码基本一致)

*的意思是保留所有内容

  • 使用-keepnames保留

混淆语句:-keepnames class li.yohan.androidsample.bean.Test

混淆结果:没有被引用的话,类被移除。如果被引用的话会保留类名和构造方法。


混淆语句:-keepnames class li.yohan.androidsample.bean.Test { *; }

是否调用:是

混淆结果:类名和构造方法被保留

1
2
3
4
5
6
7
public class Test
{
public Test()
{
System.out.println("this is test init method");
}
}

!如果其他方法被调用则该方法会被保留且不会被混淆


  • 使用-keepclassmembers

混淆语句:-keepclassmembers class li.yohan.androidsample.bean.Test { *; }

混淆结果:如果被引用类名被混淆但是其他成员和方法被完整保留了下来,如果没有引用则类被移除。

  • 使用-keepclassmembernames

混淆语句:-keepclassmembernames class li.yohan.androidsample.bean.Test { *; }

混淆结果:如果被引用类名会被混淆,其他方法被移除。如果有方法被调用则该方法会被保留且不会被混淆。如果没有引用则类被移除。

  • 使用-keepclasseswithmembers

混淆语句:

1
2
3
-keepclasseswithmembers class li.yohan.androidsample.bean.Test {
void test();
}

混淆结果:在没有引用的情况下保留了构造方法和test方法,其他方法在没有调用的情况下被移除,如果有调用的话会被混淆。

1
2
3
4
5
6
7
8
9
10
11
12
public class Test
{
public Test()
{
System.out.println("this is test init method");
}
void test()
{
System.out.println("this is test method");
}
}

!如果把混淆语句中的void test()换成void test1()则保留语句会无效

  • 使用-keepclasseswithmembernames

混淆语句:

1
2
3
-keepclasseswithmembernames class li.yohan.androidsample.bean.Test {
void test();
}

混淆结果:在没有引用的情况下类被移除。在test方法被调用的情况下类名和方法被保留,其他方法被移除或者被混淆如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test
{
public Test()
{
System.out.println("this is test init method");
}
public void test()
{
System.out.println("this is test method");
}
}

!如果test方法没有被调用则保留语句不起作用,和keepclasseswithmembers不同的是前者不需要调用也不需要引用类名和方法名都会保留而后者如果方法不被引用的话相当于没效果

填坑

在Android Studio开启instant run的情况下,混淆是被默认关闭的。