简介
Java代码是非常容易反编译的。为了很好的保护Java源代码,我们往往会对编译好的class文件进行混淆处理。
ProGuard是一个混淆代码的开源项目。它的主要作用就是混淆,当然它还能对字节码进行缩减体积、优化等,但那些对于我们来说都算是次要的功能。
官网地址:http://proguard.sourceforge.net/
原理
Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。
混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的缩短变量和函数名以及丢失部分信息的原因, 编译后 jar 文件体积大约能减少25% ,这对当前费用较贵的无线网络传输是有一定意义的。
Android 混淆
Android 混淆代码直接在Android Studio 中配置
android {
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
由于混淆会增加编译时间,并且debug时编译器无法定位到具体位置,所以debug环境下不建议打开。
-
代码混淆
配置如minifyEnabled true
。
代码混淆包括四个步骤,代码压缩,优化,混淆,预校验。- 压缩(shrink) : 移除无效的类,类成员,方法,属性等。
- 优化(optimize) : 分析和优化二进制代码,根据proguard-android-optimize.txt中的描述,优化可能会造成一定的风险,不能保证在所有版本的Dalvik上正常运行。因此android项目建议关闭该项。
- 混淆(obfuscate) : 把类名,属性名, 方法名替换为简短且无意义的名称。
- 预校验(previrfy) : 添加预校验信息。 这个预校验是作用在java 平台的, Android 平台上不需要这项功能,去掉之后还可以加快混淆速度。因此android 项目关闭该项。
这四个流程默认是开启的, 我们需要在Android混淆配置文件中关闭代码优化,和预校验功能,即
-dontoptimize
,-dontpreverify
. 当然proguard-android.txt
中默认已经关闭这两项了。
-
资源混淆
配置如shrinkResources true
。
压缩资源将移除项目及依赖库中未被使用的资源,可以减少apk的大小,一般建议开启。
资源处理包括合并资源
,移除资源
两个流程。- Android Studio 打包时会自动进行merge资源,不受该参数控制。
- 移除资源,需要配置该参数。不过建议可以使用lint先自行过一遍。
还有一种为资源名称的混淆。由facebook开发的redex。
参考 基于 Facebook Redex 实现 Android APK 的压缩和优化
java 混淆
如果要将输出的jar包进行混淆,可以用proguard工具进行操作。分为命令行式和图像界面工具。可以去网上下载或者 android sdk提供了一个图形化工具proguard,可以进行混淆。路径如下 /sdk/tools/proguard/lib/proguardgui.jar
双击打开即可。
在其中配置相关的混淆参数即可。
代码混淆规则
Android项目中配置如下proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
。 由android sdk 中提供的默认混淆规则proguard-android.txt
和 用户自定义的混淆规则proguard-rules.pro
两部分组成。
命令 | 作用 |
---|---|
-keep | 防止类和成员被移除或者被重命名 |
-keepnames | 防止类和成员被重命名 |
-keepclassmembers | 防止成员被移除或者被重命名 |
-keepclassmembersname | 防止成员被重命名 |
-keepclasseswithmembers | 防止拥有该成员的类和成员被移除或者被重命名 |
-keepclasseswithmembernames | 防止拥有该成员的类和成员被重命名 |
类通配符 * | 匹配任意长度字符,但不含包名分隔符(.) |
类通配符 ** | 匹配任意长度字符,并且包含包名分隔符(.) |
类extends | 即可以指定类的基类 |
类implements | 匹配实现了某接口的类 |
类$ | 内部类 |
成员(方法)通配符 * | 匹配任意长度字符,但不含包名分隔符(.) |
成员(方法)通配符 ** | 匹配任意长度字符,并且包含包名分隔符(.) |
成员(方法)通配符 *** | 匹配任意参数类型 |
成员(方法)通配符 ... | 匹配任意长度的任意类型参数 |
成员(方法)通配符 <> | 匹配方法名,eg. <init> |
- 常见混淆命令
# 代码混淆压缩比,在0~7之间,默认为5,一般不下需要修改
-optimizationpasses 5
# 混淆时不使用大小写混合,混淆后的类名为小写
# windows下的同学还是加入这个选项吧(windows大小写不敏感)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库的类
# 默认跳过,有些情况下编写的代码与类库中的类在同一个包下,并且持有包中内容的引用,此时就需要加入此条声明
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共的库的类的成员
-dontskipnonpubliclibraryclassmembers
# 不做预检验,preverify是proguard的四个步骤之一
# Android不需要preverify,去掉这一步可以加快混淆速度
-dontpreverify
# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
-printmapping priguardMapping.txt
# 指定混淆时采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/artithmetic,!field/*,!class/merging/*
# 保护代码中的Annotation不被混淆
# 这在JSON实体映射时非常重要,比如fastJson
-keepattributes *Annotation*
# 避免混淆泛型
# 这在JSON实体映射时非常重要,比如fastJson
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
- keep 元素不进行混淆规则
# 保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留了继承自Activity、Application这些类的子类
# 因为这些子类有可能被外部调用
# 比如第一行就保证了所有Activity的子类不要被混淆
-keep public class * extends android.app.Activity
-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
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
# 如果有引用android-support-v4.jar包,可以添加下面这行
-keep public class com.null.test.ui.fragment.** {*;}
# 保留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 {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(***);
*** get* ();
}
# 保留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 void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 对R文件下的所有类及其方法,都不能被混淆
-keepclassmembers class **.R$* {
*;
}
# 对于带有回调函数onXXEvent的,不能混淆
-keepclassmembers class * {
void *(**On*Event);
}
- 其他自定义混淆规则
#内部类
-keep class com.null.test.MainActivity$* {
*;
}
#webview
-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, java.lang.String);
}
#第三方库
-libraryjars ./libs/android-support-v4.jar
-dontwarn android.support.v4.**
-dontwarn **CompatHoneycomb
-dontwarn **CompatHoneycombMR2
-dontwarn **CompatCreatorHoneycombMR2
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment
资源保持规则
用shrinkResources true开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/ 路径下创建一个 xml 文件,例如 keep.xml。
通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:
tools:keep 定义哪些资源需要被保留(资源之间用“,”隔开)
tools:discard 定义哪些资源需要被移除(资源之间用“,”隔开)
tools:shrinkMode 开启严格模式
mapping文件
混淆过的包必须进行检查,避免因混淆引入的bug。
混淆后会在 路径/build/outputs/mapping/release/
目录下会输出以下文件:
- dump.txt 描述apk文件中所有类的内部结构
- mapping.txt 提供混淆前后类,方法,类成员等的对照表
- seeds.txt 列出没有被混淆的类和成员
- usage.txt 列出被移除的代码
一般发布包的时候都要保留mapping.txt 文件。 目的有有两个, 1. 当出现线上crash时候,可以根据该mapping文件对照源码找到对应的位置。2. 当发布补丁包时候,也会指定该mapping文件,已避免补丁包无法使用。
解出混淆栈
在sdk/tools/proguard
路径下附带了反解工具proguardgui.sh 或者 proguardgui.jar
.打开即可,看到左侧的retrace
, 选择混淆包对应的mapping.txt 文件, 再将crash 的 stack trace 粘贴到输入框中, 点击retrace 混淆前的堆栈信息即可显示。
命令行操作为:
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
eg.retace.sh -verbose mapping.txt log.txt
指定mapping文件
增加proguard-path-rules.pro
, 在其中指定-applymapping mapping.txt
.