声明
这篇文章更多的是做一个整理,内容来自于ProGuard官方文档以及各种博客等,相关文章的链接在
参考目录
里,感兴趣的可以去看看。
本人关于学习代码混淆的建议
了解基本的混淆概念和目的概念 -> 做一下简单的代码混淆实践 -> 详细了解混淆规则
目录:
- 一:代码混淆是什么?
- 二:为什么要进行代码混淆?
- 三:代码混淆能保证代码的绝对安全吗?
- 四:怎么进行代码混淆?
- 五:什么是 ProGuard
- 六:Proguard 的作用?
- 七:Android Studio 中怎么使用 ProGuard 进行代码混淆
- 八:为什么要了解混淆规则
- 九:ProGuard 常用语法(包括 保留、压缩、优化、混淆)
- 十:混淆注意事项
- 十一:ProGuard 的输出文件说明
- 十二:参考
分这么多的目录是为了 能够全面而且循序渐进得 将 ProGuard 讲清楚,同时适应于不同水平的开发者,因为很多刚入门的小白对一些比较基本的概念也是不清楚的,这些也是当初我刚接触 Proguard 时困惑我的点,所以写得比较多,你可以有选择性得看。
一、代码混淆是什么?
删除无用代码,将代码中的各种元素,如包名、类名、函数名、变量名等改成无意义的符号,使得反编译你apk的人无法根据名字猜测代码的用途,这是一种加密手段。
二、为什么要进行代码混淆?
如果代码没经过混淆,发布出去后,别人只需要反编译即可查看你的源码,这是一种知识产权的保护手段。
三、代码混淆能保证代码的绝对安全吗?
混淆的目的是为了加大反编译的成本,但是并不能彻底防止反编译
四、怎么进行代码混淆?
使用Android Studio创建项目时会在项目根目录下生成一个
proguard-rules.pro
文件,该文件便是指定项目混淆规则的文件,使用的时候只需要在里面加入相应的混淆规则即可。也就是说,你在这个文件里面指定 哪些代码需要混淆,哪些不需要
至于什么是混淆规则、混淆规则的语法、哪些需要或不需要混淆,在下面会详细讲到。
五、什么是ProGuard
ProGuard是一个开源的Java 代码混淆器。它能且只能混淆Android项目里面的java代码,对于Native代码,资源文件Drawable、Xml等无法进行混淆。
- 官方对Proguard的解释是:
Proguard是一个集合了 文件压缩、优化、混淆和校验 等功能的工具。
它通过将类名、变量名、方法名重命名为无意义的名称实现混淆效果;
它检测并删除无用的类,变量,方法和属性;
它优化字节码并删除无用的指令;
最后它还校验处理后的代码。
进入app下的build.gradle,可以看到:
buildTypes {
release {
//buildConfigField "boolean", "LEO_DEBUG", "true" 在编译时定义新的属性及属性值到BuildConfig.java中
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
其中
proguardFiles getDefaultProguardFile
就是制定 混淆规则 的文件,分两部分:
(1)前一部分
proguard-android.txt
代表系统默认的android
程序的混淆文件,该文件已经包含了基本的混淆声明(例如对枚举、注解、Activity
等的过滤),帮我们省去了很多事,这个文件在SDK
的/tools/proguard/proguard-android.txt
下;
(2)后一部分是我们项目里的自定义的混淆文件,目录在
app/proguard-rules.pro
下,在这个文件里我们可以声明一些我们所需要的定制的混淆规则,主要在这里处理的有 对第三方库的混淆声明、对实体类的混淆声明等。
六、Proguard的作用?
压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。
优化(Optimize):对字节码进行优化,移除无用的指令。
混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。
预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。
七、Android Studio中怎么使用ProGuard进行代码混淆
很简单,只需要 修改build.grade 配置文件,将
buildTypes
代码块中的 minifyEnabled false 改为 minifyEnabled true 即可。
注:接下来在以release模式下打包apk时便会自动运行ProGuard,此时即使你不添加自己的混淆规则,也会进行代码混淆。
可以看看google默认的混淆文件,在 \sdk\tools\proguard\proguard-android.txt
接下来介绍混淆规则
八、为什么要了解混淆规则
其实google已经给我们提供了很好的打包规则, 即如果我们将minifyEnabled set为true后,即使我们在proguard-rules.pro 里啥也不写, 我们打出来的release包也是混淆好的
但是!如果我们不添加混淆规则,一般程序在build的过程中会因为出现问题而中断。
如果加入一些自己的混淆规则 只需要在 proguard-rules.pro 中文件加入自己的混淆规则即可,
九、ProGuard常用语法(包括 保留、压缩、优化、混淆)
详见官方文档:https://www.guardsquare.com/en/proguard/manual/usage#classspecification
保留
libraryjars
使用该语句把你项目所有用到的jar包都声明进来
1.libraryjars class_path 应用的依赖包,如android-support-v4
例如: -libraryjars libs/universal-image-loader-1.9.0.jar
keep相关语法
表明要保留哪些 Java元素不进行混淆 (前3条指定 类或成员,后3条指定 类或成员 的名称)
1.keep [,modifier,...] class_specification 不混淆指定的类文件和类的成员
例如:
(1).保留某个包下面的类以及子包 -keep public class com.droidyue.com.widget.**
(2).-keep class com.czy.**//不混淆所有com.czy包下的类,** 换成具体的类名则表示不混淆某个具体的类
(3).-keep class com.clock.**{*;}//不混淆所有com.clock包下的类和类中的所有成员变量,**可以换成具体类名,*可以换成具体的字段,可参照Serialzable的混淆
2.keepclassmembers [,modifier,...] class_specification 不混淆指定类的成员,如果此类受到保护他们会被保护得更好
例如:
(1).保留所有类中使用otto的public方法:
-keepclassmembers class ** {
@com.squareup.otto.Subscribe public *;
@com.squareup.otto.Produce public *;
}
(2).保留Contants类的BOOK_NAME属性:
-keepclassmembers class com.example.admin.proguardsample.Constants {
public static java.lang.String BOOK_NAME;
}
3.keepclasseswithmembers [,modifier,...] class_specification 不混淆指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
4.keepnames class_specification 不混淆指定的类和类的成员的 名称(如果他们不会压缩步骤中删除)
5.keepclassmembernames class_specification 不混淆指定的类的 成员的名称 (如果他们不会压缩步骤中删除)
6.keepclasseswithmembernames class_specification 不混淆指定的类和类的成员 的名称,如果所有指定的类成员出席(在压缩步骤之后)
7.printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件
dontwarn
dontwarn是一个和keep可以说是形影不离,尤其是处理引入的library时.
引入的library可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.因此为了保证build继续,我们需要使用dontwarn处理这些我们无法解决的library的警告.
8.dontwarn [class_filter] 不提示warnning
例如:关闭Twitter sdk的警告 -dontwarn com.twitter.sdk.**
压缩
9.dontshrink 不压缩输入的类文件
10.printusage {filename}
11.whyareyoukeeping {class_specification}
优化
12.dontoptimize 不优化输入的类文件
13.assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用,即假设调用不产生任何影响,在proguard代码优化时会将该调用remove掉。如system.out.println和Log.v等等
14.allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员,
混淆
15.dontobfuscate 不混淆输入的类文件
16.obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称
17.overloadaggressively 混淆时应用侵入式重载
18.useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆
19.flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
20.repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
21.dontusemixedcaseclassnames 混淆时不会产生形形色色的类名
22.keepattributes {attribute_name,…} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
23.renamesourcefileattribute {string} 设置源文件中给定的字符串常量
通配符匹配规则
通配符 | 规则 |
---|---|
? | 匹配单个字符 |
* | 匹配类名中的任何部分,但不包含额外的包名 |
** | 匹配类名中的任何部分,并且可以包含额外的包名 |
% | 匹配任何基础类型的类型名 |
详细通配符见官方文档
https://www.guardsquare.com/en/proguard/manual/usage#classspecification
十、混淆注意事项
1.哪些不能混淆
1.反射中使用的元素,如一些ORM框架的使用,需要保证类名、方法不变, 不然混淆后, 就反射不了
如果用到了反射需要加入 :
-keepattributes Signature
-keepattributes EnclosingMethod
2.如果想让一些bean 对象不混淆, 例如com.czy.bean 包下面的全是 Json框架生成的bean对象, 那么只需加入:
-keep class czy.**{*;}//不混淆所有的com.czy.bean包下的类和这些类的所有成员变量
3.枚举也不要混淆
4.四大组件不建议混淆,因为四大组件声明必须在manifest中注册,而混淆后类名会发生更改,此时混淆后的类名没有在manifest注册,这是不符合Android组件注册机制的。(AndroidMainfest中的类不混淆,四大组件和Application的子类和Framework层下所有的类默认不会进行混淆)
5.注解不能混淆
注解在Android平台中使用的越来越多,常用的有ButterKnife和Otto.很多场景下注解被用作在运行时反射确定一些元素的特征.
为了保证注解正常工作,我们不应该对注解进行混淆.Android工程默认的混淆配置已经包含了下面保留注解的配置
-keepattributes *Annotation*
6.JNI调用的java方法
7.Java的Native方法
8.JS调用Java的方法
9.WebView中JavaScript调用的方法方法不混淆
有用到WEBView的JS调用接口,需加入如下规则:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keep class com.xxx.xxx.** { *; }//保持WEB接口不被混淆 此处xxx.xxx是自己接口的包名
10.第三方库不建议混淆,使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则(关于第三方的库的, 一般都是看他们的官方文档)
这里推荐两个开源项目,里面收集了一些第三方库的混淆规则
android-proguard-snippets
android-proguard-cn
不难理解,混淆之后,类名会变成a,b,c这种,通过包名+类名自然就会找不到该类了,自然就会出现ClassNotFoundException异常。这里推荐一篇文章:http://www.itnose.net/detail/6043297.html
11.Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常
12.GSON的序列化与反序列化
使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象
13.继承了Serializable接口的类,在反序列化的时候, 需要正确的类名等, 在Android 中大多是实现 Parcelable来序列化的
继承了Serializable接口的类,需要加上:
//不混淆Serializable接口的子类中指定的某些成员变量和方法
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
14.Layout文件引用到的自定义View
2.Log处理
我们都知道,使用Log的时候,需要用到TAG,然而TAG我们一般都会写成:
private static final String TAG = MainActivity.class.getSimpleName()
这时候MainActivity如果被混淆的话,log输出信息就会变成V/a:xxxxxxx
,所以为了让log输出信息维持原状,可以将TAG处理成固定的字符串:
private static final String TAG = "MainActivity"
正好Android Studio里面的 Live Templates 能让你轻轻松松的声明TAG !
关于Log处理,推荐一篇文章:https://www.zybuluo.com/shark0017/note/163330
移除一些log代码:
移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用,另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** i(...);
public static *** d(...);
public static *** w(...);
public static *** e(...);
}
3.Crash信息处理
代码混淆的时候记得加上在混淆文件里面记得加上这句:
# keep住源文件以及行号
-keepattributes SourceFile,LineNumberTable
否则你将看到崩溃信息
这里推荐bugly的一篇文章: http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=26&extra=page%3D1
十一、ProGuard的输出文件说明
混淆后,会在/build/proguard/目录下输出下面的文件
dump.txt 描述apk文件中所有类文件间的内部结构。
mapping.txt 列出了原始的类,方法,和字段名与混淆后代码之间的映 射。
seeds.txt 列出了未被混淆的类和成员
usage.txt 列出了从apk中删除的代码 当我们需要处理crash log的时候,就可以通过mapping.txt的映射关系找到对应的类,方法,字段等。方法如下:
sdk\tools\proguard\bin 目录下有个retrace工具可以将混淆后的报错堆栈解码成正常的类名window下为retrace.bat,linux和mac为retrace.sh,
使用方法如下:
1.将crash log保存为yourfilename.txt
2.拿到版本发布时生成的mapping.txt
3.执行命令retrace.bat -verbose mapping.txt yourfilename.txt
所以我们每次打包版本都需要保存最新的mapping.txt文件。如果要使用到第三方的crash统计平台,比如bugly,还需要我们上传APP版本对应的mapping.txt.每次都要保存最新的mapping文件,那不就很麻烦?放心,gradle会帮到你,只需要在bulid.gradle加入下面的一句。每次我们编译的时候,都会自动帮你保存mapping文件到本地的。
android {
applicationVariants.all { variant ->
variant.outputs.each { output ->
if (variant.getBuildType().isMinifyEnabled()) {
variant.assemble.doLast{
copy {
from variant.mappingFile
into "${projectDir}/mappings"
rename { String fileName ->
"mapping-${variant.name}.txt"
}
}
}
}
}
......
}
}
实践记录
混淆实体类
实体类不能混淆,需要保留
set
和get
方法。对于boolean
类型的get方法为isXXX
,不能遗漏。在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。
-keep public class com.ljd.example.entity.** {
public void set*(***);
public *** get*();
public *** is*();
}
-keep class com.demo.login.bean.** { *; }
-keep class com.demo.main.bean.** { *; }
反编译三步走:
1.下载以下三个工具:
-
apktool
- 作用:资源文件获取,可以提取出图片文件和布局文件进行使用查看
-
dex2jar
- 作用:将apk反编译成
Java
源码(classes.dex
转化成jar
文件)
- 作用:将apk反编译成
-
jd-gui
- 作用:查看APK中
classes.dex
转化成出的jar文件,即源码文件
- 作用:查看APK中
2.下载上述工具中的apktool
,解压得到3个文件:aapt.exe
,apktool.bat
,apktool.jar
将需要反编译的APK文件放到该目录下,打开命令行界面(运行-CMD) ,定位到apktool文件夹,输入以下命令:
apktool.bat d -f test.apk test
即
apktool.bat d -f [apk文件 ] [输出文件夹])
此时test文件夹下即包含了所有资源文件
3.下载上述工具中的dex2jar
和jd-gui
,解压
将要反编译的APK后缀名改为
.rar
或则.zip
,并解压,得到其中的classes.dex
文件(它就是java
文件编译再通过dx工具打包而成的),将获取到的classes.dex
放到之前解压出来的工具dex2jar-0.0.9.15
文件夹内,
在命令行下定位到dex2jar.bat
所在目录,输入dex2jar.bat classes.dex
,
在改目录下会生成一个
classes_dex2jar.jar
的文件,然后打开工具jd-gui
文件夹里的jd-gui.exe
,之后用该工具打开之前生成的classes_dex2jar.jar
文件,便可以看到源码了
4.下面图文解释,对应上面流程
注:上述反编译资料来自http://blog.csdn.net/vipzjyno1/article/details/21039349/
参考
ProGuard手册
ProGuard手册 Version4.7
ProGuard代码混淆技术详解
Android分享:代码混淆那些事
http://blog.csdn.net/qq_35224673/article/details/52038093
http://blog.csdn.net/chen930724/article/details/49687067
http://blog.csdn.net/ljd2038/article/details/51308768 很详细
http://www.tuicool.com/articles/vyEnu2A 含例子
http://www.jianshu.com/p/be7ec1819d2f 含模板
http://www.jianshu.com/p/7391f0c554be 含内嵌类处理
常用第三方库的混淆
常用第三方库的混淆