简介
Proguard工具通过移除无用的代码以及使用语义隐晦的名称来重命名类、字段和方法,从而达到压缩、优化和混淆代码的目的。最终您将获得一个较小的 .apk 文件,此文件更难于进行反向工程。由于 ProGuard 会使应用更难于进行反向工程,因此当应用使用对安全性要求极高的功能时(例如,当您向应用授予许可时),您必须使用此工具。
ProGuard 已集成到 Android 构建系统,所以您无需手动调用此工具。只有当您在发布模式下构建应用时,ProGuard 才会运行,因此当您在调试模式下构建应用时,就无需处理混淆后的代码。是否运行 ProGuard 完全由您决定,但我们强烈建议您运行该工具。
本文介绍如何启用和配置 ProGuard,以及如何使用 retrace 工具解码混淆后的堆栈跟踪信息。
除了基本的Proguard混淆外,还有一些其他的混淆方式和工具。例如:混淆资源
开启Proguard
-
Ant、Eclipse构建
<project_root>/project.properties 文件中设置 proguard.config 属性。该路径可以是绝对路径,也可以是项目根目录的相对路径。 proguard.config=proguard.cfg
-
AndroidStudio-Gradle构建
android { buildTypes { release { minifyEnabled true proguardFile getDefaultProguardFile('proguard-android.txt') } } productFlavors { flavor1 { } flavor2 { proguardFile 'some-other-rules.txt' } } }
提示:
getDefaultProguardFile()可以返回这两个文件的绝对路径。
proguardFile 可以配置多个混淆文件
配置Proguard
在某些情况下,proguard.cfg或proguard-android.txt 文件中的默认配置足以满足您的需求。不过,在很多情况下,ProGuard 很难做出正确分析,因此可能会移除它认为无用而实际上您的应用却需要的代码。部分示例如下:
- 一个只在 AndroidManifest.xml 文件中引用的类
- 一个通过 JNI 调用的方法
- 动态引用的字段和方法
默认的 proguard.cfg或proguard-android.txt 文件旨在涵盖一般的使用情形,但您可能会遇到异常情况,例如 ClassNotFoundException(此异常情况会在 ProGuard 删除您的应用调用的整个类时发生)。
您可以通过在 proguard.cfg或proguard-android.txt 文件中添加一个 -keep 行,来修复因 ProGuard 在删除代码而造成的错误。例如:
-keep public class <MyClass>
在使用 -keep 选项时,您既有许多选择也有不少需要注意的方面,因此我们强烈建议您阅读 ProGuard 手册,详细了解如何自定义您的配置文件。该手册中的“Keep 选项概述”和“示例”部分尤其有用;问题排查部分则概述了在 ProGuard 删除代码后您可能会遇到的其他常见问题。
下面给出一个常用的默认配置命令:
-include {fileame} 从给定的文件中读取配置参数
-libraryjars libs/xxxx.jar 指定库jar包
-keep public class * extends android.app.Activity 保留类不被删除
-keep class className$InnerName{ 保留内部类的属性和方法
public <fields>;
public <methods>;
}
-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();
}
-dontshrink 不压缩输入的类文件
-dontoptimize 不优化输入的类文件
-keepattributes *Annotation* 保留Annotation
-dontwarn xxx.xxx.** 不检查引用
Proguard产生的文件
当混淆后的代码输出堆栈跟踪信息时,方法名称会被混淆,即便仍能进行调试,难度也会很大。幸运的是,ProGuard 在每次运行时都会输出以下文件:
-
dump.txt
描述 .apk 文件中所有类文件的内部结构
-
mapping.txt
列出原始与混淆后的类、方法和字段名称之间的对应关系。
Windows 上的 retrace.bat 脚本以及 Linux 或 Mac OS X 上的 retrace.sh 脚本可以将混淆后的堆栈跟踪信息转换成可读文件,此文件位于 /tools/proguard/ 目录中。执行 retrace 工具的语法如下:
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
例如:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
建议发布时应保留mapping.txt文件。
-
seeds.txt
列出未混淆的类和成员
-
usage.txt
列出从 .apk 删除的代码
@Keep注解来防止混淆
写到这,你是不是发现了一个问题:非常的麻烦、一点都不灵活,而且通过-keep的方式防止混淆那种有共同特征的类、属性或方式非常有用,但是没有共同特征的呢?
这里介绍一种比较新颖、轻快的方法,通过@Keep注解来灵活的防止混淆,用起来非常的灵活、快捷、方便,怎样用呢?像普通的注解一样,如下:
//防止混淆类
@Keep
public class Person {}
//防止混淆变量
@Keep
public String name;
//防止混淆方法
@Keep
public int getAge(){}
但是当你加上上面的注解后,发现@Keep并没有起作用,该混淆的还是混淆了,这是为什么呢?
原因目前Gradle还不支持@Keep混淆,Google只是定义好了一个这种注解,并没有实现它,也就是说@Keep目前只是一个空壳。这里我们来手动开启它,让它支持防止混淆,在你的proguard.cfg或proguard-android.txt配置文件里面加入以下代码:
#手动启用support keep注解
-dontskipnonpubliclibraryclassmembers
-printconfiguration
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
@android.support.annotation.Keep *;
}
Proguard相关语法
后面的文件名,类名,或者包名等可以使用占位符代替
“?”表示一个字符 可以匹配多个字符,但是如果是一个类,不会匹配其前面的包名
“*”可以匹配多个字符,会匹配前面的包名。
- 输入输出选项
- -include filename
从给定的文件中读取配置参数 - -injars class_path
输入(即使用的) jar文件路径 - -outjars class_path
输出 jar 路径 - -libraryjars class_path
指定的jar将不被混淆 - -skipnonpubliclibraryclasses
跳过(不混淆) jars中的 非public classes - -dontskipnonpubliclibraryclasses
不跳过(混淆) jars中的 非public classes 默认选项 - -dontskipnonpubliclibraryclassmembers
不跳过 jars中的非public classes的members - -keepdirectories [directory_filter]
指定目录 keep 在 out jars中- 保持不变的选项(混淆不进行处理的内容)
- -keep {Modifier} {class_specification}
保护指定的类文件和类的成员 - -keepclassmembers {modifier} {class_specification}
保护指定类的成员,如果此类受到保护他们会保护的更好 - -keepclasseswithmembers {class_specification}
保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。 - -keepnames {class_specification}
保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除) - -keepclassmembernames {class_specification}
保护指定的类的成员的名称(如果他们不会压缩步骤中删除) - -keepclasseswithmembernames {class_specification}
保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后) - -printseeds {filename}
列出类和类的成员-keep选项的清单,标准输出到给定的文件- 压缩选项
- -dontshrink
不启用 shrink。shrink操作默认启用,主要的作用是将一些无效代码给移除,即没有被显示调用的代码。 - -printusage [filename]
打印被移除的代码,在标准输出 - -whyareyoukeeping class_specification
打印 在shrink过程中 为什么有些代码被 keep- 优化选项
- -dontoptimize
该选项表示 不启用。optimization,默认启用
当不使用该选项时,下面的才有效 - -optimizations optimization_filter
根据optimization_filter指定要优化的文件 - -optimizationpasses n
优化数量 n - -assumenosideeffects class_specification
优化时允许访问并修改类和类的成员的 访问修饰符,可能作用域会变大。 - -mergeinterfacesaggressively
合并接口,即使它们的实现类未实现合并后接口的所有方法。- 混淆选项
- -dontobfuscate
不混淆 - -printmapping [filename]
打印 映射旧名到新名 - -applymapping filename
打印相关 - -obfuscationdictionary filename
指定外部模糊字典 - -classobfuscationdictionary filename
指定class模糊字典 - -packageobfuscationdictionary filename
指定package模糊字典 - -overloadaggressively
过度加载,多个属性和方法使用相同的名字,只是参数和返回类型不同 可能各种异常 - -useuniqueclassmembernames
类和类成员都使用唯一的名字 - -dontusemixedcaseclassnames
不使用大小写混合类名 - -keeppackagenames [package_filter]
保持packagename 不混淆 - -flattenpackagehierarchy [package_name]
指定重新打包,所有包重命名,这个选项会进一步模糊包名 好东西
将包里的类混淆成n个再重新打包到一个个的package中,注:混淆是有用,但是我用的时候安装会崩溃,不知道为什么? - -repackageclasses [package_name]
将包里的类混淆成n个再重新打包到一个统一的package中 会覆盖flattenpackagehierarchy选项 - -keepattributes [attribute_filter]
混淆时可能被移除下面这些东西,如果想保留,需要用该选项。“Annotation、Exceptions, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable”- 预校验选项
- -dontpreverify
不预校验,默认选项- 通用选项
- -verbose
打印日志 - -dontnote [class_filter]
不打印某些错误 - -dontwarn [class_filter]
不打印警告信息 - -ignorewarnings
忽略警告,继续执行 - -printconfiguration [filename]
打印配置文件 - -dump [filename]
指定打印类结构
- -include filename
demo示例
##--- For:android默认 ---
-optimizationpasses 5 # 指定代码的压缩级别
-allowaccessmodification #优化时允许访问并修改有修饰符的类和类的成员
-dontusemixedcaseclassnames # 是否使用大小写混合
-dontskipnonpubliclibraryclasses # 是否混淆第三方jar
-dontpreverify # 混淆时是否做预校验
-verbose # 混淆时是否记录日志
-ignorewarnings # 忽略警告,避免打包时某些警告出现
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* # 混淆时所采用的算法
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
native <methods>;
}
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * { # 保持枚举 enum 类不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class **.R$* { #不混淆R文件
public static <fields>;
}
-dontwarn android.support.**
##--- End android默认 ---
##--- For:不能被混淆的 ---
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-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
##--- For:保持自定义控件类不被混淆 ---
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
##--- For:android-support-v4 ---
-dontwarn android.support.v4.**
-keep class android.support.v4.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class * extends android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v4.widget
-keep class * extends android.support.v4.app.** {*;}
-keep class * extends android.support.v4.view.** {*;}
##--- For:Serializable ---
-keep class * implements java.io.Serializable {*;}
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {*;}
##--- For:Gson ---
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.idea.fifaalarmclock.entity.***
-keep class com.google.gson.stream.** { *; }
##--- For:Remove log ---
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
##--- For:attributes(未启用) ---
#-keepattributes SourceFile,LineNumberTable # 保持反编译工具能看到代码的行数,以及release包安装后出现异常信息可以知道在哪行代码出现异常,建议不启用
-keepattributes *Annotation* #使用注解
-keepattributes Signature #过滤泛型 出现类型转换错误时,启用这个
#-keepattributes *Exceptions*,EnclosingMethod #没试过,未知效果