共计 12163 个字符,预计需要花费 31 分钟才能阅读完成。
入口
为了决定哪些代码要被保留哪些代码要出抛弃和混同,必须指定入口点。这些入口点通常是 main 办法,activity,service 等。
- 在压缩阶段,Proguard 从这些入口点开始递归确定哪些类或类成员要被应用,其余的都会被抛弃。
- 在优化阶段,ProGuard 会进一步优化代码。在其余优化中,能够将不是入口点的类和办法设为 private,static 或 final,删除未应用的参数,并且能够内联一些办法。
- 在混同阶段,ProGuard 会重新命名不属于入口点的类和类成员。在整个过程中,保障入口点依然能够通过其原始名称拜访。
查看 Proguard 输入后果
为了防止引入 bug 咱们有必要对 后果进行查看。
在 Android 中,开启了混同构建会在 <module-name>/build/outputs/mapping/ 目录下会输入以下文件:
- dump.txt 形容 APK 文件中所有类的内部结构
- mapping.txt 提供混同前后类、办法、类成员等的对照表
- seeds.txt 列出没有被混同的类和成员
- usage.txt 列出被移除的代码
咱们能够依据 seeds.txt 文件查看未被混同的类和成员中是否已蕴含所有冀望保留的,再依据 usage.txt 文件查看是否有被误移除的代码。
过滤器
ProGuard 为许多配置提供了不同方面的过滤选项:文件名称,目录,类别,软件包,属性,优化等。
过滤器是能够蕴含通配符的,以逗号分隔的,名称列表。
只有与列表中的我的项目匹配的名称才会通过过滤器。
每种配置的通配符可能有所不同,但以下通配符是通用的:
- ? 匹配名称中的任何单个字符。
- * 匹配不蕴含包分隔符或目录分隔符的名称的任何局部
- ** 匹配名称的任何局部,可能蕴含任意数量的包分隔符或目录分隔符。
此外,名称前能够加上否定感叹号 !
排除名称与进一步尝试匹配后续名称。
因而,如果名称与过滤器中的某个我的项目相匹配,则会立刻承受或回绝该我的项目,具体取决于我的项目是否具备否定符。
如果名称与我的项目不匹配,则会针对下一个我的项目进行测试,依此类推。
它如果与任何我的项目不匹配,则依据最初一项是否具备否定符而被承受或回绝。
如,”!foobar,*.bar” 匹配除了 foobar 之外的所有以 bar 结尾的名称。
上面以过滤文件具体举例。
文件过滤器
像通用过滤器一样,文件过滤器是逗号分隔的文件名列表,能够蕴含通配符。只有具备匹配文件名的文件被读取(在输出的状况下),或者被写入(在输入的状况下)。反对以下通配符:
- ? 匹配文件名字中的任何单个字符
- * 匹配不蕴含目录分隔符的文件名的任何局部。
- ** 匹配文件名的任何局部,能够蕴含任意数目的目录分隔符。
例如 “java/**.class ,javax/**.class” 能够匹配 java 和 javax 目录下所有的 class 文件。
此外,文件名后面可能带有感叹号 ’!’ 将文件名排除在与后续文件名匹配上。
例如 “!**.gif,images/**” 匹配 images 目录下所有除了 gif 的文件
对于更具体的用法 能够查看官网文档 filtering
keep 选项
-keep [,modifier,…] class specification
指定类和类成员(字段,办法)作为入口点被保留。
例如,为了保留一个程序,你要指定 Main 办法和类。为了保留一个库,你应该指定所有被公开拜访的元素。
- 保留 main 类和 main 办法
-keep public class com.example.MyMain {public static void main(java.lang.String[]);
}
- 保留所有被公开拜访的元素
-keep public class * {public protected *;}
Note: 如果你只保留了类,没有保留类成员,那么你的类成员将不会被保留
例如 有一个实体类
public class Product implements Serializable {
public static final int A = 1;
public static final int B = 2;
private String name;
private String url;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getUrl() {return url;}
public void setUrl(String url) {this.url = url;}
}
规定配置如下
# 保留 Product 类
-keep class cn.sintoon.camera.Product
usage.txt 文件中有以下内容,能够看到 类中的成员全副被移除了
cn.sintoon.camera.Product:
public static final int A
public static final int B
private java.lang.String name
private java.lang.String url
16:16:public java.lang.String getName()
20:21:public void setName(java.lang.String)
24:24:public java.lang.String getUrl()
28:29:public void setUrl(java.lang.String)
-keepclassmembers [,modifier,…] class specification
指定要保留的类成员, 前提是它们的类也被保留了 。
例如,你想保留实现了 Serializable 接口的类中的所有 serializable 办法和字段。
-keepclassmembers class * implements java.io.Serializable {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();}
Note: 留神字段类型带上包名; String 类型为 java.lang.String; 另外,如果只保留了类成员没有保留类跟没有保留一样
还是拿下面那个例子,改一下规定
-keepclassmembers class * implements java.io.Serializable{
private String name;
public String getName();
public static final int A;
}
再看 usage.txt 类都被移除了,保留字段没毛线用。
cn.sintoon.camera.Product
-keepclasseswithmembers [,modifier,…] class specification
指定要保留的类和类成员,条件是所有指定的类成员都在。
例如,你要保留程序中所有的主程序,不必显示的列出。
-keepclasseswithmembers public class * {public static void main(java.lang.String[]);
}
还是用下面那个例子,保留住类和所有的类成员
-keepclasseswithmembers class cn.sintoon.camera.Product{
public static final int A;
public static final int B;
private java.lang.String name;
private java.lang.String url;
public java.lang.String getName();
public void setName(java.lang.String);
public java.lang.String getUrl();
public void setUrl(java.lang.String);
}
看 seeds.text 中就会呈现这个类和类成员
cn.sintoon.camera.Product
cn.sintoon.camera.Product: int A
cn.sintoon.camera.Product: int B
cn.sintoon.camera.Product: java.lang.String name
cn.sintoon.camera.Product: java.lang.String url
cn.sintoon.camera.Product: java.lang.String getName()
cn.sintoon.camera.Product: void setName(java.lang.String)
cn.sintoon.camera.Product: java.lang.String getUrl()
cn.sintoon.camera.Product: void setUrl(java.lang.String)
Note: 肯定要留神指定的类成员必须存在,如果不存在的话,这个规定相当于没有配,一点作用没有
-keepnames class specification
-keep,allowshrinking class specification 的简写
指定要保留名称的类成员和类成员(如果它们在压缩阶段未被删除)。
例如,你可能心愿保留实现 Serializable 接口的类的所有类名,以便解决后的代码与任何原始序列化的类放弃兼容。
齐全不必的类依然能够删除。只有在混同时才实用。
-keepnames class * implements java.io.Serializable
Note: 前提是在压缩阶段没有被删除掉,这里相当于应用了修饰符 allowshrinking
-keepclassmembernames class specification
-keepclassmembers,allowshrinking class specification 的简写
指定要保留名称的类成员(如果它们在压缩阶段未被删除)。
例如,在解决由 JDK 1.2 或更早版本编译的库时,可能心愿保留合成类 $ 办法的名称。
所以当解决应用解决过的库的应用程序时,混同器能够再次检测到它(只管 ProGuard 自身不须要这个)。
只有在混同时才实用。
-keepclassmembernames class * {java.lang.Class class$(java.lang.String);
java.lang.Class class$(java.lang.String, boolean);
}
Note: 前提是在压缩阶段没有被删除掉,这里相当于应用了修饰符 allowshrinking
-keepclasseswithmembernames class specification
-keepclasseswithmembers,allowshrinking class specification 的简写
指定要保留名称的类和类成员,条件是所有指定的类成员都存在于膨胀阶段之后。
例如,可能心愿保留所有本机办法名称和类别的名称,以便解决的代码仍能够与本机库代码链接。齐全没有应用的本地办法依然能够被删除。
如果应用了一个类文件,但它的本地办法都不是,它的名字依然会被混同。只有在混同时才实用。
-keepclasseswithmembernames,includedescriptorclasses class * {native <methods>;}
Note: 前提是在压缩阶段没有被删除掉,这里相当于应用了修饰符 allowshrinking
-printseeds [filename]
指定详尽列出由各种 -keep 选项匹配的类和类成员。列表打印到规范输入或给定文件。该列表可用于验证是否真的找到了预期的类成员,尤其是在应用通配符的状况下。
例如,您可能想要列出您保留的所有应用程序或所有小程序。
参考下面说的 seeds.txt
-whyareyoukeeping class specification
指定打印详细信息,阐明为什么给定的类和类成员正在压缩步骤中。
如果想晓得为什么某些给定元素呈现在输入中,这会很有用。
一般来说,可能有很多不同的起因。
此选项为每个指定的类和类成员打印最短的办法链到指定的种子或入口点。
在以后的施行中,打印出的最短链有时可能蕴含循环扣除 – 这些并不反映理论膨胀过程。
如果指定了 -verbose 选项,则跟踪包含残缺的字段和办法签名。只实用于压缩。
压缩规定
-dontshrink
指定不被压缩的类文件。
默认状况下压缩是开启的,除了用各种用 keep
选项间接或间接用到的类或类成员,其余的都会被移除。
压缩步骤通常在优化之后,因为某些优化可能会关上曾经删除的类或类成员。
-printusage [filename]
指定列出移除的死代码。该列表打印到规范输入或给定文件。
参考下面说的 usage.txt
例如,您能够列出应用程序的未应用代码。只实用于压缩。
优化规定
-dontoptimize
指定不优化输出类文件。默认状况下,优化已启用; 所有办法都在字节码级别进行了优化
-optimizationpasses n
指定要执行的优化传递的数量。
默认状况下,执行一次传递。屡次通行可能会导致进一步的改良。如果在优化后没有找到改良,则优化完结。只实用于优化。
混同规定
-dontobfuscate
指定不混同输出的类文件。
默认状况下,混同是开启的,类和类成员会被改成新的短随机名称,除了各种 -keep 选项列出的名称外。
外部属性对于调试很有用,例如源文件名,变量名和行号被删除。
-printmapping [filename]
指定将旧名称映射到已重命名的类和类成员的新名称的映射。映射打印到规范输入或给定文件。
例如,它是后续增量混同所必须的,或者如果想再次了解混同的堆栈跟踪。只有在混同时才实用。
参考 下面说的 mapping.txt。
-useuniqueclassmembernames
指定将雷同的混同名称调配给具备雷同名称的类成员,并将不同混同名称调配给名称不同的类成员(对于每个给定的类成员签名)。
没有这个选项,更多的类成员能够被映射到雷同的短名称,比方 ’a’,’b’ 等等。
这个选项因而略微减少了后果代码的大小,然而它确保了保留的混同名称映射总是能够在随后的增量混同步骤中受到尊重。
例如,思考两个不同的接口,它们蕴含具备雷同名称和签名的办法。如果没有此选项,这些办法可能会在第一个混同步骤中获取不同的混同名称。
如果增加了蕴含实现两个接口的类的补丁程序,则 ProGuard 必须在增量混同步骤中为这两种办法强制执行雷同的办法名称。
原始含糊代码已更改,以放弃后果代码的一致性。在最后的混同步骤中应用此选项,这种重命名将永远不是必须的。
该选项仅实用于混同。
实际上,如果打算执行增量混同,则可能心愿完全避免压缩和优化,因为这些步骤可能会删除或批改局部代码,这些代码对于当前的增加至关重要。
-dontusemixedcaseclassnames
指定在混同时不生成混合大小写的类名。
默认状况下,混同的类名能够蕴含大写字符和小写字符的混合。
创立的这个齐全可承受和可用的 jars 只有在不辨别大小写的文件系统(比方 Windows)的平台上解压缩 jar 时,解压缩工具可能会让相似命名的类文件互相笼罩。
解压缩后自毁的代码!真正想在 Windows 上解压他们的 jar 的开发人员能够应用这个选项来敞开这种行为。
混同的 jars 会因而变得稍大。
只有在混同时才实用。
-keeppackagenames [package_filter]
指定不混同给定的软件包名称。
可选过滤器是包名称的逗号分隔列表。包名能够蕴含?, 和 * 通配符,并且它们能够在!否定器。只有在混同时才实用。
-flattenpackagehierarchy [package_name]
指定将所有重命名的软件包从新打包,办法是将它们挪动到单个给定的父软件包中。如果没有参数或空字符串(”),程序包将挪动到根程序包中。
该选项是进一步混同软件包名称的一个示例。它能够使解决后的代码更小,更难了解。
只有在混同时才实用。
-repackageclasses [package_name]
指定将所有重命名的类文件从新打包,办法是将它们挪动到单个给定的包中。没有参数或者应用空字符串(”),该软件包将被齐全删除。
该选项将笼罩 -flattenpackagehierarchy 选项。
这是进一步含糊软件包名称的另一个例子。
它能够使解决后的代码更小,更难了解。
其不举荐应用的名称是 -defaultpackage。
只有在混同时才实用。
正告:如果在别处挪动它们,则在其包目录中查找资源文件的类将不再失常工作。如有疑难,请不要应用此选项,免得涉及包装。
-keepattributes [attribute_filter]
指定要保留的任何可选属性。这些属性能够用一个或多个 -keepattributes 指令来指定。
可选过滤器是 Java 虚拟机和 ProGuard 反对的属性名称的逗号分隔列表。
属性名称能够蕴含?,* 和 ** 通配符,并且能够在之前加上!否定器。
例如,在解决库时,您至多应保留 Exceptions,InnerClasses 和 Signature 属性。
您还应该保留 SourceFile 和 LineNumberTable 属性以生成有用的混同堆栈跟踪。
最初,如果你的代码依赖于它们,你可能须要保留正文。
只有在混同时才实用。
# 保留 Annotation 不混同
-keepattributes *Annotation*,InnerClasses
# 防止混同泛型
-keepattributes Signature
# 抛出异样时保留代码行号
-keepattributes SourceFile,LineNumberTable
-keepparameternames
指定保留所保留办法的参数名称和类型。
该选项实际上保留了调试属性 LocalVariableTable 和 LocalVariableTypeTable 的修剪版本。
解决库时它可能很有用。
一些 IDE 能够应用这些信息来帮忙应用该库的开发人员,
例如工具提醒或主动实现。
只有在混同时才实用。
-renamesourcefileattribute [string]
指定要放入类文件的 SourceFile 属性(和 SourceDir 属性)中的常量字符串。请留神,该属性必须首先呈现,所以它也必须应用 -keepattributes 指令明确保留。
例如,您可能心愿让解决过的库和应用程序生成有用的混同堆栈跟踪。
只有在混同时才实用
预校验 规定
-dontpreverify
指定不事后验证已解决的类文件。
默认状况下,如果类文件针对 Java Micro Edition 或 Java 6 或更高版本,则会对其进行预验证。
对于 Java Micro Edition,须要进行预验证,因而如果指定此选项,则须要在解决的代码上运行内部预验证程序。
对于 Java 6,预验证是可选的,但从 Java 7 开始,它是必须的。
只有在最终对 Android 时,它才不是必须的,因而您能够将其敞开以缩短解决工夫。
-android
指定已解决的类文件针对 Android 平台。而后 ProGuard 确保一些性能与 Android 兼容。
例如,如果您正在解决 Android 应用程序,则应该指定此选项。
个别规定
-verbose
指定在解决期间写出更多信息。如果程序以异样终止,则此选项将打印出整个堆栈跟踪,而不仅仅是异样音讯。
-dontnote [class_filter]
指定不打印无关配置中可能的谬误或脱漏的正文,
例如类名中的拼写错误或短少可能有用的选项。
可选的过滤器是一个正则表达式;
ProGuard 不打印无关匹配名称的类的正文。
-dontwarn [class_filter]
指定不正告无关未解决的援用和其余重要问题。
可选的过滤器是一个正则表达式; ProGuard 不打印对于具备匹配名称的类的正告。疏忽正告可能是危险的。
例如,如果解决的确须要未解决的类或类成员,则解决后的代码将无奈失常工作。
只有在你晓得本人在做什么的状况下才应用此选项!
-ignorewarnings
指定打印任何对于未解决的援用和其余重要问题的正告,但在任何状况下都持续解决,疏忽正告。
疏忽正告可能是危险的。
例如,如果解决的确须要未解决的类或类成员,则解决后的代码将无奈失常工作。
只有在晓得本人在做什么的状况下才应用此选项!
-printconfiguration [filename]
指定应用蕴含的文件和替换的变量写出已解析的整个配置。构造打印到规范输入或给定文件。
这对于调试配置或将 XML 配置转换为更易读的格局有时会很有用。
-dump [filename]
指定在任何解决后写出类文件的内部结构。构造打印到规范输入或给定文件。
例如,可能心愿写出给定 jar 文件的内容,而不进行解决。
参考下面说的 dump.txt。
-addconfigurationdebugging
指定用调试语句来解决已解决的代码,这些语句显示短少 ProGuard 配置的倡议。
如果解决后的代码解体,那么在运行时取得实用提醒可能十分有用,因为它依然短少一些反射配置。
例如,代码可能是应用 GSON 库序列化类,可能须要一些配置。通常能够将控制台的倡议复制 / 粘贴到配置文件中。
正告:不要在发行版本中应用此选项,因为它将混同信息增加到已解决的代码中。
keep 选项修饰符
includedescriptorclasses
指定 -keep 选项所保留的办法和字段的类型描述符中的任何类也应保留。
在保留办法名称时,这通常很有用,以确保办法的参数类型不会重命名。他们的签名放弃齐全不变,并与本地库兼容。
includecode
指定放弃 -keep 选项所保留的字段的办法的代码属性也应该保留,即可能未被优化或含糊解决。这对于已优化或混同的类通常很有用,以确保在优化期间未修改其代码。
allowshrinking
指定 -keep 选项中指定的入口点可能会压缩,即便必须另外保留它们。
也就是说,能够在压缩步骤中删除入口点,但如果它们是必须的,则它们可能未被优化或混同。
allowoptimization
指定 -keep 选项中指定的入口点可能会被优化,即便它们必须另外保留。
也就是说,入口点可能会在优化步骤中被更改,但它们可能不会被删除或混同。
此修饰符仅用于实现不寻常的要求。
allowobfuscation
指定在 -keep 选项中指定的入口点可能会被混同,即便它们必须另外保留。
也就是说,入口点可能在混同步骤中被重命名,但它们可能不会被删除或优化。
此修饰符仅用于实现不寻常的要求。
keep 选项之间的关系
压缩和混同的各种 -keep 选项起初看起来有点凌乱,但实际上它们背地有一个模式。
下表总结了它们之间的关系:
内容 | 被删除或重命名 | 被重命名 |
---|---|---|
类和类成员 | -keep | -keepnames |
只有类成员 | -keepclassmembers | -keepclassmembernames |
类和类成员,援用成员存在 | -keepclasseswithmembers | -keepclasseswithmembernames |
如果指定了一个没有类成员的类,ProGuard 只保留该类及其无参数的构造函数作为入口点。它可能仍会删除,优化或混同其余班级成员。
如果指定了一个办法,则 ProGuard 仅将该办法作为入口点进行保留。其代码可能仍会进行优化和调整。
类标准
类标准是类和类成员(字段和办法)的模板。它用于各种 -keep 选项和 -assumenosideeffects 选项中。相应的选项仅实用于与模板匹配的类和类成员。
模板的设计看起来十分相似于 Java,并为通配符进行了一些扩大。为了了解语法,你应该看看这些例子,但这是对一个残缺的正式定义的尝试:
[@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 ... ] *;
...
}]
方括号“[]”示意其内容是可选的。
省略号点“…”示意能够指定任意数量的前述我的项目。
垂直条“|”划定了两种抉择。
非粗体括号“()”只是将属于标准的局部组合在一起。
缩进尝试廓清预期的含意,但在理论配置文件中,空白是不相干的。
class 关键字指的是任何接口或类。interface 关键字限度匹配接口类。enum 关键字限度匹配枚举类。在 interface 或 enum 关键字前加上!将匹配限度为不是接口或枚举的类。
每一个类名字都必须是齐全限定名,例如 java.lang.String 外部类用美元符号“$”分隔,例如 java.lang.Thread$State。类名能够被指定为蕴含以下通配符的正则表达式:
- ? 匹配类名称中的任何单个字符,但不匹配包分隔符。例如 “com.example.Test?” 能够匹配 “com.example.Test1” 和 “com.example.Test2” 但不能匹配 “com.example.Test12”
- * 匹配不蕴含包分隔符的类名的任何局部。例如 “com.example.*Test*” 可能匹配 “com.example.MyTest” 和 “com.example.MyTestProduct” 但不能匹配 “com.example.mxc.MyTest” 或者 “com.example.*” 可能匹配 “com.example” 但不能匹配 “com.example.mxc”
- ** 匹配类名称的任何局部,可能蕴含任意数量的包分隔符。例如,”**.Testz” 匹配除根包以外的所有包中的所有 Test 类。或者,”com.example.**” 匹配 “com.example” 中的所有类及其子包。
- <n> 在雷同的选项中匹配第 n 个匹配的通配符。例如,”com.example.*Foo<1>” 匹配 ”com.example.BarFooBar”。
为了取得更多的灵活性,类名实际上能够是逗号分隔的类名列表,能够加!。这个符号看起来不是很像 java,所以应该适度应用。
为了不便和向后兼容,类名 * 指任何类,而不思考它的包。
- extends 和 implements 通常用来限度应用通配符的类。目前他们是一样的。他们的意思是 只有继承或实现了给定类的类才有资格。给定的类自身不蕴含在这个汇合中。如果须要,应该在独自的选项中指定。
- @ 可用于将类和类成员限度为应用指定的正文类型进行正文的类。annotationtype 就像类名一样被指定。
- 除了办法参数列表不蕴含参数名称外,字段和办法在 Java 中的定义十分相似(就像在 javadoc 和 javap 等其余工具中一样)。这些标准还能够蕴含以下通配符通配符:
通配符 | 意义 |
---|---|
<init> | 匹配任何构造方法 |
<fields> | 匹配任何字段 |
<methods> | 匹配任何办法 |
* | 匹配任何办法和字段 |
请留神,上述通配符没有返回类型。只有 <init> 通配符才有一个参数列表。
字段和办法也能够应用正则表达式来指定。名称能够蕴含以下通配符:
通配符 | 意义 |
---|---|
? | 匹配办法名的任何单个字符 |
* | 匹配办法名的任何局部 |
<n> | 在雷同的选项中匹配第 n 个匹配的通配符 |
类型能够蕴含以下通配符
通配符 | 意义 |
---|---|
% | 匹配任何原始类型(boolean,int 等,不蕴含 void) |
? | 匹配类名中的单个字符 |
* | 匹配类名中的任何局部但不蕴含包分隔符 |
** | 匹配类名中的任何局部但不蕴含包分隔符 |
*** | 匹配任何类型(原始类型或者非原始类型,数组或者非数组) |
— | 匹配任何类型的任意数量的参数 |
<n> | 在雷同的选项中匹配第 n 个匹配的通配符。 |
请留神,?,* 和 ** 通配符永远不会匹配根本类型。
而且,只有 *** 通配符能力匹配任何维度的数组类型。
例如,“** get *()”匹配“java.lang.Object getObject()”,但不匹配“float getFloat()”和“java.lang.Object [] getObjects()”。
- 也能够应用短类名(无包)或应用残缺的类名来指定构造函数。和 Java 语言一样,构造函数标准有一个参数列表,但没有返回类型。
- 类拜访修饰符和类成员拜访修饰符通常用于限度通配类和类成员。它们指定必须为成员设置相应的拜访标记以匹配。后面加 “!” 决定相应的拜访标记应该被勾销设置。
容许组合多个标记(例如,public static)。这意味着必须设置两个拜访标记(例如 public static),除非它们有抵触,在这种状况下,至多必须设置其中一个(例如至多 public 或 protected)。
ProGuard 反对可能由编译器设置的其余修饰符 synthetic,bridge 和 varargs。
参考资料
- https://www.guardsquare.com/e…
- https://www.diycode.cc/topics…
End 微信扫一扫,关注我的公众号