Proguard源码分析(六)前文总结
目前,我们读了Proguard的代码,相信已经开始对访问者模式和装饰器模式有很深的理解,
现在我们再带着这两个模式认真的回顾下代码,因为只有这样,我们才能更好的进行下面的代码阅读。但是如果你还带着疑问,不妨看下前面的章节,或者看一些有关设计模式的书体会一下。
我们回到我们熟悉的入口Proguard类的execute方法中:
第一部分:读取(readinput)InputReader.execute:
ClassFilter filter = new ClassFilter(
new ClassReader(false,configuration.skipNonPublicLibraryClasses, //false
configuration.skipNonPublicLibraryClassMembers, //true
warningPrinter,
new ClassPresenceFilter(
programClassPool,
duplicateClassPrinter,
new ClassPoolFiller(programClassPool)))
);
readInput("Reading program ",
configuration.programJars,
filter);
ClassFilter 我们可以用一个简单的类调用关系来表示:
filter = ClassFilter->ClassReader->ClassPresenceFilter->ClassPoolFiller
这是设计模式中的装饰器.
ClassFilter 继承于 FilteredDataEntryReader 并直接使用它的方法,只不过指定了它的匹配模式:
针对.class 文件.
ClassFilter 的作用是如果是.class文件的话就执行 ClassReader 操作,但是我们知道,文件读入的时候未必是class文件,也有可能是jar或者war或者文件夹.
那么它是怎么做的呢?这其实牵扯到它又是如何读入的呢?
Proguard中读入的过程调用
private void readInput(String messagePrefix,
ClassPathEntry classPathEntry,
DataEntryReader dataEntryReader)
其中dataEntryReader 参数就是上面的Filter.前文我们说过,对于不同的输入源,会采用不同的reader来读取,我们读入的既然是个jar,我们就会生成一个
JarReader来读取这个源,来看下jarreader是如何处理读取的:
public void read(DataEntry dataEntry) throws IOException
{
ZipInputStream zipInputStream = new ZipInputStream(dataEntry.getInputStream());
try
{
// Get all entries from the input jar.
while (true)
{
// Can we get another entry?
ZipEntry zipEntry = zipInputStream.getNextEntry();
if (zipEntry == null)
{
break;
}
// Delegate the actual reading to the data entry reader.
dataEntryReader.read(new ZipDataEntry(dataEntry,
zipEntry,
zipInputStream));
}
}
finally
{
dataEntry.closeInputStream();
}
}
ZipEntry就是以class为后缀的字节码文件,那么我们回到刚才,上面描述的这些通过ClassFilter的验证。那么就到了装饰器的第二层,
ClassReader.
ClassReader的目的就是为了读取,我们可以从它的构造器中看出:它是最基础的DataEntryReader,也就是说不包装任何的DataEntryReader。
在它的read方法中,它读入数据,专程不同的Clazz内部结构,通过不同的数据访问操作来访问它,当然不以装饰的方法来访问它,而是通过类似代理的结构来访问数据。
ClassReader 的最后一参数是一个classVisitor 我们跟到上面的实现类是一个装饰器:ClassPresenceFilter->ClassPoolFiller
我们说过ClassPresenceFilter的目的是为了去除重复.从
new ClassPresenceFilter(
programClassPool,
duplicateClassPrinter,
new ClassPoolFiller(programClassPool))
可以看出ClassPresenceFilter 的目的是为了去除programClassPool 中的重复数据,如果有重复的class将通过duplicateClassPrinter 打印出来
。假如我们现在没有重复的class ,通过ClassPresenceFilter 的过滤,它传递给了ClassPoolFiller,它将这个字节码加入到代码池中。
好了,到这里我们的读入目的已经达到~接下來我们进入下一步:初始化;
===============================================================
===============================================================
===============================================================
第二部分:初始化 (initialize) Initializer.execute
初始化的过程第一步先会调用:
programClassPool.classesAccept(
new ClassSuperHierarchyInitializer(programClassPool,
libraryClassPool,
classReferenceWarningPrinter,
null));
ClassSuperHierarchyInitializer 类很纯粹,没有包装任何东西,所以看起来应该不费劲。
我们来看一下:
public void visitProgramClass(ProgramClass programClass)
{
// Link to the super class.
programClass.superClassConstantAccept(this);
// Link to the interfaces.
programClass.interfaceConstantsAccept(this);
}
它实际上是关于常量的访问.
public void visitClassConstant(Clazz clazz, ClassConstant classConstant)
{
classConstant.referencedClass =
findClass(clazz.getName(), classConstant.getName(clazz));
}
目的很明显,是为了给常量设置它的引用类。
private Clazz findClass(String referencingClassName, String name)
{
// First look for the class in the program class pool.
Clazz clazz = programClassPool.getClass(name);
// Otherwise look for the class in the library class pool.
if (clazz == null)
{
clazz = libraryClassPool.getClass(name);
if (clazz == null &&
missingWarningPrinter != null)
{
// We didn't find the superclass or interface. Print a warning.
missingWarningPrinter.print(referencingClassName,
name,
"Warning: " +
ClassUtil.externalClassName(referencingClassName) +
": can't find superclass or interface " +
ClassUtil.externalClassName(name));
}
}
else if (dependencyWarningPrinter != null)
{
// The superclass or interface was found in the program class pool.
// Print a warning.
dependencyWarningPrinter.print(referencingClassName,
name,
"Warning: library class " +
ClassUtil.externalClassName(referencingClassName) +
" extends or implements program class " +
ClassUtil.externalClassName(name));
}
return clazz;
}
我们看到,它会找到这个类并返回给你。当然有人可能会问,为什么常量的引用关系要在这里被赋予呢?为什么不再Clazz文件读入的时候就进行初始化呢?~这个说实话我也不得而知,
希望能在后面的代码中得到答案。
好了常量初始化完成之后调用
programClassPool.classesAccept(
new ClassReferenceInitializer(programClassPool,
libraryClassPool,
classReferenceWarningPrinter,
programMemberReferenceWarningPrinter,
libraryMemberReferenceWarningPrinter,
null));
这应该是成员的初始化,或者说引用类型的初始化:
public void visitProgramClass(ProgramClass programClass)
{
programClass.constantPoolEntriesAccept(this);
programClass.fieldsAccept(this);
programClass.methodsAccept(this);
programClass.attributesAccept(this);
}
这里我们只说一个访问方式programClass.attributesAccept(this);
就行那些算做Attribute呢?我推荐一个文章,http://1025250620.iteye.com/admin/blogs/1971213 先了解下class的文件结构。
class文件的属性包含有code属性和文件表述属性:
class文件自带Source属性用来标记文件名,code属性里面可以附带局部变量表,linenumber表,或者异常表。
===============================================================
===============================================================
===============================================================
第三部分:打印seed (printSeeds) SeedPrinter.write
在打印之前:
programClassPool.classesAccept(new ClassCleaner());
libraryClassPool.classesAccept(new ClassCleaner());
先清除在libraryClassPool 和 programClassPool 中的标记。
SimpleClassPrinter printer = new SimpleClassPrinter(false, ps); 是一个简单的格式化输出流。
然后调用
programClassPool.classesAcceptAlphabetically(new MultiClassVisitor(
new ClassVisitor[]
{
new KeptClassFilter(printer),
new AllMemberVisitor(new KeptMemberFilter(printer))
}));
MultiClassVisitor 是一个装饰,用来迭代平级的KeptClassFilter ,AllMemberVisitor
可以看出,最后打印的结构一定是先打印KeptClassFilter 也就是类,后打印成员 AllMemberVisitor
我们看一下 KeptClassFilter 的过滤条件
public void visitProgramClass(ProgramClass programClass)
{
if (KeepMarker.isKept(programClass))
{
classVisitor.visitProgramClass(programClass);
}
}
是通过类是否被标记作为判断条件。
AllMemberVisitor 是表示用所有的成员也就是属性和方法访问,过滤条件是:
KeptMemberFilter
也就是方法和属性是否被标记。好的,我们最本质的问题就回归到如何标记。
KeepMarker keepMarker = new KeepMarker();
ClassPoolVisitor classPoolvisitor =
ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
keepMarker,
keepMarker,
true,
true,
true);
// Mark the seeds.
programClassPool.accept(classPoolvisitor);
libraryClassPool.accept(classPoolvisitor);
如果你已经了解了大概的流程应该知道是通过classPoolvisitor 这个对象来标记的:
我们看一下ClassSpecificationVisitorFactory。createClassPoolVisitor 方法
public static ClassPoolVisitor createClassPoolVisitor(List keepClassSpecifications,
ClassVisitor classVisitor,
MemberVisitor memberVisitor,
boolean shrinking,
boolean optimizing,
boolean obfuscating)
{
MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor();
if (keepClassSpecifications != null)
{
for (int index = 0; index < keepClassSpecifications.size(); index++)
{
KeepClassSpecification keepClassSpecification =
(KeepClassSpecification)keepClassSpecifications.get(index);
if ((shrinking && !keepClassSpecification.allowShrinking)
||(optimizing && !keepClassSpecification.allowOptimization)
||(obfuscating && !keepClassSpecification.allowObfuscation))
{
ClassPoolVisitor classPoolVisitor = createClassPoolVisitor(keepClassSpecification,
classVisitor,
memberVisitor);
multiClassPoolVisitor.addClassPoolVisitor(classPoolVisitor);
}
}
}
return multiClassPoolVisitor;
}
它会针对不同的keep条件来生成不同的 ClassPoolVisitor ,这里我们的
ClassVisitor classVisitor,MemberVisitor memberVisitor
都是KeepMarker。
跟到最后我们跟到返回的ClassPoolVisitor的实现类是:
(ClassPoolVisitor)new NamedClassVisitor(composedClassVisitor, className) :
(ClassPoolVisitor)new AllClassVisitor(composedClassVisitor);
读过我的keep那个章节的应该知道如果你使用的是通用符号*,那么返回的就是AllClassVisitor ,否则就是 NamedClassVisitor
不论是那一种最后调用的都是:composedClassVisitor
它会将ClassVisitor classVisitor, MemberVisitor memberVisitor 组合起来
接下来,如果你是通用符号的话,也就是说:
if (className != null &&
(extendsAnnotationType != null ||
extendsClassName != null ||
containsWildCards(className)))
{
composedClassVisitor =
new ClassNameFilter(className, composedClassVisitor);
// We'll have to visit all classes now.
className = null;
}
则会将composedClassVisitor 包装个className的过滤器.
最后返回AllClassVisitor 对象。
如果它有继承配置,那么将在composedClassVisitor的基础上在增加 ClassHierarchyTraveler 用来传递到继承的标记。
最后SeedPrinter通过标记来区分打印。
===============================================================
===============================================================
===============================================================
第四部分:压缩 (shrink) Shrinker.execute
压缩使用的是UsageMarker 这个访问者,它的目的也是为了做标记。
先是对ClassSpecificationVisitorFactory.createClassPoolVisitor 做完标记,
然后再对其引用的
new InnerUsageMarker(usageMarker),
new AnnotationUsageMarker(usageMarker),
new SignatureUsageMarker(usageMarker),
new LocalVariableTypeUsageMarker(usageMarker)
做标记。