proguard混淆
Android源码很容易被工具反编译出来,因此,对源码做混淆是一种非常常用的保护源码的方式,不仅如此,proguard还会对源码做一些优化,可以对代码进行去冗余压缩,代码优化,代码混淆等。在Android中的主要应用就是对代码混淆:就是将类名,方法名,Field名变成如a,b,c或者1,2,3等难以阅读和理解的名字,以防止逆向工程和被反编译阅读源码。
开启proguard混淆配置
下面说一下怎样在Eclipse
和Android Studio
中为项目开启proguard混淆。
Eclipse:
在项目根路径下有两个文件:project.properties
和 proguard-project.txt
,在创建项目时默认生成的,如果没有手动创建即可。
在project.properties
里有下面一段被注释的话,去掉#号取消注释即开启了proguard混淆:
...
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
...
Android Studio:
在项目根路径下有两个文件:build.gradle
和 proguard-rules.pro
,其中proguard-rules.pro
是创建项目是默认生成的。
在build.gradle
中有下面的配置,即表示开启proguard混淆:
android{
......
buildTypes {
release {
shrinkResources true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
proguard-android.txt:这是Android SDK tools下面提供的一个默认的配置文件。
proguard-project.txt / proguard-rules.pro:这是我们自定义的配置文件,在proguard-android.txt的基础上,添加一些项目相关的自定义配置。
proguard-android.txt的默认配置:
这个文件存放于Android SDK/tools/proguard目录下,打开可以看到如下内容:
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.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.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-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 android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# The support library 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.
-dontwarn android.support.**
这个就是默认的混淆配置文件了,挨着来浏览一下。
-dontusemixedcaseclassnames
表示混淆时不使用大小写混合类名。
-dontskipnonpubliclibraryclasses
表示不跳过library中的非public的类。
-verbose
表示打印混淆的详细信息。
-dontoptimize
表示不进行优化,建议使用此选项,因为根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
-dontpreverify
表示不进行预校验。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
-keepattributes *Annotation*
表示对注解中的参数进行保留。
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
表示不混淆上述声明的两个类,这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
-keepclasseswithmembernames class * {
native <methods>;
}
表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致的。
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
表示不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了。
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了。
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
表示不混淆Parcelable实现类中的CREATOR字段,毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keepclassmembers class **.R$* {
public static <fields>;
}
表示不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-dontwarn android.support.**
表示对android.support包下的代码不警告,因为support包中有很多代码都是在高版本中使用的,如果我们的项目指定的版本比较低在打包时就会给予警告。不过support包中所有的代码都在版本兼容性上做足了判断,因此不用担心代码会出问题,所以直接忽略警告就可以了。
proguard中的keep关键字
proguard中有下面几个keep关键字:
关键字 | 描述 |
---|---|
keep | 保留类和类中的成员,防止它们被混淆或移除 |
keepnames | 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除 |
keepclassmembers | 只保留类中的成员,防止它们被混淆或移除 |
keepclassmembernames | 只保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除 |
keepclasseswithmembers | 保留类和类中的成员,防止它们被混淆或移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆 |
keepclasseswithmembernames | 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆 |
proguard中的通配符
proguard还包括下面一些通配符:
通配符 | 描述 |
---|---|
field | 匹配类中的所有字段 |
method | 匹配类中的所有方法 |
init | 匹配类中的所有构造函数 |
* | 匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.example.test.MyActivity,使用com.,或者com.exmaple.都是无法匹配的,因为无法匹配包名中的分隔符,正确的匹配方式是com.exmaple..,或者com.exmaple.test.,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西 |
** | 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包 |
*** | 匹配任意参数类型。比如void set*(***)就能匹配任意传入的参数类型,*** get*()就能匹配任意返回值的类型 |
… | 匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法 |
对项目做混淆配置proguard-android.txt
我们可以修改proguard-android.txt
中的规则,但是直接在proguard-android.txt
中修改会对我们本机上所有项目的混淆规则都生效,那么有没有什么办法只针对当前项目的混淆规则做修改呢?当然是有办法的了,你会发现任何一个Android Studio项目在app模块目录下都有一个proguard-rules.pro
文件,这个文件就是用于让我们编写只适用于当前项目的混淆规则的,那么接下来用上面的知识来对项目混淆规则做修改。
对项目中使用到的三方库做处理
-dontwarn xxx.xxx.**
某些三方库中对某些API做了引用,如果不存在,则在打包的时候会报错,使用-dontwarn
,可以在打包的时候忽略此错误.
-keep class xxx.xxx.** { *; }
这样是不对指定包下面的所有子包和类做任何混淆,因为引用的都是三方开源库,既然是开源的,所以也没用必要做混淆.
下面列出了项目中用到的三方库混淆:
google的support库:-keep class android.support.** { *; }
EventBus:
-keep class de.** { *; }
Volley:
-keep class com.android.volley.** { *; }
百度推送和百度地图:
-keep class com.baidu.** { *; }
Gson:
-keep class com.google.gson.** { *; }
universal-image-loader
-keep class com.nostra13.universalimageloader.** { *; }
友盟统计
-keep class com.umeng.analytics.** { *; }
OkHttp
-keep class okhttp3.** { *; }
okio
-keep class okio.** { *; }
LitePal
-keep class org.litepal.** { *; }
对Gson解析的实体类不能做混淆
因为Gson在把Json串解析成实体类的时候,是通过反射来获取类的字段名,所以不能对类做混淆.因为项目里面需要由Gson解析的实体类都存放在reponse包下面,所以添加下面的配置:
-keep class **.response.** { *; }
另外,所有请求返回需要解析的对应的实体类跟请求URL关联,存放在ResponseTypeProvider 里面的,所以需要添加下面的配置:
-keep class **.ResponseTypeProvider { *; }
最后,Gson还需要添加下面的配置:
-keepattributes Signature
LitePal
项目中应用了ORM数据库LitePal,会把继承了DataSupport
的类通过反射获取想要属性生成对应表和字段,所以对继承了DataSupport
的类不能做混淆:
-keep class * extends org.litepal.crud.DataSupport { *; }
EventBus
项目中用到了EventBus作为事件总线处理库,它会通过反射去看注册了的类,需要用OnEvent***()的方法,如果找不到则会直接Crash,所以需要对所有onEvent***()不做混淆,其中方法参数也是不定的,所以()里面参数用**代替:-keepclassmembers class ** { public void onEvent*(**); }
百度地图
由于对百度地图下面的所有类都做了混淆,在运行百度地图的时候,应用会Crash掉,提示vi.com.gdi.bgl.android.java.EnvDrawText
找不到,所以需要添加下面的配置:-keep class vi.com.gdi.bgl.android.java.EnvDrawText { *; }