一、配置( 项目的app/build.gradle)
buildTypes {
release {
/*打开混淆*/
minifyEnabled true
/*打开资源压缩*/
shrinkResources true
zipAlignEnabled true // Zipalign优化
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
proguard-android.txt
是官方提供的通用混淆配置,文件路径在 \sdk\tools\proguard\proguard-android.txt
下
proguard-rules.pro
即自定义配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'),
file('proguard-rules.pro'),
project.ext.LiteDir + 'buildOperation/proguard.cfg',
file('../PluginSDK/proguard-rules.pro'),
file('../LibThemeEngine/proguard-rules.pro'),
二、混淆为什么要保留类名或方法名?
- 1、让C/C++程序可以通过jni使用对应的java方法
- 2、四大组件由于在AndroidManifest.xml里面注册了,所以需要保留。
- 3、R文件混淆会导致引用错误。
- 4、第三方架包有的已经经过混淆了,再次混淆会导致找不到类名或者方法名
三、什么时候不被混淆?
一般以下情况都会不混淆:
- 1、使用了
自定义控件
那么要保证它们不参与混淆 - 2、使用了
枚举
要保证枚举不被混淆 - 3、对
第三方库
中的类不进行混淆 - 4、运用了
反射的类
也不进行混淆 - 5、使用了 Gson 之类的工具要使
JavaBean
类即实体类不被混淆 - 6、
静态成员
、JNI中调用的类
- 7、有用到
WebView
的JS 调用
也需要保证写的接口方法
不混淆,原因和第一条一样 - 8、
Serializable
,Parcelable
的子类和Creator
静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常 - 9、
四大组件
,自定义的Application
四、混淆语法
- 2.1 基本规则
两个常用的混淆命令,注意一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;而两颗星表示把本包和所包含的子包下的类名都保留。
-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*
如果既想保持类名,又想保持里面的内容不被混淆,就执行以下方法
-keep class com.example.bean.** { *; }
在此基础上,我们也可以使用Java的基本规则来保护特定类不被混淆,比如我们可以用extend,implement等这些Java规则。如下例子就避免所有继承Activity的类被混淆
保留我们使用的四大组件,自定义的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
-keep public class com.android.vending.licensing.ILicensingService
- 2.2、基本混淆模板
对于一些基本指令的添加
#############################################
#
# 对于一些基本指令的添加
#
#############################################
#保证使用同一套 mapping 文件,从而保证前后打包一直。
#mapping文件时混淆前后文件的映射关系
-applymapping mapping.txt
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
# 这句话能够使我们的项目混淆后产生映射文件# 包含有类名->混淆后类名的映射关系
-verbose
# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 避免混淆Annotation、内部类、泛型、匿名类
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 指定混淆是采用的算法,后面的参数是一个过滤器# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
#把混淆类中的方法名也混淆了
-useuniqueclassmembernames
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification
Android开发中一些需要保留的公共部分
#############################################
#
# 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
-keep public class com.android.vending.licensing.ILicensingService
# 保留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.**
# 保留R下面的资源
-keep class **.R$* {*;}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留在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{
*** get*();
void set*(***);
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序列化的类不被混淆
-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);
}
# webView处理,项目中没有使用到webView忽略即可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.webView, jav.lang.String);
}
# 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 *;
}
#fastjson混淆
-keepattributes Signature
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.**{*;}
-keep class com.alibaba.fastjson.**{*; }
-keep public class com.ninstarscf.ld.model.entity.**{*;}
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.*
# webview
-keep class android.webkit.JavascriptInterface {*;}
#自定义webview接口实现类
-keep public class com.gjmetal.app.ui.ball.**{*;}
# 微信支付
-dontwarn com.tencent.mm.**
-dontwarn com.tencent.wxop.stat.**
-keep class com.tencent.mm.** {*;}
-keep class com.tencent.wxop.stat.**{*;}
# 支付宝钱包
-dontwarn com.alipay.**
-dontwarn HttpUtils.HttpFetcher
-dontwarn com.ta.utdid2.**
-dontwarn com.ut.device.**
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}
-keep class com.alipay.mobilesecuritysdk.*
-keep class com.ut.*
五、其他混淆
然后需要在项目生成的混淆脚本中添加过滤混淆的条件
# Glide
-dontwarn com.bumptech.glide.**
-keep class com.bumptech.glide.**{*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# Gson
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
# OkHttp3
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# Okio
-dontwarn com.squareup.**
-dontwarn okio.**
-keep public class org.codehaus.* { *; }
-keep public class java.nio.* { *; }
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
# RxJava RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
#数据模型(实体类)
-keep class com.seekfangle.pad.bean.* {*;}
#第三方架包
-keep class cn.com.** { *;}
-keep class android_serialport_api.** { *;}
#butterknife
-dontwarn butterknife.internal.**
-keep class **$$ViewInjector { *; }
-keepnames class * { @butterknife.InjectView *;}
#EventBus3.0
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
#友盟
-keep class com.umeng.** {*;}
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep public class com.gjmetal.app.R$*{
public static final int *;
}
#个推
-dontwarn com.igexin.**
-keep class com.igexin.** { *; }
-keep class org.json.** { *; }
-keep class android.support.v4.app.NotificationCompat { *; }
-keep class android.support.v4.app.NotificationCompat$Builder { *; }
#routes
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
#ShareSdk
-keep class cn.sharesdk.**{*;}
-keep class com.sina.**{*;}
-keep class **.R$* {*;}
-keep class **.R{*;}
-keep class com.mob.**{*;}
-keep class m.framework.**{*;}
-dontwarn cn.sharesdk.**
-dontwarn com.sina.**
-dontwarn com.mob.**
-dontwarn **.R$*
#Picasso
-keep class com.parse.*{ *; }
-dontwarn com.parse.**
-dontwarn com.squareup.picasso.**
-keepclasseswithmembernames class * {
native <methods>;
}
#GreenDao
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
-dontwarn rx.**
#百度定位混淆配置
-keep class vi.com.gdi.** { *; }
-keep public class com.baidu.** {*;}
-keep public class com.mobclick.** {*;}
-dontwarn com.baidu.mapapi.utils.*
-dontwarn com.baidu.platform.comapi.b.*
-dontwarn com.baidu.platform.comapi.map.*
#百度地图混淆配置
-keep class com.baidu.** {*;}
-keep class vi.com.** {*;}
-dontwarn com.baidu.**
-keep class mapsdkvi.com.** {*;}
#QQ分享
-dontwarn com.tencent.**
-keep class com.tencent.** {*; }
#微信分享
-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** { *;}
六、检查混淆结果
混淆过的包必须进行检查,避免因混淆引入的bug。
一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在<module-name>/build/outputs/mapping/release/
目录下会输出以下文件:
dump.txt
描述APK文件中所有类的内部结构
mapping.txt
提供混淆前后类、方法、类成员等的对照表
seeds.txt
列出没有被混淆的类和成员
usage.txt
列出被移除的代码
我们可以根据seeds.txt
文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据usage.txt
文件查看是否有被误移除的代码。
另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。
七、解出混淆栈
混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。
在<sdk-root>/tools/proguard/
路径下有附带的的反解工具(Window 系统为proguardgui.bat,Mac 或 Linux 系统为proguardgui.sh
)。
这里以 Window 平台为例。双击运行proguardgui.ba
t 后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在<module-name>/build/outputs/mapping/release/
路径下会生成mapping.txt
文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。
以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
例如:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
注意事项:
所有在 AndroidManifest.xml
涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)
proguard-android.txt
已经存在一些默认混淆规则,没必要在proguard-rules.pro
重复添加
mapping.txt
中的混淆前后的类对照如下:
//混淆前的文件 -> 混淆后的文件
org.aspectj.runtime.reflect.SourceLocationImpl -> c.a.b.b.g:
java.lang.String fileName -> a
int line -> b
24:28:void <init>(java.lang.Class,java.lang.String,int) -> <init>
31:31:java.lang.String getFileName() -> a
32:32:int getLine() -> b
36:36:java.lang.String toString() -> toString
org.aspectj.runtime.reflect.StringMaker -> c.a.b.b.h:
org.aspectj.runtime.reflect.StringMaker middleStringMaker -> g
org.aspectj.runtime.reflect.StringMaker longStringMaker -> h
org.aspectj.runtime.reflect.StringMaker shortStringMaker -> f
int cacheOffset -> e
boolean shortTypeNames -> a
boolean includeArgs -> b
boolean includeModifiers -> c
boolean shortPrimaryTypeNames -> d
33:69:void <clinit>() -> <clinit>
19:28:void <init>() -> <init>
八、自定义混淆规则
在上文“混淆配置”中有这样一行代码
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
这行代码定义了混淆规则由两部分构成:位于 SDK 的tools/proguard/
文件夹中的 proguard-android.txt
的内容以及默认放置于模块根目录的proguard-rules.pro
的内容。前者是 SDK 提供的默认混淆文件,后者是开发者自定义混淆规则的地方。
九、常见的混淆指令
- 1、
optimizationpasses
代码混淆压缩比,在0~7之间,默认为5,一般不做修改 - 2、
dontoptimize
不进行优化 - 3、
dontusemixedcaseclassnames
混合时不使用大小写混合,混合后的类名为小写 - 4、
dontskipnonpubliclibraryclasses
指定不去忽略非公共库的类 - 5、
dontpreverify
不做预校验 - 6、
dontwarn
不提示警告,和keep可以说是形影不离,尤其是处理引入的library时. - 7、
verbose
混淆时是否记录日志 - 8、
optimizations
指定混淆是采用的算法,后面的参数是一个过滤器 - 9、
keep
保留类和类中的成员,防止被混淆或移除 - 10、
keepnames
保留类和类中的成员,防止被混淆,成员没有被引用会被移除 - 11、
keepclassmembers
只保留类中的成员,防止被混淆或移除 - 12、
keepclassmembernames
只保留类中的成员,防止被混淆,成员没有引用会被移除 - 13、
keepclasseswithmembers
保留类和类中的成员,防止被混淆或移除,保留指明的成员 - 14、
keepclasseswithmembernames
保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除 - 15、
dontshrink
不压缩类文件。默认情况下会压缩所有的类文件,除了那些用keep声明和被这些类依赖的class
更多详细的请到官网
十、规则
[保持命令] [类] {
[成员]
}
“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:
- 具体的类
- 访问修饰符
public、protected、private
- 通配符
*
,匹配任意长度字符,但不含包名分隔符(.)- 通配符
**
,匹配任意长度字符,并且包含包名分隔符(.)extends
,即可以指定类的基类
*implement
,匹配实现了某接口的类$
,内部类
成员
代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:
<init>
匹配所有构造器<fields>
匹配类中的所有字段<methods>
匹配所有方法- 通配符
*
,匹配任意长度字符,但不含包名分隔符(.)- 通配符
**
,匹配任意长度字符,并且包含包名分隔符(.)- 通配符
***
,匹配任意参数类型…
,匹配任意长度的任意类型参数。比如void test(…)
就能匹配任意void test(String a)
或者是void test(int a, String b)
这些方法。- 访问修饰符
public、protected、private
指定成员
\ 代表任意构造方法.
\ 代表任意域.
\ 代表任意方法.
* 代表任意成员(包括成员变量和方法).
类型描述通配符
% 表示任意基本类型(int,char等,但是不包括void).
? 表示类名中的任意单个字符.
* 表示类名中的任意多个字符,不包括分隔符(.).
** 表示类名中的任意多个字符,包括分隔符(.).
*** 表示任意类型.
... 表示任意多个任意类型的参数.
举个例子,假如需要将com.biaobiao.test包下所有继承Activity的public类及其构造函数都保持住,可以这样写:
-keep public class com.biaobiao.test.** extends Android.app.Activity {
<init>
}
十一、常用自定义混淆规则
1、不混淆某个类
-keep public class com.biaobiao.example.Test { *; }
2、不混淆某个包所有的类
-keep class com.biaobiao.test.** { *; }
3、不混淆某个类的子类
-keep public class * extends com.biaobiao.example.Test { *; }
4、不混淆某个接口的实现
-keep class * implements com.biaobiao.example.TestInterface { *; }
5、不混淆某个类的构造方法
-keepclassmembers class com.biaobiao.example.Test {
public <init>();
}
//保持指定类的所有字段
-keep class com.xy.myapp.MyClass { public <fields>; }
6、不混淆某个类的特定的方法
-keepclassmembers class com.biaobiao.example.Test {
public void test(java.lang.String);
}
//保持指定类的所有方法
-keep class com.xy.myapp.MyClass { public <methods>; }
//保持用指定参数作为形参的方法
-keep class com.xy.myapp.MyClass { public <methods>(java.lang.String); }
7、不混淆某个类的内部类
//所有内部类
-keep class com.biaobiao.example.Test$* { *; }
//内部类 Builder
-keep class com.biaobiao.example.Test$Builder { *; }
//保持MyClass内部类JavaScriptInterface中的所有public内容。
-keepclassmembers class com.xy.myapp.MyClass$JavaScriptInterface {
#保持该类下所有的共有内容不被混淆
public *;
#保持该类下所有的私有方法不被混淆
private * ;
}
8、混淆引入的 library
//使指定的类不输出警告信息
-dontwarn purang.purang_shop.**
-keep class purang.purang_shop.**{*;}
十二、组件模块定义混淆规则
子模块混淆文件的指定是通过consumerProguardFiles
这个属性来指定的,并不是proguardFiles
属性,而且我们无需配置其他的选项,只需要配置consumerProguardFiles
属性就可以。该属性表示在打包的时候会自动寻找该module下我们指定的混淆文件对代码进行混淆。可以把一些公共的混淆定义在子模块中。
release {
consumerProguardFiles 'proguard-rules.pro'
}
consumerProguardFiles作用:
- proguard.txt会被打包进aar中
- 此配置只对aar进行混淆。
- 此配置只对库文件有效,对应用程序无效。
当app模块将全部代码汇总混淆时,Library
模块会被打包为release aar
,然后被引用汇总,通过proguard-rule.pro
规则各自混淆,保证只混淆一次。
组件化工程中具体混淆用法:
我们可以将固定的第三方混淆放到base模块的proguard-rule.pro文件中,每个模块独有的第三方引用库混淆放到各自的proguard-rule.pro文件中,在app模块的proguard-rule.pro文件中放入Android基础属性的混淆声明,如四大组件和全局的混淆等配置。这样可以最大限度的完成混淆解耦操作。
十三、自定义要保持的资源
资源压缩包含了“合并资源”和“移除资源”两个流程。“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources
属性控制,也无法被禁止, gradle 必然会做这项工作。
当我们开启了资源压缩之后,系统会默认替我们移除所有未使用的资源,假如我们需要保留某些特定的资源,可以在我们项目中创建一个被 <resources> 标记的 XML 文件(如 res/raw/keep.xml)
,并在tools:keep
属性中指定每个要保留的资源,在tools:discard
属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。同样,我们可以使用字符 * 作为通配符。
<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/activity_video*,@layout/dialog_update_v2"
tools:discard="@layout/unused_layout,@drawable/unused_selector" />
十四、移除替代资源
一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。我们使用 resConfig 属性来指定需要支持的属性,例如
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}