zoukankan      html  css  js  c++  java
  • DroidAssist执行流程分析。

    1:首先整个插件的入口是DroidAssistPlugin.groovy下面的apply函数。在自定义插件的apply方法中,获取module对应的project的AppExtension,然后通过其registerTransform方法注册一个自定义的Transform。注册之后,在编译流程中会通过TaskManager-createPostCompilationTasks为这个自定义的Transform生成一个对应的Task,在.class文件转换成.dex文件的流程中会执行这个Task,对所有的.class文件(可包括第三方库的.class)进行转换,转换的逻辑定义在Transform的transform方法中。

    class DroidAssistPlugin implements Plugin<Project> {
    
        @Override
        void apply(Project project) {
            /*
            扩展属性的配置,droidAssistOptions属性
            * */
            project.extensions.create("droidAssistOptions", DroidAssistExtension)
    
            if (project.plugins.hasPlugin(AppPlugin.class)) { ////这句话判断是不是主程序,如果是主程序,获得所有的class文件
                AppExtension extension = project.extensions.getByType(AppExtension)
                /*
                如果当前引用插件的项目为apply plugin: 'com.android.application'则配置的DroidAssistTransform的属性标记为true
                * */
                extension.registerTransform(
                        new DroidAssistTransform(project, true))
            }
            /*
            同上,这个只是对类的处理
            * */
            if (project.plugins.hasPlugin(LibraryPlugin.class)) {
                LibraryExtension extension = project.extensions.getByType(LibraryExtension)
                extension.registerTransform(
                        new DroidAssistTransform(project, false))
            }
        }
    }
    

      2:下面我们来看一下transform函数,这个函数在DroidAssistTransform.groovy里面;在这个transform函数里面,最主要调用的是核心函数 onTransform,下面我们来看一下这个函数。

    如果在配置里面你不开启这个插件的话,也就是droidAssistOptions里面的enabled=false,那么程序会原封不动的将输入文件拷贝到输出文件夹中,交给下一个Transform处理,其实Transform就是用Task装饰了一下,最终执行的还是任务。isIncremental这个变量判断的是否实现增量更新,不是的话直接删除所有输出文件,然后创建DroidAssistContext,这个类作用是将所有要操作的类放入类池中,当然是先加载规则文件剔除掉不需要操作的class,主要是通过configure()函数来实现,最后是创建DroidAssistExecutor类实现字节码的操作,主要是通过execute()函数实现,其他的代码都是和日志相关的;所以后面我们主要关注的函数就是configure() 和 execute();

        void onTransform(
                Context gradleContext,
                Collection<TransformInput> inputs,/////这里的输入包括jar输入和directoryinputs
                Collection<TransformInput> referencedInputs,
                TransformOutputProvider outputProvider,
                boolean isIncremental)
                throws IOException, TransformException, InterruptedException {
    
            Logger.info("Transform start, " +
                    "enable:${gradleExtension.enable}, " +
                    "incremental:${isIncremental}")
    
            // 这个属性为false的时候直接copy输入文件到输出位置,交给下一个Transform处理----这个属性的配置是用户在gradle.build直接配置droidassistoptions就可以了
            if (!gradleExtension.enable) {
                outputProvider.deleteAll()
                def dirStream = inputs
                        .parallelStream()
                        .flatMap { it.directoryInputs.parallelStream() }
                        .filter { it.file.exists() }
    
                def jarStream = inputs
                        .parallelStream()
                        .flatMap { it.jarInputs.parallelStream() }
                        .filter { it.file.exists() }
    
                Stream.concat(dirStream, jarStream).forEach {
                    def copy = it.file.isFile() ? "copyFile" : "copyDirectory"
                    FileUtils."$copy"(
                            it.file,
                            GradleUtils.getTransformOutputLocation(outputProvider, it))
                }
                return
            }
    
            def start = System.currentTimeMillis()
            Logger.info("DroidAssist options: ${gradleExtension}")
            def timingLogger = new TimingLogger("Timing", "execute")
    
           //不是增量更新每次删除旧的输出文件
            if (!isIncremental) {
                outputProvider.deleteAll()
                timingLogger.addSplit("delete output")
            }
          //将需要操作的类都加入到流中
            def context =
                    new DroidAssistContext(
                            gradleContext,
                            project,
                            gradleExtension,
                            referencedInputs)
            context.configure()
            timingLogger.addSplit("configure context")
        ///////创建字节码操作执行者
            def executor =
                    new DroidAssistExecutor(
                            context,
                            outputProvider,
                            isIncremental)
            timingLogger.addSplit("create executor")
    
            //Execute all input classed with byte code operation transformers
            executor.execute(inputs)
            timingLogger.addSplit("execute inputs")
    
            timingLogger.dumpToLog()
            Logger.info("Transform end, " +
                    "input classes count:${executor.classCount}, " +
                    "affected classes:${executor.affectedCount}, " +
                    "time use:${System.currentTimeMillis() - start} ms")
        }
    }
    

      2.1:我们先看一下configure的实现  ;首先是创建类池,在这个类池中会整合dirStream 和 jarstream. 然后我们通过loadConfiguration()这个函数加载.xml配置文件,然后在这个函数里面还通过DroidAssistConfiguration.groovy类对xml文件的解析,得到过滤机制,然后对类池的class进行过滤。下面我们具体介绍一下createClassPool() 和 loadConfiguration()函数。

        def configure() {
            try {
            //创建类的操作池
                createClassPool()
            } catch (Throwable e) {
                throw new DroidAssistException("Failed to create class pool", e)
            }
            //加载配置文件并过滤掉不需要修改的class
     
            transformers = loadConfiguration()
            
    

      2.1.1: 类池的创建createClassPool()

     def createClassPool() {
            classPool = new DroidAssistClassPool()
            //将android代码放入
            org.gradle.api.logging.Logger logger=project.logger;
            classPool.appendBootClasspath(project.android.bootClasspath)
            logger.quiet("path:"+project.android.bootClasspath);
            //拿出class文件流
            def dirStream = referencedInputs
                    .parallelStream()
                    .flatMap { it.directoryInputs.parallelStream() }
                    .filter { it.file.exists() }
        //拿出jar文件下的流
            def jarStream = referencedInputs
                    .parallelStream()
                    .flatMap { it.jarInputs.parallelStream() }
                    .filter { it.file.exists() }
         //合并两个流,将他们全部放入操作池中
            Stream.concat(dirStream, jarStream).forEach {
                Logger.info("Append classpath: ${IOUtils.getPath(it.file)}")
                classPool.appendClassPath(it.file)
    

      2.1.2:loadConfiguration()函数,我们通过DroidAssistConfiguration的parse来解析xml文件,后面我们会看一下如何解析的。

    def loadConfiguration() {
            def transformers = extension.configFiles
                    .parallelStream()
                    .flatMap {
                try {
                    //解析xml文件
                    def list = new DroidAssistConfiguration(project).parse(it)
                    return list.stream().peek {
                        transformer ->
                            //添加包括哪些类
                            transformer.classFilterSpec.addIncludes(extension.includes)
                            //添加不包裹哪些类
                            transformer.classFilterSpec.addExcludes(extension.excludes)
                            transformer.setClassPool(classPool)
                            transformer.setAbortOnUndefinedClass(extension.abortOnUndefinedClass)
                            //开始检测
                            transformer.check()
                    }
                } catch (Throwable e) {
                    throw new DroidAssistException("Unable to load configuration," +
                            " unexpected exception occurs when parsing config file:$it, " +
                            "What went wrong:
    ${e.message}", e)
                }
            }//parse each file
                    .collect(Collectors.toList())
     
            Logger.info("Dump transformers:")
            transformers.each {
                Logger.info("transformer: $it")
            }
            return transformers
        }
    

      2.1.3:先用XmlParser将文件解析为Node,然后遍历节点,根据节点的值进行不同Transformer的创建,这些具体的结点的值都有哪些,文档里面说的很清楚了。照着来就行;进过这个函数之后就创建了很多种类型的的TransformerNodeHandler,这些不同的transform类都是在tarnsform文件夹下定义,根据的是策略模式的组织方式。之后就可以执行execute()方法了。

    List<Transformer> parse(File file) {
            Node configs = new XmlParser(true, true, true).parse(file)
     
            configs.Global.Filter.Include.each { globalIncludes.add(it.text()) }
            configs.Global.Filter.Exclude.each { globalExcludes.add(it.text()) }
          //添加不同的处理策略
            configs.Replace.MethodCall.each {
                node ->
                    sourceTargetTransformerNodeHandler(METHOD, node) {
                        //代表第三个参数,最后一个参数的闭包可以这么写
                        return new MethodCallReplaceTransformer()
                    }
            }
            //添加不同的处理策略
            configs.Replace.MethodExecution.each {
                node ->
                    sourceTargetTransformerNodeHandler(METHOD, node) {
                        return new MethodExecutionReplaceTransformer()
                    }
            }
     
            configs.Replace.ConstructorCall.each {
                node ->
                    sourceTargetTransformerNodeHandler(CONSTRUCTOR, node) {
                        return new ConstructorCallReplaceTransformer()
                    }
            }
            configs.Replace.ConstructorExecution.each {
                node ->
                    sourceTargetTransformerNodeHandler(CONSTRUCTOR, node) {
                        return new ConstructorExecutionReplaceTransformer()
                    }
            }
    。。。。。。。。。
        
    }
    

      2.2  执行execute()方法,这个方法定义在DroidAssistExecutor.groovy里面。代码如下:

     void execute(Collection<TransformInput> inputs) {
            //合并两个类型class流
            def dirStream = inputs.stream()
                    .flatMap { it.directoryInputs.stream() }
     
            def jarStream = inputs.stream()
                    .flatMap { it.jarInputs.stream() }
     
            Stream.concat(dirStream, jarStream)
                    .parallel()
             //将流转化为task执行
                    .map { createTask(it) }/////其中createTask合并两种输入的class流
                    .filter { it != null }
                    .forEach { it.run() }////这个run方法会调用每一个子类的execute()方法
    

      

    createTask创建了两种task,一种是JarInputTask,一种是DirInputTask,两个任务都有一个方法是execute()方法,因为JarInputTask和DirInputTask都是实现的是InputTask中的抽象方法。我们主要看一下execute方法的执行。
    我们先看一下DirInputTask中的:
    InputTask createTask(QualifiedContent content) {
            //创建两个输入任务
            def taskInput =
                    new InputTask.TaskInput(
                            input: content,
                            dest: GradleUtils.getTransformOutputLocation(outputProvider, content),
                            incremental: incremental)
            if (content instanceof JarInput) {
                return new JarInputTask(context, buildContext, taskInput)
            }
            if (content instanceof DirectoryInput) {
                return new DirInputTask(context, buildContext, taskInput)
            }
            return null
    

      判断如果是增量更新的话,判断哪些文件改变了,删除的删除,改变的将它全部copy到目的地然后放到files集合,过滤出class文件,执行executeClass方法改变字节码,最后将改变的字节码copy到输出目录覆盖原来的。所以executeClass方法是核心方法,主要完成字节码的转换。

    void execute() {
            DirectoryInput input = taskInput.input
            def inputDir = input.file
            def executor = new WorkerExecutor(1)
            List<File> files = Lists.newArrayList()
    /**
     * 如果增量更新的话只改变增量更新
     */
     
            if (taskInput.incremental) {
                //process changedFiles in incremental mode.
                //if file is removed, delete corresponding dest file.
                //if file is changed or added, add file to pending collections.
                input.changedFiles.each {
                    file, status ->
                        def destFile = getDestFileMapping(file, inputDir, taskInput.dest)
                        context.project.logger.quiet("taskInput.dest:" + taskInput.dest.getAbsolutePath());
                        context.project.logger.quiet("file:" + file.getAbsolutePath());
                        context.project.logger.quiet("inputDir:" + inputDir.getAbsolutePath());
                        context.project.logger.quiet("destFile:" + destFile.getAbsolutePath());
                        if (status == Status.REMOVED) {
                            if (destFile != null) {
                                FileUtils.deleteQuietly(destFile)
                            }
                        }
                        if (status == Status.CHANGED || status == Status.ADDED) {
                            //添加进files
                            files << file
                            if (destFile != null) {
                                executor.execute {
                                    FileUtils.copyFile(file, destFile)
                                }
                            }
                        }
                }
            } else {
                //process every class file in Non-incremental mode
                executor.execute {
                    FileUtils.copyDirectory(inputDir, taskInput.dest)
                }
     
                def fileList = Files.walk(inputDir.toPath())
                        .parallel()
                        .map { it.toFile() }//Path to file
                        .filter { it.isFile() }
                        .filter { it.name.endsWith(DOT_CLASS) } //Filter class file
                        .collect(Collectors.toList())
     
                files.addAll(fileList)
            }
    //过滤出class
            files.stream()
                    .filter { it.isFile() }//Path to file
                    .filter { it.name.endsWith(DOT_CLASS) }//Filter class file
                    .forEach { executeClass(it, inputDir, temporaryDir) }
    /**
     * 操作完输入给输出覆盖掉原来的
     */
            executor.execute {
                FileUtils.copyDirectory(temporaryDir, taskInput.dest)
            }
     
            executor.finish()
        }
    

      2.3 派生类DirinputTask中也有一个executeClass方法,但是这只是对基类InoutTasks中的一个封装如下:

    DirinputTask:

        void executeClass(File classFile, File inputDir, File tempDir) {
            def className =
                    FilenameUtils.
                            removeExtension(
                                    inputDir.toPath()
                                            .relativize(classFile.toPath())
                                            .toString())
                            .replace(File.separator, '.') ///////  将/替换为.
            executeClass(className, tempDir)////调用基类中的方法:
        }
    

      其中里面比较重要的一个函数是performTransform,这个函数是定义在所有transform类型的公共基类Transformer.groovy中。因为各种transformer的构建是按照策略模式安排的。我们首先看一下performTransform

      boolean executeClass(String className, File directory) {
            buildContext.totalCounter.incrementAndGet()
            def inputClass = null
            def transformers = context.transformers.findAll { /////得到之前建立的所有的transformer
                it.classAllowed(className)
            }
    
            if (transformers.isEmpty()) {
                return false
            }
    
            inputClass = context.classPool.getOrNull(className)////得到所有的待转换的类
            if (inputClass == null) {
                return false
            }
    
            transformers.each {
                try {
                    it.performTransform(inputClass, className)///转换开始
                } catch (NotFoundException e) {
                    throw new DroidAssistNotFoundException(
                            "Transform failed for class: ${className}" +
                                    " with not found exception: ${e.cause?.message}", e)
                } catch (CannotCompileException e) {
                    throw new DroidAssistBadStatementException(
                            "Transform failed for class: ${className} " +
                                    "with compile error: ${e.cause?.message}", e)
                } catch (Throwable e) {
                    throw new DroidAssistException(
                            "Transform failed for class: ${className} " +
                                    "with error: ${e.cause?.message}", e)
                }
            }
    
            if (inputClass.modified) {//类被修改的话则完成修改,将文件写入directory文件夹下
                buildContext.affectedCounter.incrementAndGet()
                inputClass.writeFile(directory.absolutePath)
                return true
            }
            return false
        }
    }
    

      2.4 这个方法让ctclass这个类可以重新操作然后执行beforeTransform和onTransform,最终字节码修改是在onTransform中实现。onTransform是在ExprExecTransformer.groovy中实现的。可以看出有两种类型,onTransformExpr 和 onTransformExec,

        protected final boolean onTransform(
                CtClass inputClass,
                String inputClassName)
                throws NotFoundException, CannotCompileException {
            //expr
            if (TRANSFORM_EXPR.equals(getTransformType())) {
                return onTransformExpr(inputClass, inputClassName);
            }
            //exec
            if (TRANSFORM_EXEC.equals(getTransformType())) {
                return onTransformExec(inputClass, inputClassName);
            }
            return false;
        }
    

      我们看一下 onTransformExpr(),,我们可以看到editor中有三个函数重载,对应方法,属性,new操作三种处理方式。而在editor类中,edit方法总会调用各种transformer类型的execute()方法,下面我们以MethodCallInsertTransformer.groovy中的execute()实现来看一下:

    //检查class是否在include或者不在einclude
            if (!filterClass(inputClass, inputClassName)) {
                return false;
            }
     
            final AtomicBoolean modified = new AtomicBoolean(false);
    Editor editor = new Editor() { /////有三种函数重载,对应三种不同的调用         ///遇到方法调用会调用下面这个 @Override public void edit(MethodCall call) throws CannotCompileException { if (METHOD_CALL.equals(getExecuteType())) { boolean disposed; try { disposed = execute(inputClass, inputClassName, call); } catch (NotFoundException e) { String msg = e.getMessage() + " for input class " + inputClassName; throw new CannotCompileException(msg, e); } modified.set(modified.get() | disposed); } }         /////遇到属性声明会调用这个 @Override public void edit(FieldAccess fieldAccess) throws CannotCompileException { if (FIELD_ACCESS.equals(getExecuteType())) { boolean disposed; try { disposed = execute(inputClass, inputClassName, fieldAccess); } catch (NotFoundException e) { String msg = e.getMessage() + " for input class " + inputClassName; throw new CannotCompileException(msg, e); } modified.set(modified.get() | disposed); } }         ///遇到new对象会走这个 @Override public void edit(NewExpr newExpr) throws CannotCompileException { if (NEW_EXPR.equals(getExecuteType())) { boolean disposed; try { disposed = execute(inputClass, inputClassName, newExpr); } catch (NotFoundException e) { String msg = e.getMessage() + " for input class " + inputClassName; throw new CannotCompileException(msg, e); } modified.set(modified.get() | disposed); } } }; //得到默认构造方法函数, CtConstructor initializer = tryGetClassInitializer(inputClass); if (initializer != null) { if (instrument(initializer, editor)) { modified.set(true); } } //得到类的所有方法 CtMethod[] declaredMethods = tryGetDeclaredMethods(inputClass); for (CtMethod method : declaredMethods) { if (instrument(method, editor)) { modified.set(true); } } //得到所有声明的构造方法 CtConstructor[] declaredConstructors = tryGetDeclaredConstructors(inputClass); for (CtConstructor constructor : declaredConstructors) { if (instrument(constructor, editor)) { modified.set(true); } } return modified.get();

      

    2.5:这个方法主要也是做了三件事,检查这个methodCall的调用类是不是source中的类,是的话检测是在前插入代码还是后插入代码,后和前将原来代码放在中间替换原来的代码,这样就实现了代码的前或后插入。replaceInstrument()函数将原来的替换成新的码

    /如果是super的直接不予修改
            if (methodCall.isSuper()) {
                return false;
            }
            String insnClassName = methodCall.getClassName();
            String insnName = methodCall.getMethodName();
            String insnSignature = methodCall.getSignature();
         //是否能找到这个方法的调用
            CtClass insnClass = tryGetClass(insnClassName, inputClassName);
            if (insnClass == null) {
                return false;
            }
            //判断这个类是否是source处
            if (!isMatchSourceMethod(insnClass, insnName, insnSignature)) {
                return false;
            }
     
            String target = getTarget();
            int line = methodCall.getLineNumber();
            if (!target.endsWith(";")) target = target + ";";
     
            String before = isAsBefore() ? target : "";
            String after = isAsAfter() ? target : "";
     
            String proceed = isVoidSourceReturnType() ? "$proceed($$);" : "$_ =$proceed($$);";
     
            String statement = before + proceed + after;
       //将原来代码替换为附加代码
            String replacement = replaceInstrument(methodCall, statement);
     
            if (isAsBefore()) {
                Logger.warning(getPrettyName() + " insert before call by: " + replacement
                        + " at " + inputClassName + ".java" + ":" + line);
            }
     
            if (isAsAfter()) {
                Logger.warning(getPrettyName() + " insert after call by: " + replacement
                        + " at " + inputClassName + ".java" + ":" + line);
            }
            return true;
        
    

      

  • 相关阅读:
    Java之设计模式详解 (转)
    强引用,软引用,弱引用和虚引用总结
    Java基础知识总结
    深入理解Java的接口和抽象类
    Android Studio高级配置
    JS中innerHTML 和innerText和value的区别
    Prompt isNaN 数组 function DOM window.open/close/location/history
    WebForm组合查询
    WebForm分页浏览
    WebForm上传文件FileUpload
  • 原文地址:https://www.cnblogs.com/mataiyuan/p/12836492.html
Copyright © 2011-2022 走看看