Proguard作为一个java的程序,它的入口在ProGuard.main()中
这章节我们讲Proguard的参数解析。实际上Proguard的处理无非分两步,解析参数和处理逻辑。解析参数这一步的主要类是:
ConfigurationParser.java
它的解析结果将放置在:
Configuration.java类中
我们打开Configuration 来看,发现Configuration类如其名,就是记录配置的一个Pojo类,虽然我这么说可能不合理,但是我们确实可以把它当成一个非常简单的类来看。既然我们这一章节的标题叫做参数解析,我们要重点分析的对象自然是:
ConfigurationParser
参数数据源->ConfigurationParser->Configuration对象,在分析ConfigurationParser之前我们先打开
![](https://app.yinxiang.com/shard/s7/res/ff492711-f97e-4cd6-baf8-0be62122b3b5/ActivityDiagram.jpg?resizeSmall&width=1340)
ConfigurationConstants.java这个类,这个类记录了Proguard里面定义的关键字。有些常量用来模式匹配,有些常量是用来提供混淆范围,还有一些是用来配置Proguard的环境参数。这里我们只讲模式匹配。也就是下列这些参数:
public static final char OPEN_SYSTEM_PROPERTY = '<'; public static final char CLOSE_SYSTEM_PROPERTY = '>'; public static final String ANNOTATION_KEYWORD = "@"; public static final String NEGATOR_KEYWORD = "!"; public static final String CLASS_KEYWORD = "class"; public static final String ANY_CLASS_KEYWORD = "*"; public static final String ANY_TYPE_KEYWORD = "***"; public static final String IMPLEMENTS_KEYWORD = "implements"; public static final String EXTENDS_KEYWORD = "extends"; public static final String OPEN_KEYWORD = "{"; public static final String ANY_CLASS_MEMBER_KEYWORD = "*"; public static final String ANY_FIELD_KEYWORD = "<fields>"; public static final String ANY_METHOD_KEYWORD = "<methods>"; public static final String OPEN_ARGUMENTS_KEYWORD = "("; public static final String ARGUMENT_SEPARATOR_KEYWORD = ","; public static final String ANY_ARGUMENTS_KEYWORD = "..."; public static final String CLOSE_ARGUMENTS_KEYWORD = ")"; public static final String SEPARATOR_KEYWORD = ";"; public static final String CLOSE_KEYWORD = "}"; |
Proguard中的取词操作放在WordReader这个类中,为何我要顺带说一下这个类呢?是因为这个类被复用的功能极强。如果你想要写一个词法解析器的话,不妨直接考虑这个东西。WordReader本身是一个抽象类他的实现主要有
LineWordReader 和 ArgumentWordReader ,其中Proguard默认使用的是ArgumentWordReader.但是你如果使用的文件方式来配置proguard的话,那么它使用的是LineWordReader 这个类。我们就使用LineWordReader 这个类。当然我在我的工程下面建了一个目录resources和outs用来保存我的资源配置和输出文件。
我们看到WordReader这个类抽象的非常好,它抽象出了一个模板方法nextLine,用于抽象取行的方式(图1)。我们来看一下它取词的过程,在nextWord(boolean isFileName) 方法中:
![](https://app.yinxiang.com/shard/s7/res/8846bd86-5f38-4533-9295-0bef51a7b8a6.jpg?resizeSmall&width=1340)
[图1]
我们将nextWord函数的源码分成几个部分:
第一:预先编译(过滤掉空白符号和注释)
while (currentLine == null || currentIndex == currentLineLength) { currentLine = nextLine(); lineNumber++; if (currentLine == null) { return null; } currentLineLength = currentLine.length(); // Skip any leading whitespace. currentIndex = 0; while (currentIndex < currentLineLength && Character.isWhitespace(currentLine.charAt(currentIndex))) { currentIndex++; } // Remember any leading comments. if (currentIndex < currentLineLength && isComment(currentLine.charAt(currentIndex))) { // Remember the comments. String comment = currentLine.substring(currentIndex + 1); currentComments = currentComments == null ? comment : currentComments + '\n' + comment; // Skip the comments. currentIndex = currentLineLength; } } |
我们可以看到代码的红色那一部分,实际上对于过滤空格后的第一个字符,如果是"#"的话,那么它直接会将索引currentIndex设置为字符串长度currentLineLength那么也就过滤掉了注释的这一行,而如果到达结尾了~它将会再取得的下一行。然后重新解析,而你所得到的这些注释会拼成一个comment的长字符,用\n分割然后放在currentComments变量中。接下来就是第二步:
取词:
int startIndex = currentIndex; int endIndex; char startChar = currentLine.charAt(startIndex); if (isQuote(startChar)) { // The next word is starting with a quote character. // Skip the opening quote. startIndex++; // The next word is a quoted character string. // Find the closing quote. do { currentIndex++; if (currentIndex == currentLineLength) { currentWord = currentLine.substring(startIndex-1, currentIndex); throw new IOException("Missing closing quote for "+locationDescription()); } } while (currentLine.charAt(currentIndex) != startChar); endIndex = currentIndex++; } elseif (isFileName && !isOption(startChar)) { // The next word is a (possibly optional) file name. // Find the end of the line, the first path separator, the first // option, or the first comment. while (currentIndex < currentLineLength) { char currentCharacter = currentLine.charAt(currentIndex); if (isFileDelimiter(currentCharacter) || ((isOption(currentCharacter) || isComment(currentCharacter)) && Character.isWhitespace(currentLine.charAt(currentIndex-1)))) { break; } currentIndex++; } endIndex = currentIndex; // Trim any trailing whitespace. while (endIndex > startIndex && Character.isWhitespace(currentLine.charAt(endIndex-1))) { endIndex--; } } else if (isDelimiter(startChar)) { // The next word is a single delimiting character. endIndex = ++currentIndex; } else { // The next word is a simple character string. // Find the end of the line, the first delimiter, or the first // white space. while (currentIndex < currentLineLength) { char currentCharacter = currentLine.charAt(currentIndex); if (isDelimiter(currentCharacter) || Character.isWhitespace(currentCharacter) || isComment(currentCharacter)) { break; } currentIndex++; } endIndex = currentIndex; } // Remember and return the parsed word. currentWord = currentLine.substring(startIndex, endIndex); return currentWord; |
红色区域的代码代表引号这种成对出现的符号内的全部字母都作为nextword的一部分,当然包括空格。蓝色部分的代码是为了解决在文件读入的时候的文字匹配。我们以后再提。金黄色部分可以解决对于类似@xxx或者(XXX)这类,不知道大家是否也发现了~其实写成(xxx也是可以的.而对于以文件方式读入的参数将会携带@标识符作为函数的返回值。
--非子墨