Proguard作为一个java的程序,它的入口在ProGuard.main()中
这章节我们讲Proguard的参数解析。实际上Proguard的处理无非分两步,解析参数和处理逻辑。解析参数这一步的主要类是:
ConfigurationParser.java
它的解析结果将放置在:
Configuration.java类中
我们打开Configuration 来看,发现Configuration类如其名,就是记录配置的一个Pojo类,虽然我这么说可能不合理,但是我们确实可以把它当成一个非常简单的类来看。既然我们这一章节的标题叫做参数解析,我们要重点分析的对象自然是:
ConfigurationParser
参数数据源->ConfigurationParser->Configuration对象,在分析ConfigurationParser之前我们先打开
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) 方法中:
[图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++; }
else if (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也是可以的.而对于以文件方式读入的参数将会携带@标识符作为函数的返回值。
--非子墨