Android Studio如何做混淆

举报
yd_221104950 发表于 2020/12/03 00:56:17 2020/12/03
【摘要】 概述 ProGuard是一个Java类文件压缩器、优化器、混淆器、预校验器: 压缩阶段会检测和移除未使用的类、字段、方法、属性。ProGuard以递归的方式检查并决定哪些类和类成员是被用到的,而其他没有用到的类和类成员就会被丢弃。优化阶段会分析并优化方法的字节码。ProGuard会进一步优化代码。其他优化包括不是入口点的类或方法可能会变成private、final、...

概述

ProGuard是一个Java类文件压缩器、优化器、混淆器、预校验器:

  • 压缩阶段会检测和移除未使用的类、字段、方法、属性。ProGuard以递归的方式检查并决定哪些类和类成员是被用到的,而其他没有用到的类和类成员就会被丢弃。
  • 优化阶段会分析并优化方法的字节码。ProGuard会进一步优化代码。其他优化包括不是入口点的类或方法可能会变成private、final、static,而没有使用的参数可能会被移除等。
  • 混淆阶段会用简短的无意义的名称来重命名剩下的类名、字段名、方法名。ProGuard会重命名那些不是入口点(说白了入口点就是公开的,public的)的类和类成员,会保留那些入口点(public的),保证他们能够正确地访问。

进行完以上三个段后,我们的apk包会更小、更高效,并且更难进行反向工程。

  • 预校验阶段会将预验证信息添加到类中,这是Java Micro Edition所必需的,或者可以缩短Java 6的启动时间。只有这一阶段不需要知道入口点的。
    在这里插入图片描述

ProGuard读取Jar包(或者是wars、ears、zips或者目录。然后开始压缩资源、优化代码、混淆代码和预校验它们。

前面这四个步骤都是可选的。

proguard-rules.pro文件配置

为了决定哪些代码要保留、哪些代码要丢弃或混淆,我们必须在配置文件中指明。proguard-rules.pro文件可以配置的选项:

  1. 输入输出选项
-include {filename} 从给定的文件中递归读取配置选项
@filename  '-include filename'的简写
-basedirectory {directoryname} 指定为这些配置参数或配置文件中的相对文件提供基础目录
-injars {class_path} 指定要处理的应用程序的jar,war,ear和目录
-outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称,我们应该避免输出的文件重写输入文件,不使用这个选项的话就可以做到,就不会有任何jar包被重写。
-libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件,这里的文件就不会被包含进输入jars中。
-dontskipnonpubliclibraryclasses 指定跳过非公共类当读取库jar包时,加速处理和减少ProGuard的内存使用。默认情况下,ProGuard会读取非公共和公共库类文件。
-dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。
-skipnonpubliclibraryclasses 指定路过的非public类,当读库文件,加速处理和减少ProGuard的内存占用。默认情况ProGuard读取非public和public库文件。但是有些库包含了扩展自public库类的非public库类,如果使用了此选项就会报错,所以建议不要使用此选项。
-keepdirectories [directory_filter]指定要在输出jars文件中保留的目录。默认情况下,目录入口都会被移除,可以减少jar包的大小。如果不加过滤目录,只指定了这个选项,则全部目录都会被保留,否则只保留过滤目录。
-target version 指定版本号,用于设置被处理好的类文件。默认,类文件的版本号设置后不会再变,如果要更新类文件,就可以改变这个版本号,就会去校验它。
-forceprocessing 指定处理输入,即使输出已经是最新的了。更新的检测是基于对比指定的输入文件、输出文件、配置文件或目录的时间戳。

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 压缩选项
-dontshrink 关闭压缩。默认开启,处理时会移除除用-keep保留(间接或直接依赖的都会被保留)的之外未被使用的类和类成员,并且会在优化阶段再次执行,因为优化后可能会再次暴露出一些未被使用的类和成员。
-printusage {filename} 将输入类文件中没有用的代码列出来,并打印到标准输出或给定的文件中
-whyareyoukeeping {class_specification} 给出为什么在压缩步骤要保留某些类和类成员的理由,当你想知道某个给定的元素为什么会在输出中时,就可以查看这个理由。这个选项只在压缩阶段有效。

  
 
  • 1
  • 2
  • 3
  1. 保留选项
-keep {Modifier} {class_specification} 指定要保留的类文件和类的成员
-keepclassmembers {modifier} {class_specification} 指定要保留的类成员,如果此类也保留会更好
-keepclasseswithmembers {class_specification} 保留指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
-keepnames {class_specification}  保留指定的类和类的成员的名称,如果他们在压缩步骤中,不会被删除。这是 -keep,allowshrinking class_specification的简写。例如你要保留所有实现了Serializable接口的类名。在混淆时起作用。
-keepclassmembernames {class_specification} 保留指定的类的成员的名称,如果他们不会被删除,在压缩步骤中。这是-keepclassmembers,allowshrinking class_specification的简写。在混淆时起作用。
-keepclasseswithmembernames {class_specification} 保留指定的类和类成员的名称,前提是在压缩步骤,这些类和类成员都还存在。这是-keepclasseswithmembers,allowshrinking class_specification的简写。
-printseeds {filename} 列出-keep选项的清单的类和类成员,将打印到标准输出或输出到给定的文件。这将会非常有用,可以用于验证已扩展的类成员是否能找到,尤其是用了通配符的情况下。如,可以例出所有你保留的,不用混淆的类和类成员。

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 优化选项
-dontoptimize 关闭优化。默认开启,所有方法都会在字节码级别执行优化,让应用运行的更快。
-optimizationpasses n 指定优化次数。默认,一次。
-assumenosideeffects {class_specification} 指定没有副作用的方法(除了可能会返回值的)。在优化阶段,ProGuard会删除对这类方法的调用,如果返回值没有被使用。ProGuard会分析你的代码,会自动查找这一类的方法。ProGuard不会去分析库代码,正因如此,这个选项还是很有用的。
-allowaccessmodification 指定在处理期间可以扩展类和类成员的访问修饰符 
-mergeinterfacesaggressively 可以合并接口,当它们的实现类没有实现所有接口方法时。这可以减少输出文件的大小从而减少类的总数。

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 混淆选项
-dontobfuscate 关闭混淆。默认开启,增大反编译难度,类和类成员会被随机命名,除非用keep保护。
-printmapping {filename} 指定输出类的新名称与旧名称和改名后的类成员。可以输出到标准输出或指定的文件。
-applymapping {filename} 重用-printmapping输出的映射,用于进行增量式混淆
-obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法能和字段的名称,默认情况下是使用像'a', 'b'等这样的短名称。
-classobfuscationdictionary filename 指定包含合法的用于混淆后的类的名称的字符集合的文本文件。
-packageobfuscationdictionary filename 通过文本文件指定合法的包名。
-overloadaggressively 混淆时允许多个字段和方法可以得到相同的名称,只要它们的参数和返回类型不同(不仅仅是它们的参数)。这可以让程序代码更小。
-useuniqueclassmembernames 分配相同的混淆名称给有相同名称的类成员。确定统一的混淆类的成员名称来增加混淆。
-flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
-repackageclasses [package_name] 重新包装所有已重命名的类文件,并将它们移动入给定的单一包中。
-dontusemixedcaseclassnames 混淆时不产生混合大小写的类名。默认情况下会。
-keeppackagenames [package_filter] 不混淆给定的包名  
-keepattributes {attribute_name,...} 保留给定的可选属性,例如Exceptions, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, and AnnotationDefault,InnerClasses
-keepparameternames 保留参数名和方法类型。
-renamesourcefileattribute {string} 设置源文件中给定的字符串常量

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 预验证选项
-dontpreverify 关闭验证,默认是开启的。
-microedition 指定被处理的类文件对应的Java Micro Edition。 

  
 
  • 1
  • 2
  1. 通用选项

-verbose 在处理期间,写出更多的的信息,如果遇到异常终止了,这个选项将打印出所有栈跟踪的信息。
-dontnote [class_filter] 不打印有关配置中潜在错误或遗漏的注释
-dontwarn [class_filter] 不警告未解决的引用和其他重要的问题
-ignorewarnings 打印有关未解决的引用和其他重要问题的任何警告
-printconfiguration [filename] 用包含的文件和替换的变量写出已分析的整个配置
-dump [filename] 写出类文件的内部结构

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

至此,Proguard.pro文件里可以的配置的东西都在上面有说明了,接下来我们看看如何用。

例子

压缩、优化、混淆的关闭

  • 压缩(Shrinking):默认开启
  -dontshrink #关闭压缩

  
 
  • 1
  • 优化(Optimization):默认开启
 -dontoptimize  #关闭优化

  
 
  • 1
  • 混淆(Obfuscation):默认开启
  -dontobfuscate  #关闭混淆

  
 
  • 1

星号通配符:*代表当前包下的类,**代表当前包及子包下的类

  • 保持某个类名不被混淆
  -keep class com.wong.BaseDaoImpl

  
 
  • 1
  • 一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆
  -keep class com.wong.demo.*

  
 
  • 1
  • 两颗星表示把本包和所含子包下的类名都保持;
  -keep class com.wong.demo.**

  
 
  • 1

注意:上面三种方式,类名虽然未混淆,但里面的类成员名称还是被混淆了

  • 保持某个类的类名及内部的所有内容不会混淆
  -keep class com.wong.BaseDaoImpl{*;}

  
 
  • 1
  • 既可以保持该包下的类名,又可以保持类里面的内容不被混淆;
  -keep class com.wong.demo.*{*;}

  
 
  • 1
  • 既可以保持该包及子包下的类名,又可以保持类里面的内容不被混淆;
  -keep class com.wong.demo.**{*;}

  
 
  • 1
  • 保持类中特定内容,而不是所有的内容可以使用如下:
  -keep class com.wong.bean.MyBean{ <init>; #匹配所有构造器 <fields>;#匹配所有域 <methods>;#匹配所有方法
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

上面就保护了MyBean这个类中的所有的构造方法、变量、和方法不被混淆。
可以在<fields>或<methods>前面加上private 、public、native等来进一步指定不被混淆的内容,如:

 -keep class com.wong.BaseDaoImpl{ public <methods>;#保护该类下所有的公有方法不被混淆 public *;#保护该类下所有的公有内容不被混淆 private <methods>;#保护该类下所有的私有方法不被混淆 private *;#保护该类下所有的私有内容不被混淆 public <init>(java.lang.String);#保护该类的有String类型参数的构造方法不被混淆
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 要保护一个类的内部类不被混淆用 $ 符号,如保护BaseDaoImpl类的内部MyInnerClass不被混淆
  -keep class com.wong.BaseDaoImpl$MyInnerClass{*;}

  
 
  • 1
  • extends、implement:保护Android底层组件和类不要混淆
  -keep public class * extends android.app.Activity
  -keep public class * extends android.app.Application
  -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.view.View

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 不需要保持类名,只需保持该类下的特定方法保持不被混淆,如保持MyProguardTest类下test(String)方法不被混淆
  -keepclassmembernames class com.wong.MyProguardTest{ public void test(java.lang.String);
  }

  
 
  • 1
  • 2
  • 3
  • 保留类和类成员,类和类成员都不会被混淆
  -keepclasseswithmembernames class com.wong.MyProguardTest1

  
 
  • 1

重要注意事项

  • jni方法不可混淆,因为native方法是用完整的包名类名方法名来定义的,不能修改,否则找不到
 -keepclasseswithmembernames class * { native <methods>; 
}

  
 
  • 1
  • 2
  • 3
  • AndroidMainfest中的类不混淆,所以四大组件和Application的子类和Framework层下所有的类默认不要进行混淆
  • 用GSON、FastJson等框架解析服务端数据时,所写的JSON对象类(实体类)不要混淆,否则无法将JSON解析成对应的对象。
  • 使用第三方开源库或者引用其他第三方的SDK包时,如果有特别要求,也需要在混淆文件中加入对应的混淆规则
  • WebView的JS调用Android写的接口需要保证接口方法不混淆,否则js找不到对应的方法调用
  • 自定义的View默认也不会被混淆
  • 反射用到的类混淆时需要注意:只保持反射用到的类名和方法即可,否则也会找不到,并不需要将整个被反射到的类都进行保持
  • 使用enum时,不要混淆以下两个方法,它们会被反射调用到,如果混淆了,反射就会失效,找不到方法。
  -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }

  
 
  • 1
  • 2
  • 3
  • 4
  • Parcelable的子类和Creator静态成员变量不混淆,否则会产生Android.os.BadParcelableException异常;
  -keep class * implements Android.os.Parcelable { public static final Android.os.Parcelable$Creator *;
  }

  
 
  • 1
  • 2
  • 3

Android Studio启用混淆

1、开启混淆
Android Studio自身集成Java语言的ProGuard作为压缩、优化、混淆的工具,配合Gradle构建工具使用。只需要在工程应用目录的gradle文件中设置minifyEnabled为true即可:

android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、在proguard-rules.pro文件中加入混淆规则

# 代码混淆压缩比,在0~7之间,默认为5,一般不下需要修改
-optimizationpasses 5

# 混淆时不使用大小写混合,混淆后的类名为小写
# windows下的同学还是加入这个选项吧(windows大小写不敏感)
-dontusemixedcaseclassnames

# 指定不去忽略非公共的库的类
# 默认跳过,有些情况下编写的代码与类库中的类在同一个包下,并且持有包中内容的引用,此时就需要加入此条声明
-dontskipnonpubliclibraryclasses

# 指定不去忽略非公共的库的类的成员
-dontskipnonpubliclibraryclassmembers

# 不做预检验,preverify是proguard的四个步骤之一
# Android不需要preverify,去掉这一步可以加快混淆速度
-dontpreverify

# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
#混淆前后的映射
-printmapping proguardMapping.txt
#apk 包内所有 class 的内部结构
-dump class_files.txt
#未混淆的类和成员
-printseeds seeds.txt
#列出从 apk 中删除的代码
-printusage unused.txt

# 指定混淆时采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

# 保护代码中的Annotation不被混淆
# 这在JSON实体映射时非常重要
-keepattributes *Annotation*

# 避免混淆泛型
# 这在JSON实体映射时非常重要
-keepattributes Signature

# Gson specific classes
-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.** { *;}

# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

# 保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * { native <methods>;
}

# 保留了继承自Activity、Application这些类的子类
# 因为这些子类有可能被外部调用
# 比如第一行就保证了所有Activity的子类不要被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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

#support.v4/v7包不混淆
-keep class android.support.** { *; }
-keep class android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v7.** { *; }
-keep public class * extends android.support.v7.**
-keep interface android.support.v7.app.** { *; }
-dontwarn android.support.** # 忽略警告

# 保留Activity中的方法参数是view的方法,
# 从而我们在layout里面编写onClick就不会影响
-keepclassmembers class * extends android.app.Activity { public void * (android.view.View);
}

# 枚举类不能被混淆
-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String);
}

# 保留自定义控件(继承自View)不能被混淆
-keep public class * extends android.view.View { public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); public void set*(***); *** get* ();
}

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

# 不混淆实体类
-keep class com.jxty.app.garden.model.** { *; }

# 保留Serializable 序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient <fields>; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve();
}

# 对R文件下的所有类及其方法,都不能被混淆
-keepclassmembers class **.R$* { *;
}

# 对于带有回调函数onXXEvent的,不能混淆
-keepclassmembers class * { void *(**on*Event);
}


  
 
  • 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
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

执行ProGuard后会生成的文件:

1)dump.txt 描述apk文件里的所以类的内部结构
2)mapping.txt 列出了原始的和混淆后的类、方法和属性的对应关系
3)seeds.txt 列出了没有被混淆的类和属性
4)usage.txt 列出了没有被打到apk文件中的代码
路径:项目目录/module名称/build/outputs/mapping/release
也可以直接在Android studio中查看:
在这里插入图片描述
谢谢阅读

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/102606205

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。