美文网首页
ProGuard基础

ProGuard基础

作者: qiuxintai | 来源:发表于2020-09-02 17:29 被阅读0次

    引言

    很早就想写篇关于proguard的文章,但是CSDN、简书、博客园等等网站上面已经有大量关于proguard的文章,并且很多都写得很好。我再写似乎也是重复而已,而且很有可能还没有前辈们写得好。思来想去,我觉得对我来说超不超越前人并不重要,记录自己的知识最重要,由此便有了本文。写着写着发现自己大部分是在翻译英文文档,实在没有什么自己的东西,但实战是要有理论基础的,为了下一篇《ProGuard实战》能更容易理解,我还是决定即使是纯翻译也要把它写完。如果你英语还不错,建议你跳过本文,直接阅读原汁原味的英文文档:$SDK/tools/proguard/docs/manual/usage.html($SDK表示android sdk路径)。
    看完你还有兴趣的话,欢迎再回来看《ProGuard实战》。如果你英语一般般,那建议至少将本文简单过一遍,再看《ProGuard实战》

    1. proguard简介

    proguard是一个能够对Java 代码进行压缩(Shrink),优化(Optimize),混淆(Obfuscate),预检(Preveirfy)的工具。 proguard已集成到Android SDK中,路径为$SDK\tools\proguard,其中包含可执行文件、jar、文档、使用例子及默认的混淆配置文件等等:


    proguard目录结构.png

    简单介绍一下proguard的主要文件:
    (1) bin目录下是可执行文件,包括:
    proguard.bat : 用于对代码进行压缩、优化、混淆、预检的可执行文件。
    retrace.bat : 用于对混淆后的代码出现的异常或者错误日志进行堆栈还原的可执行文件。
    proguardgui.bat: 一个GUI工具,它集成了proguard.bat和retrace.bat,在可视化界面中提供了处理过程的各个步骤的配置项,比在命令行使用更加方便。

    (2) lib目录下是jar文件,与可执行文件相对应也有三个:
    proguard.jar
    retrace.jar
    proguardgui.jar
    (3) docs目录下是使用文档,里面有使用手册,不熟悉时可查看其中的文档。
    (4) exmaples是使用例子,可结合例子学习和理解proguard的使用。
    (5) proguard-android.txt、proguard-android-optimize.txt、proguard-project.txt 是默认的proguard配置文件。

    2. proguard处理流程

    proguard处理流程图如下图所示:


    proguard处理流程.png
    • 压缩(Shrink): 检测并删除未使用的类,字段,方法和属性。
    • 优化(Optimize): 分析并优化方法的字节码。
    • 混淆(Obfuscate): 使用简短的无意义名称例如a,b,c等,重命名类,字段和方法。
    • 预检(Preveirfy): 主要是在Java平台上对处理后的代码进行预检。

    前面三步使代码库占用空间更小,执行更高效,并且更难以逆向工程, 最后的预验证步骤将预验证信息添加到类中,这对于JavaME是必需的,也可以缩短启动时间。这些步骤都是可选的。 例如,proguard可以仅用于列出应用程序中的无效代码,或预验证类文件,以便高效的在Java 使用。

    3. proguard入口点

    3.1 入口点

    为了确定哪些代码必须保留,哪些代码可以丢弃或混淆,我们必须为代码指定一个或多个入口点,入口点通常是含有main方法的类。

    • 在压缩(Shrink)步骤中,从入口点开始递归确定已使用的类和类成员,未使用的类和类成员将被丢弃。
    • 在优化(Optimize)步骤中,进一步优化代码,非入口点的类和方法可能被设为private,static或final的,未使用的参数可能被删除,某些方法可能被设为inlined。
    • 在混淆(Obfuscate)步骤中,重命名不是入口点的类和类成员。在整个过程中将保留入口点,确保混淆后仍可以使用其原名称来访问它们。
    • 预检(Preveirfy)步骤是唯一不必知道入口点的步骤。

    3.2 反射

    值得注意的是,如代码中使用了反射需要特殊处理。例如,Class.forName()构造可以在运行时引用任何类,通常情况下,我们无法预见哪些类的类名必须保留。因此,必须将反射动态创建或调用的类或类成员也指定为入口点。我们必须在proguard配置文件中使用-keep选项来保留它们的类名。但是,proguard会自动检测并处理以下情况:

    Class.forName("SomeClass")
    SomeClass.class
    SomeClass.class.getField("someField")
    SomeClass.class.getDeclaredField("someField")
    SomeClass.class.getMethod("someMethod", new Class[] {})
    SomeClass.class.getMethod("someMethod", new Class[] { A.class })
    SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
    SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
    SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
    SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
    AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
    AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
    AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")
    

    当然,类和类成员的名称可能会有所不同,但实际上类的所有构造方法在字面上是相同的,proguard可以识别它们。被引用的类和类成员在压缩阶段被保留,并且字符串参数在混淆阶段被适当地更新。
    此外,如果出现某些类或类成员需要保留,proguard会提供一些建议。例如,proguard会注意类似“(SomeClass)Class.forName(variable).newInstance()”的构造。这些可能表明该类或接口SomeClass或其实现类可能需要保留。然后,我们可以根据建议相应地调整配置。
    为了能够正确的混淆,我们需要对正在处理的代码有所了解。混淆大量使用反射的代码可能会出现错误,需要反复试验,尤其是在没有关于代码内部的必要信息的情况下。

    4. proguard的使用

    要使用proguard,只需在命令行键入:
    java -jar proguard.jar options ...

    第1节中我们提到过proguard.jar,它的路径为$SDK/tools/proguard/lib/proguard.jar。当然我们也可以选择使用$SDK/tools/proguard/bin/proguard.bat,用文本编辑器打开proguard.bat可以看到它只是一个脚本,实际上还是用java指令来执行的。通常,我们会将proguard的大多数选项都放在配置文件中,然后运行proguard,例如配置文件为myconfig.pro:
    java -jar proguard.jar @myconfig.pro

    我们还可以组合命令行选项和配置文件中的选项。例如:
    java -jar proguard.jar @myconfig.pro -verbose

    proguard 选项参数和配置文件的说明:

    • proguard配置文件中可以添加注释,注释以#字符开始,一直持续到该行的末尾。
    • 单词和限定符之间的多余空格将被忽略。带有空格或特殊字符的文件名应使用单引号或双引号引起来。
    • 选项可以在命令行中的参数和配置文件中的行中任意分组,这意味着可以引用命令行选项的任意部分,例如,以避免特殊字符的shell扩展。
    • 选项通常是与顺序无关的。为了使用方便,可以将其缩写为第一个唯一字符。

    5. proguard选项

    参考:$SDK/tools/proguard/docs/manual/usage.html

    5.1 输入/输出选项(Input/Output Options)

    • -include filename,使用时也可以缩写成@filename,从给定的文件中递归读取配置选项。

    • -basedirectory directoryname,为配置参数或此配置文件中的相对文件名指定基本目录。

    • -injars class_path,指定输入jar的路径(或war,ears,zip或目录)。这些jar中的类文件将被处理并写入输出jar。默认情况下,所有非类文件都将被复制而不会更改。直接从目录中读取输入文件时,可以过滤掉临时文件的路径(例如由IDE创建的临时文件),在过滤器小节会进一步解释。为了提高可读性,可以使用多个-injars选项指定类路径条目。

    • -outjars class_path, 指定输出jar的路径(或wars,ears,zips或目录)。-injars选项指定的jar处理后将被写入-outjars指定的jar。我们可以将一组输入jar的内容处理后输出到对应的一组输出jar中。与-injars选项一样,可以过滤输出条目,在过滤器小节会进一步解释。然后,将每个已处理的类文件或资源文件通过匹配的过滤器写入输出jar组中的第一个输出条目。我们必须避免让输出文件覆盖任何输入文件。为了提高可读性,可以使用多个-outjars选项指定类路径条目。如果没有任何-outjars选项,则不会写入任何jar。

    • -libraryjars class_path, 指定依库jar(或war,ears,zip或目录)。依赖库jar中的文件将不会包含进outjars中。尽管依赖库jar的类的存在可以改善优化步骤的结果,但是它们只需要被调用,不需要存在于outjars中。可以过滤整个依赖库jar的路径,在过滤器小节会进一步解释。为了提高可读性,可以使用多个-libraryjars选项指定类路径条目。请注意,在查找库类时,proguard不考虑boot path和class path,所以必须明确指定代码将使用的运行时jar。

    • -skipnonpubliclibraryclasses,指定在读取依赖库jar时跳过非公共类,以加快处理速度并减少proguard的内存使用量。默认情况下,proguard会读取非公共和公共库类。非公用类通常不相关,不影响输入jar中的实际程序代码,忽略它们可以加快proguard的速度,而不会影响输出。某些库中可能包含了由公共库类扩展的非公共库类,这种情况我们就不能使用此选项。如果由于设置了此选项而出现can't find classes错误,则proguard将打印警告。

    • -dontskipnonpubliclibraryclasses, 指定不跳过非公共库类。从4.5版开始,这是默认设置。Android SDK中的是4.7,因此这个是默认设置。

    • -dontskipnonpubliclibraryclassmembers, 指定不跳过包可见的库类成员(字段和方法)。默认情况下,proguard在解析库类时会跳过包可见的库类成员。当我们确实引用了包可见的类成员时,需要设置此项。

    • -keepdirectories [directory_filter], 指定要保留在输出jar中的目录(或wars,ears或directory)。默认情况下,目录条目被删除。这样可以减小jar的大小,但是如果程序代码尝试使用“ MyClass.class.getResource(“”)“”之类的结构来查找它们,则可能会找不到的。如果指定的选项没有过滤器,则保留所有目录。使用过滤器时,仅保留匹配的目录。

    • -target version,指定要在已处理的类文件中设置的版本号。默认情况下,类文件的版本号保持不变。例如,版本号可以是1.0、1.1、1.2、1.3、1.4、1.5(或仅5),1.6(或仅6)或1.7(或仅7)之一。某些情况下,可能通过更改类文件的版本号并对其进行预先验证。

    • -forceprocessing, 强制重新处理,即使输出看起来是最新的也是如此。最新的检测机制是比较指定输入,输出和配置文件或目录的日期戳。

    5.2 保留选项(Keep Options)

    • keep [,modifier,...] class_specification,指定要保留的类和类成员(字段和方法)。例如,为了保留应用程序,我们需要保留包含main方法的主类。为了处理库,我们应该保留所有可公开访问的元素。

    • -keepclassmembers [,modifier,...] class_specification,指定要保留的类成员(如果它们的类也被保留)。例如,我们可能想要保留所有实现Serializable接口的类的序列化字段和方法。

    • -keepclasseswithmembers [,modifier,...] class_specification,指定保留满足条件的类和类成员。例如,我们可能希望保留所有具有main方法的类,但是又不想一一列出它们。

    • -keepnames class_specification,-keep,allowshrinking class_specification的缩写,指定要保留名称的类和类成员。例如,我们可能希望保留实现Serializable接口的类的所有类名,以便处理后的代码与任何原始序列化的类保持兼容。注意,这个选项仅在混淆时适用,因此仅对压缩阶段未删除的类和类成员有效,完全未使用的类在压缩阶段仍可以删除。

    • -keepclassmembernames class_specification,-keepclassmembers,allowshrinking class_specification的缩写。指定要保留名称的类成员。同样的,这个选项仅在混淆时适用。

    • -keepclasseswithmembernames class_specification, -keepclasseswithmembers,allowshrinking class_specification的缩写。指定保留满足条件的类和类成员的名称。例如,我们可能希望保留所有native方法名称及其类的名称,以便处理后的代码仍可以与so代码链接。如果一个类文件被使用了,但是它不包含native方法,则其名称仍将被混淆。同样的,这个选项仅在混淆时适用。

    • -printseeds [filename],详细列出与各种-keep选项匹配的类和类成员,将列表打印到标准输出或给定文件。该列表对于验证是否确实找到了预期的类成员很有用,尤其是在使用通配符的情况下。例如,您可能要列出所有应用程序或保留的所有小程序。

    -keep可以用于压缩和混淆,各种-keep选项可能看起来有些混乱,但是实际上它们背后有一个模式。 下表总结了它们之间的关系:


    proguard的keep规律.png

    如果不确定所需要的选项,则应该只使用-keep。 这样可以保证在压缩步骤中不删除指定的类和类成员,并且在混淆步骤中不将其重命名。
    注意:
    (1)指定类而不指定该类的成员只会将类保留为入口点,它的类成员仍然可以被删除,优化或混淆。
    (2)指定类成员仅将类成员保留为入口点,相关的代码仍可以被优化和调整。

    5.3 压缩选项(Shrinking Options)

    • -dontshrink,指定不压缩的类文件,默认压缩。除各种-keep选项列出的类以及它们直接或间接依赖的类之外,所有类和类成员都将被删除。在每个优化步骤之后也会压缩,因为某些优化后可能会有更多类和类成员可以被删除。

    • -printusage [filename],列出未使用的代码。该列表将打印到标准输出或给定文件。例如,您可以列出应用程序的未使用代码。仅在压缩时适用。

    • -whyareyoukeeping class_specification, 打印为什么在压缩步骤中保留给定类和类成员的详细信息。对于每个指定的类和类成员,此选项将最短的方法链打印到指定的种子或入口点。打印出的最短链有时可能包含循环扣除,这些未反映实际的压缩过程。如果指定了-verbose选项,则跟踪将包括完整的字段和方法签名。仅在压缩时适用。如果我们想知道为什么输出中存在某些给定的元素,这可能会很有用。

    5.4 优化选项(Optimization Options)

    • -dontoptimize,指定不优化的类文件。默认启用优化。所有方法都在字节码级别进行了优化。

    • -optimizations Optimization_filter, 在更细粒度的级别上指定要启用和禁用的优化。仅在优化时适用。这是一个专家选项。

    • -optimizationpasses n,指定要执行的优化遍数,n的取值范围为0-7,默认为1,Android项目一般设置为5。多次优化可能会导致进一步的改进。如果在优化之后未发现任何改进,则优化结束。仅在优化时适用。

    • -assumenosideeffects class_specification,指定删除没有任何影响的方法,比如没有返回值的且不影响执行结果的方法、有返回值但未使用其返回值且不影响执行结果的方法。例如,我们可以指定方法Log.v(),用来优化无用的Log。请注意,该选项将作用于整个jar,很容易破坏原有代码,应当尽量少用。最好的做法是保持一个良好的编码习惯,冗余代码是在写的时候就把它删掉。仅在优化时适用。

    • -allowaccessmodification,指定在处理过程中可以扩大类和类成员的访问修饰符。这可以改善优化步骤的结果。例如,当有一个public getter时,可能也必须将访问的字段改成public。大多数情况下,不应该使用此选项,使用后可能导致在API中设计为不公开的类和类成员被公开。

    • -mergeinterfacesaggressively,指定侵略性的合并接口,即使接口的实现类未实现所有接口方法,也可以合并接口。这样可以通过减少类的总数来减小输出的大小。同样会破坏原有代码,如不清楚其影响,应当不使用该选项。仅在优化时适用。

    5.5 混淆选项(Obfuscation Options)

    • -dontobfuscate,指定不混淆的类文件,默认全部混淆。除各种-keep选项列出的类和类成员外,其它类和类成员将会被随机重命名成简短、无意义的名称,并且会删除对调试有用的内部属性,例如源文件名、变量名、行号等。

    • -printmapping [filename],指定映射文件。重命名后,重命名后的类和类成员与重命名前的类和类成员有一个映射关系,此选项会打印这个映射关系,输出到标准输出或指定文件。例如,混淆后应用程序运行遇到了异常,我们就需要这个映射关系文件来还原异常的堆栈信息,以便定位源代码中出错的位置。仅在混淆时适用。

    • -applymapping filename,指定重复使用先前混淆生成的映射文件。映射文件中列出的类和类成员将使用原有名称。映射文件中没有的类和类成员,即新增的类和类成员将被重命名。映射可以引用输入类以及库类。此选项在增量混淆时很有用,在这种情况下,还可以考虑使用-useuniqueclassmembernames选项只允许一个映射文件。仅在混淆时适用。

    • -obfuscationdictionary filename,指定一个混淆字典。默认情况下,短名称(如“ a”,“ b”等)用作混淆后的字段和方法的名称。指定混淆字典后,混淆字典是一个文本文件,其中所有有效单词都将被用作混淆的字段和方法的名称,但#符号后的空格,标点符号,重复的单词和注释将被忽略。我们可以在混淆字典中的输入一系列保留关键字、外文字符的标识符。注意,混淆字典几乎不能改善混淆。因为文本编译器可以很容易的自动替换它们,并且使用混淆字典混淆后,还可以使用更简单的名称再次进行混淆。通常,最有用的使用场景是在混淆字典中指定类中已经存在的字符串(例如“Code”)作为保留关键字,这样可以将类文件的大小减小一点。仅在混淆时适用。

    • -classobfuscationdictionary filename,指定一个文本文件,所有有效单词都用作混淆的类名。作用类似obfuscationdictionary选项。仅在混淆时适用。

    • -packageobfuscationdictionary filename,指定一个文本文件,所有有效单词都用作混淆的包名。作用类似obfuscationdictionary选项。仅在混淆时适用。

    • -overloadaggressively,指定在混淆时应用侵略性的重载。多个字段和方法只要它们的参数和返回类型不同(不仅是参数),就可以使用相同的名称。此选项可以使处理后的代码更小、更难理解。此选项在某些版本可能会出现异常,例如,Google的Dalvik VM无法处理重载的静态字段。如不清楚其影响,应当不使用该选项,除非明白处理这种异常。仅在混淆时适用。

    • -useuniqueclassmembernames,指定将相同的混淆名称分配给具有相同名称的类成员,将不同的混淆名称分配给具有不同名称的类成员。如果没有该选项,则可以将更多的类成员映射到相同的短名称,如“ a”,“ b”等。因此,该选项会稍微增加结果代码的大小,但可以确保保存的混淆名称映射始终是在后续的渐进混淆步骤中能够识别。此选项仅在混淆时适用。

    • -dontusemixedcaseclassnames,指定在混淆时不生成大小写混合的类名。默认情况下,混淆的类名可以包含大写字符和小写字符的混合。这将输出完全可接受且可用的jars。但仅当在不区分大小写的文件系统(例如Windows)的平台上解压缩jar时,解压工具才可能会使名称相似的类文件相互覆盖,这样解压缩后相当于自己损坏了代码。想在Windows上解压缩jar的开发人员可以使用此选项关闭此行为。请注意,混淆的jar将因此变大。仅在混淆时适用。

    • -keeppackagenames [package_filter],指定不混淆指定的包名。package_filter是一系列由逗号分隔的包名。包名可以包含?,*和**通配符,并且可以在其前面加上!否定限定符。仅在混淆时适用。

    • -flattenpackagehierarchy [package_name],指定父包名,将重命名后的所有包移动到指定父包下重新封包。不带参数或带空字符串('')的包将被移入根目录。此选项可以进一步混淆包名。它可以使处理后的代码更小,更难理解。仅在混淆时适用。

    • -repackageclasses [package_name],指定通过将所有重命名的类文件移动到单个给定的包中来重新打包它们。不带参数或带空字符串('')的,整个包将被完全删除。该选项将覆盖-flattenpackagehierarchy选项。这是进一步混淆软件包名称的另一个示例。它可以使处理后的代码更小,更难以理解。它不建议使用的名称是-defaultpackage。仅在混淆时适用。如果将类移动到其他位置,则在其包目录中查找资源文件的类将无法正常工作。有疑问的情况下,请不要使用此选项,以保持包装原封不动。

    • -keepattributes [attribute_filter],指定要保留的所有可选属性。可以使用一个或多个-keepattributes指令指定属性。attribute_filter是一系列由逗号分隔的属性名称。同样的,属性名称可以包含?,*和**通配符,并且可以在其前面加上!否定限定符。典型的可选属性有:Exceptions,Signature,Deprecated,SourceFile,SourceDir,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Synthetic,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault。还可以将内部类的属性名称作为源文件的一部分,指定定内部类的属性名称。例如,在处理库时,至少应保留Exceptions,InnerClasses和Signature属性。还应该保留SourceFile和LineNumberTable属性,以产生有用的混淆堆栈跟踪。最后,如果您的代码依赖注解,则可能需要保留注解。仅在混淆时适用。

    • -keepparameternames,指定保留参数名称和类型的方法。此选项实际上保留了精简版本的调试属性LocalVariableTable和LocalVariableTypeTable。在处理库时,它很有用。某些IDE可以使用这些信息来帮助使用该库的开发人员,例如提供工具提示或自动完成功能。仅在混淆时适用。

    • -renamesourcefileattribute [string],指定要放入类文件的SourceFile属性(和SourceDir属性)中的常量字符串。请注意,必须首先保留该属性,因此还必须使用-keepattributes指令显式地保留该属性。例如,您可能希望处理后的库和应用程序生成有用的混淆堆栈跟踪。仅在混淆时适用。

    • -adaptclassstrings [class_filter],指定与类名相对应的字符串常量也应该被混淆。如果没有过滤器,则将修改所有与类名称相对应的字符串常量。使用过滤器时,仅匹配与过滤器匹配的类中的字符串常量。例如,如果您的代码包含大量引用类的硬编码字符串,并且您不想保留其名称,则可能要使用此选项。主要适用于混淆时,但相应的类也会在压缩步骤中自动保留。

    • -adaptresourcefilenames [file_filter],根据相应类文件的混淆名称指定要重命名的资源文件。如果没有过滤器,则将与类文件对应的所有资源文件重命名。使用过滤器,仅重命名匹配的文件。仅在混淆时适用。

    • -adaptresourcefilecontents [file_filter],指定要更新其内容的资源文件。资源文件中提到的任何类名都将根据相应类的混淆名进行重命名。没有过滤器,所有资源文件的内容都会更新。使用过滤器,仅更新匹配的文件。使用平台的默认字符集来解析和写入资源文件。您可以通过设置环境变量LANG或Java系统属性file.encoding来更改此默认字符集。

    5.6 预检选项(Preverification Options)

    • -dontpreverify,指定不预先验证已处理的类文件。 默认情况下,如果目标版本是Java Micro Edition或Java 6或更高版本,类文件将进行预验证。 对于Java Micro Edition,需要进行预验证,因此,如果指定此选项,则需要在已处理的代码上运行外部预验证器。 对于Java 6,尚不需要进行预验证,但可以提高Java虚拟机中类加载的效率。android项目中一般会配置此选项。

    • -microedition,指定已处理的类文件针对Java Micro Edition。 然后,预验证器将添加适当的StackMap属性,该属性与Java Standard Edition的默认StackMapTable属性不同。 例如,如果您正在处理Midlet,则将需要此选项。

    5.7 通用选项(General Options)

    • -verbose,指定在处理期间写出更多信息。如果程序因异常终止,则此选项将打印出整个堆栈跟踪,而不仅仅是异常消息。

    • -dontnote [class_filter],指定不打印有关配置中潜在的错误或遗漏,例如类名中的错字或缺少可能有用的选项。可选过滤器是一个正则表达式。设置后,proguard不会打印匹配有关名称的类的注释。

    • -dontwarn [class_filter],指定不警告尚未解决的引用和其他重要问题。可选过滤器是一个正则表达式; proguard不会打印匹配有关名称的类的警告。忽视警告可能很危险,如果未处理的类或类成员确实需要进行处理,则处理后的代码将无法正常运行。仅当我们知道没有风险时才使用此选项,例如,android的默认混淆配置中忽略了support包或androidx包中所有类的警告,因为support包或androidx包一般不会出什么问题。

    • -ignorewarnings,指定打印有关未解决的引用和其他重要问题的任何警告,但在任何情况下都将继续处理。

    • -printconfiguration [filename],指定打印已解析的整个配置,包括所包含的文件和替换的变量。结构被打印到标准输出或给定的文件。有时这对于调试配置或将XML配置转换为更具可读性的格式很有用。

    • -dump [filename],指定在进行任何处理后输出该类文件的内部结构。结构被打印到标准输出或给定的文件。例如,您可能想要输出给定jar文件的内容,但不想进行任何处理。

    5.8 类路径(Class Paths)

    proguard接受通用化的类路径来指定输入文件和输出文件。类路径由许多条目组成,这些条目由传统的路径分隔符分隔(例如,在Unix上为“:”,在Windows平台上为“;”)。在重复的情况下,按条目的顺序决定优先级。
    每个输入条目可以是:

    • 一个类文件或资源文件,
    • 包含以上任何内容的jar文件,
    • 包含以上任何内容的war文件,
    • 包含以上任何内容的ear文件,
    • 包含以上任何内容的zip文件,
    • 包含以上任何内容的目录。

    直接指定的多个类文件和资源文件的路径将被忽略,因此多个类文件通常应为jar文件,war文件,ear文件,zip文件或目录的一部分。此外,在归档或目录内的类文件的路径不应有任何其他目录前缀。

    每个输出条目可以是:

    • 一个jar文件,其中将包含了所有已处理的类文件和资源文件。
    • 一个war文件,包含了以上所有内容,
    • 一个ear文件,包含了以上所有内容,
    • 一个zip文件,包含了以上所有内容,
    • 一个目录,包含了以上所有内容。

    指定输出条目后,proguard通常会以合理的方式打包结果,并根据需要重新构造输入条目。一般是直接将所有输入内容写入输出目录,输出目录将包含输入条目的完整重构。但是打包过程是可以自定义的,我们也可以将文档也打包到输出目录,重新生成zip文件,如果有此需要,请参考使用手册的restructure output archives.一节。

    此外,proguard还可以根据其完整的相对文件名来过滤类路径及其内容。每个类路径后面都可以跟随多达5种类型的文件过滤器,过滤器以括号包涵,不同类型的过滤器以分号分隔,相同类型的过滤器以逗号分隔:

    • zip名称过滤器,
    • ear名称过滤器,
    • war名称过滤器,
    • jar名称过滤器,
    • 类文件名和资源文件名的过滤器。

    如果指定的过滤器少于5个,则假定它们是后者。任何空的过滤器都将被忽略。类路径的过滤格式一般如下所示(方括号“ []”表示其内容是可选的):

    classpathentry([[[[zipfilter;]earfilter;]warfilter;]jarfilter;]filefilter)
    

    例如,“ rt.jar(java/**.class,javax/**.class)”匹配rt jar内java和javax目录中的所有类文件。
    例如,“ input.jar(!**.gif,images/**)”匹配input.jar内images目录中的所有文件,但gif文件除外。
    注意,不同的过滤器将应用于所有相应的文件类型,而不管其在输入中的嵌套级别如何; 它们是正交的。
    例如,“ input.war(lib/**.jar,support/**.jar; **.class,**.gif)”仅考虑input.war中lib和support目录中的jar文件,而不考虑 其他jar文件。 然后,它将匹配所有遇到的类文件和gif文件。

    5.9 文件名(File Names)

    proguard接受基于绝对路径和相对路径的文件名和目录名,相对路径解释如下:

    • 如果设置了基准目录,则相对于基本目录(前面提到的basedirectory选项),否则
    • 如果指定了配置文件的位置,则相对于指定配置文件的位置,否则
    • 相对于工作目录。

    名称可以包含以'<'和'>'分隔的Java系统属性。系统属性将自动替换为其各自的值。例如,<java.home>/lib/rt.jar将自动扩展为/usr/local/java/jdk/jre/lib/rt.jar。同样,<user.home>将扩展到用户的主目录,而<user.dir>将扩展到当前的工作目录。

    带有特殊字符(如空格和括号)的名称必须用单引号或双引号引起来。请注意,名称列表中的每个文件名都必须单独引用。另请注意,在命令行上使用引号本身时可能需要转义。例如,在命令行上,可以使用 '-injars "my program.jar":"/your directory/your program.jar"'.之类的选项。

    5.10 过滤器(Filters)

    为了配置的处理过程中的诸多方面,proguard提供了许多带有过滤器的选项:文件名,目录名,类名,包名,属性名,优化名等。而过滤器是可以包含通配符的逗号分隔名称的列表。 只有与列表中的项目匹配的名称才能通过过滤器,输入文件中只有具有匹配文件名的文件才被读取;输出文件中只有具有匹配文件名的文件才被被写入。 支持的通配符取决于使用过滤器的名称的类型,但是以下通配符是通用的:


    proguard通配符.png

    5.11 保留选项修饰符(Keep Option Modifiers)

    • allowshrinking,允许压缩,指定-keep选项中指定的入口点可以压缩,即使必须保留这些入口点也可以。 也就是说,可以在压缩步骤中移除入口点,但是如果确有必要,可以不对其进行优化或混淆。
    • allowoptimization,允许优化,指定可以优化-keep选项中指定的入口点,即使必须保留这些入口点也是如此。 也就是说,可以在优化步骤中更改入口点,但不能将其删除或混淆。 该修饰符仅对实现不常用要求有用。
    • allowobfuscation,允许混淆,指定-keep选项中指定的入口点可能会被混淆,即使必须保留它们也是如此。 也就是说,入口点可以在混淆步骤中重命名,但是它们可能不会被删除或优化。 该修饰符仅对达到不常用要求有用。

    5.12 类规范(Class Specifications)

    类规范是类和类成员(字段和方法)的模板。 它在各种-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关键字将匹配限制为枚举类。在接口或枚举关键字之前加!将匹配分别限制为不是接口或枚举的类。

    • 每个类别名称必须完整的类名,例如java.lang.String。但类名可以使用通配符“?”,“*”, “**”、否定限定符 “!”。

    • extends和implements 通常用于使用通配符限制类。一个用于类,一个用于接口,可以看做是等效的,只能匹配扩展或实现了指定类的类,即某个类或接口的子类。请注意,匹配的类不包括该类本身。如果需要,应在单独的选项中指定该类。

    • @ specifications可用于将类和类成员限制为使用指定注释类型进行注释的成员。指定注释类型就像类名一样。

    • 字段和方法的指定与Java中的指定非常相似,除了方法参数列表不包含参数名称(就像在Javadoc和javap等其他工具中一样)。规范还可以包含以下所有通配符:
      (1)<init>匹配任何构造函数。
      (2)<fields>匹配任何字段。
      (3)<methods>匹配任何方法。
      (4)* 匹配任何字段或方法。
      请注意,上述通配符没有返回类型。仅<init>通配符具有参数列表。
      字段和方法也可以使用正则表达式指定。名称可以包含以下通配符:
      (1)?匹配方法名称中的任何单个字符。
      (2)* 匹配方法名称的任何部分。
      描述符中的类型可以包含以下通配符:
      (1)% 匹配任何原始类型(“ boolean”,“ int”等,但不匹配“ void”)。
      (2)? 匹配类名称中的任何单个字符。
      (3)* 与不包含包分隔符的类名的任何部分匹配。
      (4)** 匹配类名的任何部分,可能包含任意数量的包分隔符。
      (5)*** 匹配任何类型(基本类型或非基本类型,数组或非数组)。
      (6)... 匹配任何类型的任意数量的参数。
      请注意,?,*和**通配符将永远与基本类型不匹配。此外,仅***通配符将匹配任何维度的数组类型。例如,“ ** get *()”匹配“ java.lang.Object getObject()”,但不匹配“ float getFloat()”,也不匹配“ java.lang.Object [] getObjects()”。

    • 也可以使用其简短的类名(不带包名)或使用其完整的类名来指定构造函数。与Java语言一样,构造函数规范具有参数列表,但没有返回类型。

    • 类和类成员的访问修饰符通常用于限制通配类和类成员。只有设置了相同的访问修饰符才能匹配。前面的!代表不能设置该访问修饰符。允许组合多个修饰符(例如public static)。这意味着必须设置两个访问修饰符(例如public和static),除非它们发生冲突,否则必须至少设置其中一个(例如public或protected)。

    最后,本文翻译内容居多,部分内容翻译之后重新组织了语言进行描述,可能有不正确或者不准确的地方,欢迎交流、指正。

    相关文章

      网友评论

          本文标题:ProGuard基础

          本文链接:https://www.haomeiwen.com/subject/dqdwsktx.html