目录
一、前言
二、ProGuard简介
三、ProGuard原理分析
四、ProGuard注意事项
五、Android中的混淆
Android开启混淆的设置
ProGuard基本命令
Android混淆方案实例
一、前言
-
编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。C、C++、Delphi、Pascal、Fortran
-
解释型语言:不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低。Java、Basic、javascript、python
java文件先编译成与平台无关的.class的字节码文件,然后.class文件在java虚拟机(JVM)上进行解释运行。由于跨平台的特性,Java字节码中包含许多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法。这些信息很容易被反编译成Java源代码,所以我们使用混淆器对Java字节码进行混淆。
使用混淆的原因:
- 混淆后的代码很难被反编译,即使被反编译了也很难看懂真正语义。
- 混淆后的代码保留原来的档案格式和指令集,执行结果与混淆前一样。
二、ProGuard简介
ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具,它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大限度地优化字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。常常用于Android开发用于混淆最终的项目,增加项目被反编译的难度。__来自百度百科
ProGuard的四个功能:
- 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
- 优化(Optimize):对字节码进行优化,移除无用的指令。
- 混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
- 预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。
三、ProGuard原理分析
编译过程图来自ProGuard官网。Entry Point是在ProGuard过程中不会被处理的类或方法。在压缩的步骤中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非Entry Point的类、方法都会被设置为private、static或final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard会对非Entry Point的类和方法进行重命名。
四、ProGuard注意事项
不能混淆的情况:
- Java的反射:代码混淆,类名、方法名、属性名都改变了,而反射是按照原来的名字去反射,所以会反射错误。
- 注解:注解用了反射,所以不能混淆。
- native:不混淆任何包含native方法的类的类名以及native方法名,否则找不到本地方法。
- Activity:Activity不能混淆,因为AndroidManifest.xml文件中是完整的名字。
- 自定义view:自定义view也是带了包名写在xml布局中,不能混淆。
五、Android中的混淆
Android开启混淆的设置
app混淆的开启:
android{
buildTypes {
release {
minifyEnabled true //开启混淆
zipAlignEnabled true //压缩优化
shrinkResources true //移出无用资源
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
}
}
module混淆的开启(子模块是否混淆受app的build.gradle的控制;子模块混淆使用consumerProguardFiles关键字):
android{
buildTypes {
release {
consumerProguardFiles 'proguard-rules.pro'
}
}
}
ProGuard基本命令
通配符 描述
<field> 匹配类中的所有字段
<method> 匹配类中所有的方法
<init> 匹配类中所有的构造函数
* 匹配任意长度字符,不包含包名分隔符(.)
** 匹配任意长度字符,包含包名分隔符(.)
*** 匹配任意参数类型
系统配置:
- dontusemixedcaseclassnames 混淆时不使用大小写混合类名
- dontskipnonpubliclibraryclasses 不跳过library中的非public的类
- verbose 打印混淆的详细信息
- dontoptimize 不进行优化,建议使用此选项,
- dontpreverify 不进行预校验,Android不需要,可加快混淆速度。
- ignorewarnings 忽略警告
- optimizationpasses 5 指定代码的压缩级别
保留选项:
- keep{Modifier}{class_specification}: 保护指定的类文件(类名)和类成员, 防止被混淆或移除
- keepclassmembers{Modifier}{class_specification}: 通过成员来指定来只保护指定类的某些成员, 防止被混淆或移除, 注意类名还是会被混淆
- keepclasswithmembers{class_specification}: 通过成员来指定哪些类不被混淆处理。保持匹配到的类的类名和指定的方法不被混淆,其他未指定的方法仍然会被混淆
- keepnames{class_specification}: 等于-keep,allowshrinking class_specification的别名,允许该类被压缩,未被使用的元素将会在压缩阶段被移除,此选项会保持对应的类名和指定的成员不被混淆(未指定的成员依然会被混淆)
- keepclassmembernames{class_specification}: -keepclasseswithmembers,allowshrinking class_specification的别名,但是未使用的类和成员可能会在压缩阶段被移除
- keepclasseswithmembernames {class_specification}: -keepclasseswithmembers,allowshrinking class_specification的别名,未使用的类和成员可能会在压缩阶段被移除
压缩:
- dontshrink: 不压缩输入的类文件
- printusage{filename}: 输出无用文件
优化:
- dontoptimize: 不优化输入的类文件
- assumenosideeffects{class_specification}: 在优化阶段移除相关方法的调用
- allowaccessmodification: 优化时允许访问并修改有修饰符的类和类成员变量
混淆:
- dontobfuscate不混淆输入的类文件
- printmapping proguardMapping.txt : 输出映射表
- applymapping{filename}:重用映射增加混淆。
- obfuscationdictionary{filename}: 使用给的文件中的关键作为要混淆方法的名称。
- overloadaggressively:混淆时应用侵入式重载。混淆的时候大量使用重载,多个方法名使用同一个混淆名(慎用)
- useuniqueclassmembernames:确定统一的混淆类的成员名称来增加混淆。
- renamesourcefileattribute{string}:设置源文件中给定的字符串常量。
预检:
- dontpreverify
Android混淆方案实例
# 代码混淆压缩比,在0~7之间
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify
-verbose
# 避免混淆泛型
-keepattributes Signature
#保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
#google推荐算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 避免混淆Annotation、内部类、泛型、匿名类
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 重命名抛出异常时的文件名称
-renamesourcefileattribute SourceFile
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 处理support包
-dontnote android.support.**
-dontwarn android.support.**
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# 保留R下面的资源
-keep class **.R$* {*;}
# 保留四大组件,自定义的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.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
# 保留在Activity中的方法参数是view的方法,
# 这样以来我们在layout中写的onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留枚举类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#assume no side effects:删除android.util.Log输出的日志
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
#保留Keep注解的类名和方法
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
@android.support.annotation.Keep *;
}
#3D 地图 V5.0.0之前:
-dontwarn com.amap.api.**
-dontwarn com.autonavi.**
-keep class com.amap.api.**{*;}
-keep class com.autonavi.**{*;}
-keep class com.amap.api.maps.**{*;}
-keep class com.autonavi.amap.mapcore.*{*;}
-keep class com.amap.api.trace.**{*;}
#3D 地图 V5.0.0之后:
-keep class com.amap.api.maps.**{*;}
-keep class com.autonavi.**{*;}
-keep class com.amap.api.trace.**{*;}
#定位
-keep class com.amap.api.location.**{*;}
-keep class com.amap.api.fence.**{*;}
-keep class com.autonavi.aps.amapapi.model.**{*;}
#搜索
-keep class com.amap.api.services.**{*;}
#2D地图
-keep class com.amap.api.maps2d.**{*;}
-keep class com.amap.api.mapcore2d.**{*;}
#导航
-keep class com.amap.api.navi.**{*;}
-keep class com.autonavi.**{*;}
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform
#fastjson混淆
-keepattributes Signature
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.**{*;}
-keep class com.alibaba.fastjson.**{*; }
-keep public class com.ninstarscf.ld.model.entity.**{*;}