为了避免 apk 在发布后被用户通过反编译拿到源代码和资源文件,然后修改资源和代码之后就变成一个新的 apk。而经过混淆后的 APK,即使被反编译,也难以阅读,注意混淆不是让 apk 不能阅读,而是加大阅读的难度,为了避免劳动成果被窃取,也避免出现安全漏洞和隐患,所以在 apk 发布之前一定要进行混淆。
Java 是一种跨平台、解释型语言,Java 源代码编译成的 class 文件中有大量包含语义的变量名、方法名的信息,很容易被反编译为 Java 源代码。为了防止这种现象,我们可以对 Java 字节码进行混淆。混淆不仅能将代码中的类名、字段、方法名变为无意义的名称,保护代码,也由于移除无用的类、方法,并使用简短名称对类、字段、方法进行重命名缩小了程序的大小。
ProGuard 由 shrink、optimize、obfuscate 和 preverify 四个步骤组成,每个步骤都是可选的,需要哪些步骤都可以在脚本中配置。参见 ProGuard 官方介绍。
压缩(Shrink):默认开启,侦测并移除代码中无用的类、字段、方法和特性,减少应用体积,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未使用的类和成员)。
-dontshrink 关闭混淆
优化(Optimize):默认开启,分析和优化字节码,让应用运行的更快。
-dontoptimize 关闭优化,默认混淆配置文件开始
-optimizationpasses n 表示 proguard 对代码进行迭代优化的次数,Android 一般为 5
混淆(Obfuscate):默认开启,使用 a、b、c、d 这样简短而无意义的名称,对类、字段和方法进行重命名,增大反编译难度。
-dontobfuscate 关闭混淆
上面三个步骤使代码大小更小、更高效,也更难被逆向工程。
预检(Preverify):在 java 平台上对处理后的代码进行预检。
混淆流程图:
Proguard 读入 input jars(or wars,zip or directories), 经过四个步骤生成处理之后的 jars(or wars,ears,zips or directories),Optimization 步骤可选择多次进行。
为了确定哪些代码应该被保留,哪些代码应该被移除或混淆,需要确定一个或多个 Entry Point。Entry Point 经常是带有 main methods,applets,midlets 的 classes,它们在混淆过程中会被保留。
Proguard 的几个步骤如何处理 Entry Points。
(1). 在压缩阶段,Proguard 从上述 Entry Points 开始遍历搜索哪些类和类成员被使用。其他没有被使用的类和类成员会移除。
(2). 在优化阶段,Proguard 进一步设置非 Entry Point 的类和方法为 private、static 和 final 来进行优化,不使用的参数会被移除,某些方法会被标记为内联。
(3). 在混淆阶段,Proguard 重命名非 Entry Points 的类和类成员。
(4). 预检阶段是唯一没有触及 Entry Points 的阶段。
在 build.gradle 文件内相应的构建类型中添加 minifyEnabled true 即可。
除了 minifyEnable 属性外,还有用于定义 ProGuard 规则的 proguardFiles 属性:
- proguardFiles getDefaultProguardFile('proguard-android.txt'),
- 'proguard-rules.pro'
官方文档介绍:
getDefaultProguardFile('proguard-android.txt') 方法可从 Android SDK tools/proguard / 文件夹获取默认的 ProGuard 设置。要想做进一步的代码压缩,请尝试使用位于同一位置的 proguard-android-optimize.txt 文件。它包括相同的 Proguard 规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减少 APK 大小和帮助提高其运行速度。
proguard-rules.pro 文件用于添加自定义 Proguard 规则。默认情况下,该文件位于模块根目录(build.gradle 文件旁),内容为空。
在 gradle 2.2 之后,defaultProguardFile 没有使用 sdk 目录下的 proguard-android.txt,而是使用了 gradle 自带的 proguard-android.txt,不同的 gradle 版本带有不同的默认混淆文件,在项目根目录的 build/intermediates/proguard-files/proguard-android.txt-2.3.1。
混淆配置文件不检查规则是否重复,如果两条规则冲突,则采用白名单的,比如设置了开启优化和不优化两个选项后,不论顺序,最终都会执行不优化的操作。
构建时 Proguard 都会输出下列文件:(build 之后)
(1)dump.txt --- 说明 APK 中所有类文件的内部结构
(2)mapping.txt --- 提供原始与混淆过的类、方法和字段名称之间的转换
(3)seeds --- 列出未进行混淆的类和成员
(4)usage.txt --- 列出从 APK 移除的代码
这些文件保存在 / build/outputs/mapping/release 目录下。
使用混淆后,保存好 mapping 文件,程序 csh 时通过脚本进行解码。
retrace 工具位于 / tools/proguard / 目录下,解码命令为:
- retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
(1)不适用大小混写类名
- -dontusemixedcaseclassnames
默认情况下混淆的类名可以包含大小写字符的混合
(2)不忽略公共类库
- -dontskipnonpubliclibraryclasses
指定不去忽略非 public 的 library classes。从 Proguard 4.5 开始,是默认的设置。
(3)不优化指定的文件与不预检验
- -dontoptimize
- -dontpreverify
默认 optimize 和 preverify 选项是关闭的,因为 Android 的 dex 并不想 Java 虚拟机需要 optimize(优化) 和 previrify(预检) 两个步骤。
(4)指定哪个属性不要混淆,可一次指定多个属性
- -keeppattributes [attribute_filter]
通常 Exceptions,Signature,Deprecated,SourceFile,SourceDir,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Synthetic,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault 属性需要被保留,根据项目具体使用情况保留。
gradle 默认的 keepattributes 属性不全,只保留了 Annotations,Signature,InnerClasses,EnclosingMethod,为了混淆之后定位 csh 代码方便,需要在 proguard_rules.pro 中手动添加抛出异常时保留代码行号,并且重命名超出异常时的文件名称,这样能方便定位问题:
#抛出异常时保留代码行号
- - keeppattributes SourceFile,
- LineNumberTable
#重命名抛出异常时的文件名称
- -renamesourcefileattribute SourceFile
keep 选项制定了哪些类,哪些方法不被混淆,从而保证了程序的正常运行。
keep 用法有 6 种:
(1)-keep(names) 选项 指定类和类成员(变量和方法)不被混淆
-keep [,modifier,...] class_specification
// 指定类名不被改变
- -keep public class com.google.vending.licensing.ILicensingService
// 指定使用了 Keep 注解的类和类成员都不被改变
- - keep@android.support.annotation.Keep class * { * ;
- }
(2)-keepclassmembers(names) 指定类成员不被混淆,类名会被混淆
//keep setters in views 使得 animations 仍然能够工作
- -keepclassmembers public class * extends android.view.View {
- void set*(***);
- *** get*();
- }
(3)-keepclasseswithmembers(names) 指定类和类成员都不被混淆
-keepclasseswithmembers [,modifier,...] class_specification
// 包含 native 方法的类名和 native 方法都不能被混淆,如果 native 方法未被调用,则被移除。由于 native 方法与对应 so 库中的方法名称对应,方法名被混淆会导致调用出现问题,所以 native 方法不能被混淆。
- -keepclasseswithmembernames class * {
- native <methods>;
- }
不带 names 的选项为 From being removed or renames, 既不会被移除或重命名,即使类或类成员未被使用。带有 names 的选项为 From being renamed, 不会被重命名,如果是无用的类或类成员,会被移除,移除是指在压缩(Shrinking)时是否会被删除。
通用 Options:
(1)-verbose 打印混淆详细信息
(2)-dontnote 选项:指定不去输出打印该类产生的错误或遗漏
-dontnote com.android.vending.licensing.ILicensingService
-dontnote android.support.**
(3)-dontwarn 选项:指定不去 warn unresolved references 和其他重要的 problem
-dontwarn android.support.**
如上面(2)(3)所示,android.support 的 libraries 需要保留。
匹配一个字符
* 匹配一个名字,除了目录外分隔符外的任意部分
** 匹配任意名,可能包含任意路径分隔符
! 排除
-keep class com.lily.test.** 本包和所包含子包下的类名都保持
-keep class com.lily.test.* 保持该包下的类名
-keep class com.lily.test.** {*;} 保持包和子包的类名和里面的内容均不被混淆
如果要保留一个类中的内部类不被混淆则需要用 $ 符号。
assumeosideeffects 是 Optimization 过程中的选项,所以为保证指令的有效,需要开启 optimization。这个指令的含义是 Proguard 会在 optimization 过程中删除对这些方法的调用,需要注意:当你知道你在做什么的时候才能使用它。
- #代码混淆压缩比,在0~7之间
- -optimizationpasses 5#
- #混淆时不适用大小写混合,混合后的类名为小写
- -dontusemixedcaseclassnames
- #指定不去忽略非公共库的类
- -dontskipnonpubliclibraryclasses
- #不做预校验,preverify是proguard的四个步骤之一,Android不需要precerify,去掉这一步能够加快混淆速度。
- -dontpreverify
- -verbose
- #google推荐算法
- -optimizations !code/simplification/arithmetic,!code/simplication/cast,!field/*,!class/mergin/*
- #避免混淆Annotation、内部类、泛型、匿名类
- -keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
- #重命名抛出异常时的文件名称
- -renamesourcefileattribute SourceFile
- #抛出异常时保留代码行号
- -keepattributes SourceFile,LineNumberTable
- #处理support包
- -dontnote android.support.**
- -dontwarn android.support.**
- #保留四大组件,自定义的Application等这些类不被混淆
- -keep public class * extends android.app.Activity
- -keep public class * extends android.app.Application
- -keep public class * extends android.app.Service
- -keep public class * entends 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
- #保留本地native方法不被混淆
- -keepclasseswithmembernames class * {
- native <methods>
- }
- #保留枚举类不被混淆
- -keep class * implements android.os.Parcelable {
- public static final android.os.Parcelable$Creator *;
- }
- #第三方jar包不被混淆
- -keep class com.github.test.** {*;}
- #保留自定义的Test类和类成员不被混淆
- -keep class class.lily.Test {*;}
- #保留自定义的xlog文件夹下面的类、类成员和方法不被混淆
- -keep class com.text.xlog.** {
- <fields>;
- <methods>;
- }
- #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 *
- -keepclassmember class * {
- @android.support.annotation.Keep *;
- }
jni 方法不混淆,因为方法需要和 native 方法保持一致。
- -keepclasseswithmembernames class * {
- # 保持native方法不被混淆
- native <methods>;
- }
反射用到的类不混淆(否则混淆可能出现问题)。
- -keepatrributes EnclosingMethod
AndroidMainfest 中的类不混淆,所以四大组件和 Application 的子类和 Framework 层下所有的类默认不会进行混淆。自定义的 View 默认也不会被混下,所以排除自定义 View,或者四大组件被混淆的规则在 ndroid Studio 中无需加入的,下面是兼容性比较高的规则:
- -keep public class * extends android.app.Fragment
- -keep public class * extends android.app.Activity
- -keep public class * extends android.app.Service
- -keep public class * extends android.content。BroadcastReceiver
- -keep public class * extends android.content.ContentProvider
- -keep public class * entends android.app.backup.BackupAgentHelper
- -keep public class * entends android.preference.Preference
与服务器交互时,使用 GSON、fastjson 等框架解析服务端数据时,所写的 JSON 对象类不混淆,否则无法将 JSON 解析成对应的对象。
使用第三方开源库或者引用其他第三方的 SDK 包时,如果有特别要求,也需要在混淆文件中加入对应的混淆规则。
有用到 WebView 的 JS 调用也需要保证写的接口方法不混淆,原则和第一条一样。
- - keepclassmembers classs fqcn.of.javascript.interface.
- for.webview {
- public * ;
- }
Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常;
- -keep class * implements Android.os.Parcelable {
- # 保持Parcelable不被混淆
- public static final Android.os.Parcelable$Creator *;
- }
使用 enum 类型时需要注意避免以下两个方法混淆,因为 enum 类的特殊性,以下两个方法会被反射调用。
- -keepclassmembers enum * {
- public static **[] values();
- public static ** valueOf(java.lang.String);
- }
9. 注解不混淆
- -keepatrributes *Annotation*
- -keep class * extends java.lang.annotation.Annotation {*;}
10. 泛型不混淆
- -keepattributes Signature
- -keepattributes InnerClasses
- -dontwarn com.google.**
- -keep class com.google.gson.** {*;}
- -keepattributes *Annotation*
- -keepclassmembers class ** {
- @com.squareup.Subscribe public *;
- @com.squareup.otto.Produce public *;
- }
- -dontwarn com.nostra13.universalimageloader.**
- -keep class com.nostra13.universalimageloader.** {*;}
- -keepclassmembers class * {
- public <init> (org.json.JSONObject);
- }
- #友盟统计5.0.0以上SDK需要
- -keepclassmembers enum * {
- public static **[] values();
- public static ** valueOf(java.lang.String);
- }
- #友盟统计R.java删除问题
- -keep public class com.gdhbgh.activity.R$*{
- public static final int *;
- }
- -dontwarn com.squareup.okhttp.**
- -keep class com.squareup.okhttp.** {*;}
- -keep interface com.squareup.okhttp.** {*;}
- -dontwarn okio.**
- -dontwarn com.nineoldandroids.*;
- -keep class com.nineoldandroids.** {*;}
- -keep class com.alipay.android.app.IAlixPay{*;}
- -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 socket.io - client. - keepclasswithmembers,
- allowshrinking class socket.io - client. * { * ;
- } - keep class io.socket. - keepclasseswithmembers,
- allowshrinking class io.socket. * { * ;
- }
- -dontwarn cn.jpush.**
- -keep class cn.jpush.** {*;}
- # protobuf(jpush依赖)
- -dontwarn com.google.**
- -keep class com.google.protobuf.** {*;}
- -dontwarn com.umeng.**
- -dontwarn com.tencent.weibo.sdk.**
- -keep public interface com.tencent.**
- -keep public interface com.umeng.socialize.**
- -keep public interface com.umeng.socialize.sensor.**
- -keep public interface com.umeng.scrshot.**
- -keep public class com.umeng.socialize.* {*;}
- -keep class com.umeng.scrshot.**
- -keep public class com.tencent.** {*;}
- -keep class com.umeng.socialize.sensor.**
- -keep class com.umeng.socialize.handler.**
- -keep class com.umeng.socialize.handler.*
- -keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
- -keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}
- -keep class im.yixin.sdk.api.YXMessage {*;}
- -keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}
- -keep class com.tencent.** {*;}
- -dontwarn com.tencent.**
- -keep public class com.umeng.soexample.R$*{
- public static final int *;
- }
- -keep class com.tencent.open.TDialog$*
- -keep class com.tencent.open.TDialog$* {*;}
- -keep class com.tencent.open.PKDialog
- -keep class com.tencent.open.PKDialog {*;}
- -keep class com.tencent.open.PKDialog$*
- -keep class com.tencent.open.PKDialog$* {*;}
- -keep class com.sina.** {*;}
- -dontwarn com.sina.**
- -keep class com.alipay.share.sdk.** {*;}
一般网络层都不进行混淆,可以经过划分包后直接不混淆网络层的包:
- - keep class com.xxx.xxx.http. * *{ * ;
- }
- - keep class * implements java.io.Serializable { * ;
- } - keepclassmembers class * implements java.io.Serializable { * ;
- }
有时候上面的这种方式可能会导致应用卡住,没有任何错误提示,所以建议采用分包模式,吧所有 bean 放在同一个包中,直接对该包加白名单。
- - keep class com.xxx.xxx.domain.xx { * ;
- }
如果遇到一些空间无法 Inflate,报 NullPointException,比如 ListView,NavigationView 等等
- - keep class * . * *{ * ;
- }
如果混淆后报错,通过 retrace 后找到错误的问题后可以直接编写规则来去掉混淆,但是如果报的错误莫名其妙,而且报错的类没有混淆,那么可以采用极端的方法,加入下面的规则:
- - keep class * . * *{ * ;
- }
这条规则表示不混淆所有类及其中所有代码,加了这条规则之后,还不能运行表示是其他问题,例如注解,内部类等等,可以运行后,可以通过反编译,寻找所有包名,记录下来,吧上述规则改为:
- - keep class android. * *{ * ;
- } - keep class com. * *{ * ;
- } - keep class org. * *{ * ;
- }
一个个去掉检查是否有报错,例如查到
- - keep class com. * *{ * ;
- }
加了就没有错误,则可以继续一级级往下检查。
但要注意,有时候可能是几个包混合问题。
参考文章:
http://mp.weixin.qq.com/s/WmJyiA3fDNriw5qXuoA9MA
http://www.jianshu.com/p/7436a1a32891#
http://www.2cto.com/kf/201607/530170.html
来源: http://www.cnblogs.com/zhangmiao14/p/7098168.html