ProGuard基础

2020-09-02  本文已影响0人  qiuxintai

引言

很早就想写篇关于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

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

3. proguard入口点

3.1 入口点

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

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 选项参数和配置文件的说明:

5. proguard选项

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

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

5.2 保留选项(Keep Options)

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


proguard的keep规律.png

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

5.3 压缩选项(Shrinking Options)

5.4 优化选项(Optimization Options)

5.5 混淆选项(Obfuscation Options)

5.6 预检选项(Preverification Options)

5.7 通用选项(General Options)

5.8 类路径(Class Paths)

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

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

每个输出条目可以是:

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

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

如果指定的过滤器少于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接受基于绝对路径和相对路径的文件名和目录名,相对路径解释如下:

名称可以包含以'<'和'>'分隔的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)

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 ... ] *;
    ...
}]

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

上一篇 下一篇

猜你喜欢

热点阅读