项目到了快发布的时候,我们必然会对项目打包进行混淆,这样做可以提高别人对我们项目反编译的难度,其实Android的混淆在Android studio中已经帮我们做了一些基本的配置,看了网上了一些讲关于混淆的,都说在android sdk 目录下 \sdk\tools\proguard\proguard-android.txt
下提供了默认的混淆配置,我们打开这个文件查看,发现开头有这样一段注解:
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the# default rules at build time and stores them in the build directory.
大致的意思就是说这个文件不再维护了,并且在Android Gradle插件2.2+以上不再使用了。相反,Android Gradle插件在构建时生成默认规则,并将它们存储在build目录中。所以说现在混淆的默认规则是由Gradle自动生成的。我们先看一点gradle中的配置:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
println getDefaultProguardFile('proguard-android.txt')
}
}
要生成默认规则,上面的minifyEnable一定要改为true,如果为false是不会生成的。我们看到上面有一个输出语句println,这里打印的就是生成的proguard-android.txt文件的位置:
getDefaultProguardFile('proguard-android.txt').exists()
可以通过上面这个输出语句判断文件是否存在,minifyEnable为true时存在的,为false时是不存在的,如果已经存在,为false也是存在的,来看下输出:
E:\AndroidWorkProject\proguardtest\build\intermediates\proguard-files\proguard-android.txt-3.1.0
这里在说下查看输出的位置,对于3.0之前的版本,右下角有个Gradle Console可以查看,我用的是Android studio 3.1,查看位置如下图:
在看下生成后的工程目录:
在build目录下生成了三个混淆文件,可以看到这里多出了一串数字,这个是Gradle插件的版本号,后面会讲到。proguard-android.txt-3.1.0使用这个文件混淆时,是不会对混淆进行优化的,而另外两个是在混淆时会进行优化。
接下来我们来看下下面这个方法:
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
对于studio是3.0之前的版本,想要看源码需要配置,不会的自己百度。对于这样的代码如果你有点看不太懂,那你就需要补点Groovy的语法了,这里我们先看下下面这个方法:
getDefaultProguardFile('proguard-android.txt')
这里我们直接按住ctr+左键 进入到这个方法内部去瞧一瞧,
public File getDefaultProguardFile(String name) {
if (!ProguardFiles.KNOWN_FILE_NAMES.contains(name)) {
extraModelInfo
.getSyncIssueHandler()
.reportError(
EvalIssueReporter.Type.GENERIC, ProguardFiles.UNKNOWN_FILENAME_MESSAGE);
}
return ProguardFiles.getDefaultProguardFile(name, project);
}
这里看到有一个判断,判断在这个集合中是否包含传进来的name,这个集合是通过lambda将一个枚举转换过来的的,我们来看下这个枚举:
public enum ProguardFile {
/** Default when not using the "postProcessing" DSL block. */
DONT_OPTIMIZE("proguard-android.txt"),
/** Variant of the above which does not disable optimizations. */
OPTIMIZE("proguard-android-optimize.txt"),
/**
* Does not disable any actions, includes optimizations config. To be used with the new
* "postProcessing" DSL block.
*/
NO_ACTIONS("proguard-defaults.txt"),
;
@NonNull public final String fileName;
ProguardFile(@NonNull String fileName) {
this.fileName = fileName;
}
}
就是上面生成的那三个文件,我们接着往下看getDefaultProguardFile()这个方法:
public static File getDefaultProguardFile(@NonNull String name, @NonNull Project project) {
if (!KNOWN_FILE_NAMES.contains(name)) {
throw new IllegalArgumentException(UNKNOWN_FILENAME_MESSAGE);
}
return FileUtils.join(
project.getRootProject().getBuildDir(),
AndroidProject.FD_INTERMEDIATES,
"proguard-files",
name + "-" + Version.ANDROID_GRADLE_PLUGIN_VERSION);
}
这个KNOW_FILE_NAMES还是上面那个集合,这里就是判断传进来的那个name是否是三个文件名中的一个,如果不是,那么会抛异常,我们来看下这个异常:
Supplied proguard configuration file name is unsupported. Valid values are: [proguard-android-optimize.txt, proguard-defaults.txt, proguard-android.txt]
这就是说传进来的文件名必须是这三个文件名中的一个,上面我们看到生成的文件有一个版本号的后缀,在这里我们就可以得到解释了,我们再往下看这个join方法:
public static File join(@NonNull File dir, @NonNull String... paths) {
if (paths.length == 0) {
return dir;
}
return new File(dir, PATH_JOINER.join(paths));
}
这下就该明白了,这里就是在创建一个文件。看来默认的混淆文件就是这样创建而来的。
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
到这,getDefaultProguardFile()就分析完了,接下来就是是看proguardFiles,这也是个方法,后面的就是方法中的参数,proguard-rules.pro就是我们自己定义的混淆规则,来看看代码:
public BuildType proguardFiles(@NonNull Object... files) {
checkPostprocessingConfiguration(PostprocessingConfiguration.OLD_DSL, "proguardFiles");
for (Object file : files) {
proguardFile(file);
}
return this;
}
遍历所有传进来的文件然后执行proguardFile():
public BuildType proguardFile(@NonNull Object proguardFile) {
checkPostprocessingConfiguration(PostprocessingConfiguration.OLD_DSL, "proguardFile");
getProguardFiles().add(project.file(proguardFile));
return this;
}
这里getProguardFiles()返回的是一个List集合,也就是说这里把所有的混淆文件添加进了一个集合中,也就是说这两个混淆文件是共同作用的。
我们这里来看下Android中的默认混淆规则,也就是proguard-android.txt这个文件中的内容:
-dontoptimize
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Preserve some attributes that may be required for reflection.
-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keep public class com.google.android.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService
-dontnote com.google.vending.licensing.ILicensingService
-dontnote com.google.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#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);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# Preserve annotated Javascript interface methods.
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
# The support libraries contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontnote android.support.**
-dontwarn android.support.**
# This class is deprecated, but remains for backward compatibility.
-dontwarn android.util.FloatMath
# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
这里看到有好些规则Android中已经为我们配置了,那么我们在proguard-rules.pro中就没必要在去重新配置了,看到网上的好多配置都在proguard-rules.pro中去重新配置了。
这里我说一个配置的属性,在proguard-rules.pro中我们配置如下属性:
-assumenosideeffects class android.util.Log {
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
并且把proguard-android.txt中-dontoptimize去掉,那么我们在代码中的log日志将不会打印,当然也可以通过自己写个日志工具类来控制是否输出。
再来一个配合proguard-android.txt的proguard-rules.pro的通用配置:
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5
# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################
# 保留我们使用的四大组件,自定义的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
# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# 保留我们自定义控件(继承自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);
}
# 保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable序列化的类不被混淆
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用
# 记得proguard-android.txt中一定不要加-dontoptimize才起作用
# 另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制
#-assumenosideeffects class android.util.Log {
# public static int v(...);
# public static int i(...);
# public static int w(...);
# public static int d(...);
# public static int e(...);
#}
当我们这样配置编译报错时,如果是没有忽略warning,我么可以加上:
-ignorewarnings
好了,到这就差不多了。