0%

安卓混淆

简介

ProGuard是一个压缩、优化和混淆Java字节码文件的开源工具,它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大限度地优化字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。常常用于Android开发用于混淆最终的项目,增加项目被反编译的难度。

作用

  • 压缩: 移除无效的类、属性、方法等
  • 优化: 优化字节码,并删除未使用的结构
  • 混淆: 将类名、属性名、方法名混淆为难以读懂的字母,比如a,b,c

混淆注意事项

不能混淆

  • 在AndroidManifest中配置的类,比如四大组件
  • JNI调用的方法
  • 用了反射的类
  • WebView中JavaScript调用的方法
  • Layout文件引用到的自定义View
  • 一些引入的第三方库(一般都会有混淆说明的)
  • Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常
  • 使用了枚举要保证枚举不被混淆

因为混淆之后,类名会变成a,b,c这,通过包名+类名就找不到该类了,会出现ClassNotFoundException异常。

保留java bean

使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆,混淆后属性名改变,变成了没有意思的名称,json转换就会出错

注解不能混淆

注解在Android平台中使用的越来越多,常用的有ButterKnife和Otto。很多场景下注解被用作在运行时反射确定一些元素的特征.

为了保证注解正常工作,我们不应该对注解进行混淆.Android工程默认的混淆配置已经包含了下面保留注解的配置

1
-keepattributes *Annotation*

Crash信息处理

代码混淆的时候记得加上在混淆文件里面记得加上这句:

keep住源文件以及行号

1
-keepattributes SourceFile,LineNumberTable

ProGuard用法和选项

保留选项:

注意选项的命名规律:-keep* 用于防止目标被移除或者重命名、-keep*names则仅仅用于防止重命名。

选项 说明
-keep -keep [,modifier,…] class_spec 作为入口点保留的类、类成员。对于库,所有公共成员都需要保留
-keepclassmembers -keepclassmembers [,modifier,…] class_spec 指定要保留的类成员,如果它们所属的类被保留的话
-keepclasseswithmembers -keepclasseswithmembers [,modifier,…] class_spec 指定需要保留的类,如果其成员符合条件
-keepnames class_spec 等价于-keep,allowshrinking class_spec 指定其名称需要保留的类和类成员,如果在压缩阶段这些类没有被删除的话。该选项仅用于混淆阶段
-keepclassmembernames -keepclassmembernames class_spec 指定名称要保留的类成员,如果在压缩阶段这些类没有被删除的话。该选项仅用于混淆阶段
-keepclasseswithmembernames -keepclasseswithmembernames class_spec 指定要保留的类和类成员,如果所有指定的类成员在经历了压缩阶段还存在
-printseeds [filename] 打印所有匹配-keep的类和类成员,默认打印到标准输出

-keep选项支持添加限定符,格式为: -keep,限定符 ,限定符包括:

限定符 说明
includedescriptorclasses 方法、字段的类型描述符中的任何类,跟随被keep的方法/字段一并保留,通常用于保留Native方法的名字时,防止Native方法参数的名字被修改,以保证和Native库兼容
allowshrinking 指定是否入口点可以被压缩
allowoptimization 指定是否入口点可以被优化
allowobfuscation 指定是否入口点可以被混淆

压缩选项

选项 说明
-dontoptimize 不进行优化。默认情况下优化启用,所有方法都在字节码级别进行优化
-optimizations opt_filter 在更细粒度上控制进行哪些优化
-optimizationpasses n 优化的步骤数,默认1步,如果发现没有可优化的,后续步骤自动省略
-assumenosideeffects class_spec 指定不具有副作用(不改变任何状态信息)的方法规格,如果这些方法的返回值没有被使用,那么这样的调用会清除
-allowaccessmodification 是否允许放宽类、类成员的访问限定符。这可能有利于优化,例如对getter()进行内联,需要相应字段成为public的
-mergeinterfacesaggressively 允许接口合并,甚至在实现类没有实现所有接口方法的情况下

混淆选项

选项 说明
-dontobfuscate 是否进行混淆,默认是。除了匹配-keep系列选项的类、类成员的名字将被随机的改为短名。为了方便调试而保留的内部属性,例如源代码名称、变量名、行号,都将被移除
-printmapping [filename] 打印混淆前后类名、类成员名的对照
-applymapping filename 指定一个先前生成的混淆名对照表,本次依照该对照继续混淆,不在表中的成员生成新的名字
-obfuscationdictionary filename 指定存放有效混淆后变量名的文件
-overloadaggressively 混淆时支持激进的重载,允许多个字段、方法使用重复的名字,只要参数和返回值不同
-useuniqueclassmembernames 让不同名的类成员具有不同的混淆后的名称,让同名的类成员混淆后的名称依旧相同
-dontusemixedcaseclassnames 混淆后,不使用混合大小写的类名
-keeppackagenames [pkg_filter] 指定不混淆的包的过滤器,过滤器支持* **和前导的!
-flattenpackagehierarchy [pkg_name] 对所有被重命名的包进行重新打包,如果不指定参数值,则打包到根目录
-repackageclasses [pkg_name] 对所有被重命名的类进行重新打包,如果不指定参数值,则打包到根目录
-keepattributes [attr_filter] 指定需要保留的所有可选属性,参数值是逗号分隔的,所有JVM或ProGuard支持的属性值。支持使用? * ** !。在处理库的时候,至少应该保留Exceptions, InnerClasses, Signature属性,如果程序依赖于注解,则应该保留
-keepparameternames 保留方法参数的名称和类型。该选项实质上保留了一个修剪(trim)后的LocalVariableTable、LocalVariableTypeTable这两个调试属性,在处理库时可能使用。注意,方法局部变量的名称依旧会混淆
-renamesourcefileattribute [string] 设置SourceFile、SourceDir 属性为指定的常量值
-adaptclassstrings [class_filter] 代表了类名的字符串常量值,是否也被混淆(与目标类的名字保持一致)。如果不指定参数值,所有代表类名的字符串常量都被混淆
-adaptresourcefilenames [file_filter] 是否重命名资源文件,如果其文件名反映了一个被混淆的类的名字
-adaptresourcefilecontents [file_filter] 是否修改资源文件中的类名,如果对应的类的名字已经被混淆。ProGuard使用平台默认字符集读取文件,如果需要改变这一行为,需要设置LANG环境变量或者JVM系统属性file.encoding

预校验选项

选项 说明
-dontpreverify 指定不进行字节码预校验,对于Java6+默认开启
-microedition 只是目标类文件将在JME平台上运行

一般选项

选项 说明
-verbose 在处理时打印冗长的信息
-dontnote [class_filter] 不打印配置选项中,与正则式匹配的类相关的错误或者疏忽
-dontwarn [class_filter] 不打印配置选项中,与正则式匹配的类相关的重要错误,例如unresolved references
-ignorewarnings 忽略所有警告,强制进行处理。这可能很危险
-printconfiguration [filename] 打印解析后的配置信息到目标文件
-dump [filename] 打印处理后的类文件的内部结构

通配符匹配规则

通配符 规则
匹配单个字符
* 匹配类名中的任何部分,但不包含额外的包名
** 匹配类名中的任何部分,并且可以包含额外的包名
% 匹配任何基础类型的类型名
* 匹配任意类型名 ,包含基础类型/非基础类型
匹配任意数量、任意类型的参数
匹配任何构造器
匹配任何字段名
匹配任何方法
*(当用在类内部时) 匹配任何字段和方法
$ 指内部类

一些规则

两个常用的混淆命令,

注意一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;两颗星表示把本包和所含子包下的类名都保持;

1
2
-keep class cn.hadcn.test.** 
-keep class cn.hadcn.test.*

用以上方法保持类后,你会发现类名虽然未混淆,但里面的具体方法和变量命名还是变了,这时如果既想保持类名,又想保持里面的内容不被混淆,我们就需要以下方法了

1
-keep class com.example.bean.** { *; }

在此基础上,我们也可以使用Java的基本规则来保护特定类不被混淆,比如我们可以用extend,implement等这些Java规则。如下例子就避免所有继承Activity的类被混淆

1
2
3
4
5
6
7
8
9
10
11
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
# 因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 混淆后,不使用混合大小写的类名
-dontusemixedcaseclassnames

# 指定需要保留的所有可选属性
# 更多 参考。https://www.guardsquare.com/en/proguard/manual/attributes
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod

# 打包到根目录
-repackageclasses ''

# 是否允许放宽类、类成员的访问限定符。
-allowaccessmodification

# 打印解析后的配置信息到目标文件
-printconfiguration proguard_test.txt

# 优化的步骤数
-optimizationpasses 5

# 在处理时打印冗长的信息
-verbose

# 不跳过依赖库中的非public类,4.5+默认值
-dontskipnonpubliclibraryclasses

# 不跳过包可见的依赖库类成员(字段、方法)
-dontskipnonpubliclibraryclassmembers


# 保留native方法的规格,以便能与native库链接
-keepclasseswithmembernames class * {
native <methods>;
}

# keep setters in Views so that animations can still work
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

# 删除输出的日志

-assumenosideeffects class com.tuya.smart.android.common.utils.L {

public static *** d(...);

public static *** v(...);

public static *** i(...);

public static *** e(...);

public static *** w(...);

}

# 保留Parcelable序列化类不被混淆
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}





内部类

1
2
3
4
-keepattributes Exceptions,InnerClasses,...  
-keep class [packagename].A{*;}
-keep class [packagename].A$* {*;}

其中 A$* 表示所有A的内部类都保留下来,也可以如下使用:

1
2
3
4
-keepattributes Exceptions,InnerClasses,...  
-keep class com.xxx.A{ *; }
-keep class com.xxx.A$B { *; }
-keep class com.xxx.A$C { *; }

参考资料

欢迎关注我的其它发布渠道