ProGuard系列 - ProGuard基础

作者:Tong,欢迎交流。
邮箱:wangtotang@gmail.com
GitHub:https://github.com/wangtotang

导读:
  我在学习Android的时候,知道ProGuard是用来混淆和压缩代码的,但使用的时候都只是按网上的第三方库提供的ProGuard配置进行复制粘贴,并没有理解里面的原理,在这个系列文章里,将总结我从头学习ProGuard的知识点,让我们来一次有趣的ProGuard之旅吧!

这是这个系列的第一篇文章,主要来了解ProGuard基础知识,包括功能和基础语法。

1 ProGuard介绍

ProGuard是用于Java文件的压缩、优化、混淆和预校验的免费工具,利用它可以检查和移除无用代码减少程序包体积,优化和混淆并对代码进行预校验。
  ProGuard提供模板化配置,还有简单的命令行选项以及快速的处理过程的优点,广泛用于各种开发场景,特别是Android应用。

ProGuard的用处:
  ● 创建更紧凑的代码和更小的程序包,便于网络传输、减少内存占用以及更快的加载;
  ● 使得程序更难被反编译;      
  ● 检查出无用代码,并移除;      
  ● 重定向和预校验Java类文件。

1.1 ProGuard功能

完整的ProGuard由压缩、优化、混淆和预校验四个步骤组成,每个步骤都默认开启,我们可以在配置文件中决定是否取消这些步骤。

ProGuard按照上图的顺序依次执行,首先检查发现没有使用的类、方法、字段和特性并对其进行移除,减少程序包的体积大小;然后是对字节码进行优化,移除无用的指令(因为Android打包会进行优化,默认不启);再然后是混淆,通过使用简短的名称,如a、b、c,来命名类、方法和变量名,增加程序被反编译的难度(这里需要注意利用反射等动态加载的代码,以免混淆后报错);最后是代码预校验,确保加载的class文件是可执行的(同样因为Android自带代码校验,默认不开启),最后输出新程序包。

由于Java先被编译成.class字节码文件,然后在打成程序包,如war、jar和apk等,都很容易解压得到字节码文件,而字节码文件中包含几乎所有的程序信息,可以很容易被反编译出来得到完整的程序文件,被一些别有居心的人利用,不但无法保证版权,而且还会造成很大的损失,为了更好的保护应用程序运行,我们需要对编译好的class文件进行混淆。

上面四个步骤都是可选的,可以通过以下选项进行关闭功能:
 
  #不进行压缩
  -dontshrink
  #不进行优化
  -dontoptimize
  #不进行混淆
  -dontobfuscate
  #不进行预校验
  -dontpreverify

1.2 工作原理

在上面讲到,ProGuard检查出没有使用的代码,然后移除,它是如何发现没有使用的代码呢?这里就要引入一个入口点(Entry Point)的概念。入口点是指被保持的代码,来决定哪些代码不被移除或者混淆。

  • 在压缩阶段,ProGuard会从入口点开始递归遍历,搜索哪些类和类成员被使用,而其余的没有使用的类和类成员就会被移除;
  • 在优化阶段,ProGuard会用private、static或者final来修饰非入口点的类和方法,无用的参数会被移除,还有将一些方法变成内联方法;
  • 在混淆阶段,ProGuard保留作为入口点的类和类成员的原来名称,重命名非入口点的类和类成员;
  • 预校验阶段是唯一一个不用知道入口点的处理阶段。

那入口点怎么来的呢?其实我们可以通过在ProGuard配置文件中使用Keep选项进行配置它。在接下来一节,我们就开始学习如何使用ProGuard。

1.3 小结

ProGuard处理代码有四个步骤,它们都是可选的,可以通过指令选项关闭;ProGuard通过入口点来递归处理代码,这入口点就是我们
配置的需要保持的代码。学习如何配置ProGuard,这是使用ProGuard的最主要的部分。

这一部分内容可以查看ProGuard介绍

2 ProGuard基础语法

ProGuard的语法很简单:

  • 以一行作为一条语句,支持单空格作为分隔符,额外的空格会被忽略;
  • 支持使用 # 进行行注释;
  • 文件名有空格等特殊符号的用单、双引号标记;
  • ProGuard提供很多通配符给我们使用,不同通配符使用的地方可能不同,这里列出通配符(大致)含义:
    • '?'表示单个字符,
    • '*'表示多个字符 (但不支持包名分隔符),
    • '**'表示任意多个字符,
    • '%'表示基本类型,
    • '***'表示任意类型,
    • '...'表示多个参数,
    • <init>表示构造方法,
    • <fields>表示字段,
    • <methods>表示方法.

2.1 Keep选项

想要使用ProGuard,就要熟悉ProGuard指令选项。Android中我们常用的指令选项一般是Keep选项,这里着重了解一下Keep选项的用法。

# ------------------- 不被移除且不被混淆 ----------------------
#保持类及其成员
-keep [,modifier,...] class_specification

 #保持类成员
-keepclassmembers [,modifier,...] class_specification

#保持含有指定成员及其类
-keepclasseswithmembers [,modifier,...] class_specification

# ---------------------- 不被混淆 ----------------------------
#保持类及其成员的名称
#等同于 -keep,allowshrinking class_specification
-keepnames class_specification

#保持类成员的名称
#等同于 -keepclassmembernames,allowshrinking class_specification
-keepclassmembernames class_specification

#保持含有指定成员及其类的名称
#等同于 -keepclasseswithmembers,allowshrinking class_specification
-keepclasseswithmembernames class_specification

现在我们尝试使用一下Keep选项,

  • 保持一个Activity
# 保持指定的类名,需要全路径类名
-keep public class mypackage.MyActivity
  • 保持一些成员方法
# 保持任意enum类中的static修饰values(),valueOf()方法
# 参数类型也需要全路径类名
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
  • 保持一些成员名称
# 保持class$()方法名称,class$()方法是Java编译器实现构造的,无需理会
-keepclassmembernames class * {
    java.lang.Class class$(java.lang.String);
    java.lang.Class class$(java.lang.String, boolean);
}
  • 保持指定成员名称及其类名称
# 保持含有native修饰的方法的类名称和native修饰的方法名称
-keepclasseswithmembernames class * {
    native <methods>;
}

在我们单独使用*代表类名时,表示任意类名,否则,不单独使用仅匹配多个字符,例如Test*,表示Test1或TestWorld等,且不含有包分隔符,例如不能表示Test.World;

我们在使用的时候,如何具体地写保持的内容呢?下面是完整的模板定义:

[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
    [extends|implements [@annotationtype] classname]
[{
    [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                                  (fieldtype fieldname);
    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                                     <init>(argumenttype,...) |
                                                                                                     classname(argumenttype,...) |
                                                                                                     (returntype methodname(argumenttype,...));
    [@annotationtype] [[!]public|private|protected|static ... ] *;
    ...
}]

Keep选项给我们一种很混乱的感觉,有时候可能不确定选择使用哪一个,其实我们大可不必纠结,直接使用-keep就好了,它能确保指定的类及其成员不被移除且不被混淆。例如:

# 保持model包及其子包下的所有类和所有成员
-keep class com.google.gson.examples.android.model.** { *; }

对于Keep选项,我们要记得两点:
1.只指定类但没有指定类成员,那么类成员可能会被移除、优化或者混淆;
2.只指定类成员,与其相关联的代码可能会被优化或者混淆。

2.2 指令选项

指令选项分成7种,这里只列出在Android中常用到的部分。

I/O选项
# 不忽略类库中非public的类
-dontskipnonpubliclibraryclasses
Keep选项
# 保持ILicensingService这个公共类,名称要使用全路径类名
-keep public class com.google.vending.licensing.ILicensingService

# 对于R(资源)下的所有类及其方法,都不能被混淆
# 这里的$是Java编译器自动编译时内部类产生的
-keep class **.R$* {
    *;
}

# 保持继承自View类的set()、get()方法
# 这里的class *表示任意类,不考虑包名
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# 保持使用@Keep注解的字段及其类
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

# 保持native方法及其类(移除后剩下的)
# 等同于 -keepclasseswithmembers,allowshrinking,这里的allowshrinking是Keep选项的修饰符
-keepclasseswithmembernames class * {
    native <methods>;
}
压缩选项
# 列出无用代码
-printusage
优化选项
# 关闭优化
-dontoptimize

# 优化控制选项,通过指定优化名称进行过滤操作
# 不对算术和类型转化操作符优化,'!'表示排除匹配到的优化
-optimizations !code/simplification/arithmetic,!code/simplification/cast

# 优化次数,如果没有发现可优化的,就会结束优化
# 在0和7之间,默认为5
-optimizationpasses 5

# 允许提升访问修饰符,扩大可访问范围
-allowaccessmodification
混淆选项
# 不使用大小写混合的类名(默认设置)
# 混淆后的类名为小写
-dontusemixedcaseclassnames

# 保持Annotations
-keepattributes *Annotation*

# 保持泛型
-keepattributes Signature

# 保持文件属性和代码行号
# 方便在异常分析中定位
# SourceFile可能保留了重要的文件信息,如无必要,最好去掉
-keepattributes SourceFile,LineNumberTable
预校验选项
# 关闭预校验
-dontpreverify
通用选项
# 打印处理代码的信息,出现异常时,会打印完整栈追踪信息
-verbose

# 生成映射文件
# 包含有类名->混淆后类名的映射关系
# 与-verbose一起使用
-printmapping proguardMapping.txt

# 不打印处理过程中的警告信息
-dontwarn android.support.**

# 不打印关于错误或缺省配置的说明信息
-dontnote rx.internal.util.PlatformDependent
优化名称清单(挺多的,简略写):
   ● class/marking/final  #尽量使用final修饰类
   ● class/merging/*      #尽量使类层次成vertical或者horizontal
   ● field/*              #字段优化相关
   ● method/*             #方法优化相关
   ● code/*               #代码优化相关

2.3 小结

ProGuard的配置文件类似脚本文件,指令选项就是它的命令行。指令选项虽然有7种,不过Keep选项是ProGuard指令选项的主要部分,学习掌握Keep选项和常用的指令选项可以让我们使用Android ProGuard事半功倍。

这一部分内容可以查看 使用指南

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容