zoukankan      html  css  js  c++  java
  • 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)
    做标记。

  • 相关阅读:
    没有比脚更长的路 没有比人更高的山
    Nginx---应用场景小结
    程序员北漂6年,活着 ---纪念逝去的青春
    程序员/PM怎么让项目预估的时间更加准确
    程序员从技术开发到项目管理PM--思维转变
    什么是MSF
    程序员有七个等级?你又属于哪个等级呢?
    linux之 sed 基础
    linux之awk基础
    centos 7 jenkins 部署
  • 原文地址:https://www.cnblogs.com/feizimo/p/3523834.html
Copyright © 2011-2022 走看看