美文网首页
Transform注册到底有几种方式?一段代码引发的疑问。

Transform注册到底有几种方式?一段代码引发的疑问。

作者: MIAN勉 | 来源:发表于2019-07-12 15:33 被阅读0次

    首先,看到题目我怀疑我有些标题党了。为了吸引眼球,是不是有些不择手段了些。EMMM...要反省一下。废话不多说,进入正题。先来看看我们常见的注册Transform的方式。

        class SystracePlugin implements Plugin<Project> {
            @Override
            void apply(Project project) {       
                project.getExtensions().getByType(AppExtension).registerTransform(new CustomTransform())
            }
        }
    

    那各位就要问了:除了这个还有什么别的方式可以注册?别急,先来看一段代码,

    public static void inject(Project project, def variant) {
         ...
         project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
            @Override
            public void graphPopulated(TaskExecutionGraph taskGraph) {
                 for (Task task : taskGraph.getAllTasks()) {
                      if ((task.name.equalsIgnoreCase(hackTransformTaskName) ||
                              task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))
                              && !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) {
                            project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name)
                            project.logger.info("variant name: " + variant.name)
                            Field field = TransformTask.class.getDeclaredField("transform")
                            field.setAccessible(true)
                            //注意了!!!
                            field.set(task, new SystemTraceTransform(project, variant, task.transform))
                            project.logger.warn("transform class after hook: " + task.transform.getClass())
                            break
                        }
                    }
                }
            })
        }
    

    各位又说了,这没头没尾的一段想说明什么?大佬们不要心急,且听我慢慢道来。上面这一段并不是出自我手。这是张绍文在极客时间上开设的专栏《Android开发高手课》中,第七章中的一个简单的课后sample,Github地址。各位可自行下载查验。按照sample的说明运行后发现,这个项目没有文章刚开始提到的那种常见的注册方式,而貌似和Transform注册挂钩的就是我上面摘出的这一段了。

    是不是会有这个疑问:这种注册方式同传统的注册方式有什么不同?

    专栏也有读者提出了这个疑问,
    Q: 请问project.getProperties().get("android").registerTransform跟示例的注册Transform方式哪一种更好点呢?
    A: 因为我们是想修改原来transform的逻辑,通过hook的方式可以控制的更好,而且关键还有时序的问题。

    EMMM...这个答案确实没有解决了我心中的疑问。那么只好自己动手了。

    在上一篇apply plugin: 'xxx'到底做了啥中,我对apply plugin这一过程进行了简单的分析,文中也提到很多细节忽略了。这里我们聚焦该过程中一处细节,看看能不能挖出我们想要的东西。我们从上一篇结束的地方开始,taskManager#createTasksForVariantData中会创建各种任务,其中有一个与本文关系密切,方法如下,

    public void createPostCompilationTasks(TaskFactory tasks, VariantScope variantScope) {
         ...    
         AndroidConfig extension = variantScope.getGlobalScope().getExtension();
           
         List<Transform> customTransforms = extension.getTransforms();
         List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();
         int i = 0;
    
         AndroidTask dexTask;
         for(int count = customTransforms.size(); i < count; ++i) {
              Transform transform = (Transform)customTransforms.get(i);     
              dexTask = transformManager.addTransform(tasks, variantScope, transform);
              if (dexTask != null) {
                    List<Object> deps = (List)customTransformsDependencies.get(i);
                    if (!deps.isEmpty()) {
                        dexTask.dependsOn(tasks, new Object[]{deps});
                    }
    
                    if (transform.getScopes().isEmpty()) {
                        variantData.assembleVariantTask.dependsOn(new Object[]{tasks, dexTask});
                    }
               }
          }
          ...
     }
    

    我们将最重要的一条语句单独摘出来,

    dexTask = transformManager.addTransform(tasks, variantScope, transform);
    

    这说明从BasePlugin#apply开始,到ApplicationTaskManager#createPostCompilationTasks的逻辑中涉及到transform操作,当然这里还没有涉及到transform内部逻辑的调用,但这里确实已经开始着手准备了。transform内部什么时候执行我们先不管,也不是这里的重点。

    这里不禁发问,TaskManager#createPostCompilationTasks中的transform从哪里来?

    先来探知一下transform的源头。trasform是从customTransforms集合中遍历而来,customTransforms是调用extension#getTransforms而来,extension是这样获得的

    AndroidConfig extension =variantScope.getGlobalScope().getExtension();
    

    variantScope类型为VariantScope接口,实现类为VariantScopeImpl,VariantScopeImpl#getGlobalScope为

    public GlobalScope getGlobalScope() {
            return this.globalScope;
    }
    

    再看GlobalScope#getExtension

    public AndroidConfig getExtension() {
            return this.extension;
    }
    

    extension类型为接口AndroidConfig,看一下AndroidConfig的类继承结构图。


    Figure 1 AndroidConfig类继承结构图

    看到Extension类,熟悉的感觉回来了,有没有。我们知道自定义Transform注册过程是调用 BaseExtension#registerTransform

    public void registerTransform(Transform transform, Object... dependencies) {
          this.transforms.add(transform);
          this.transformDependencies.add(Arrays.asList(dependencies));
    }
    

    再看一下BaseExtension#getTransforms

    public List<Transform> getTransforms() {
          return ImmutableList.copyOf(this.transforms);
    }
    

    是不是和上面完美衔接。createPostCompilationTasks中extension.getTransforms()返回的就是我们通常注册Transform填充的transforms容器的Copy版本。也就是说按照常规注册Transform,在apply plugin时就会涉及到Transform的准备工作(Transform是否会在该过程中被执行还未查明)。

    这一部分内容告一段落,截至目前,本文有点像是上一篇的细节展开。接下来,我们还是回归sample的那段代码,直接聚焦关键代码:

    project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
                @Override
                public void graphPopulated(TaskExecutionGraph taskGraph) {
                    
                    for (Task task : taskGraph.getAllTasks()) {
                        if ((task.name.equalsIgnoreCase(hackTransformTaskName) ||
                                task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))
                                && !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) {
                         
                            Field field = TransformTask.class.getDeclaredField("transform")
                            field.setAccessible(true)
                            field.set(task, new SystemTraceTransform(project, variant, task.transform))
                            project.logger.warn("transform class after hook: " + task.transform.getClass())
                            break
                        }
                    }
                    
                }
            })
    

    注意到,for循环所处位置是在TaskExecutionGraphListener的回调方法中,也就是说,for循环是在任务图(TaskGraph)构建完成后才执行的,记住这一点,我们后续会再接着说。

    注意到for循环中的if条件判断,先不去管其中的逻辑,我有点想知道hackTransformTaskName和hackTransformTaskNameForWrapper是什么?这里需要我们打印log查看一下,但是请注意,Transform类中的逻辑是发生在构建过程中,想看到输出的log可没有那么容易。而且,一旦修改了Transform类中的逻辑,需要重新发布到库(无论是远程库Maven、Ivy,还是本地库),然后重新构建systrace-samle-android,这样你想要看到的log才会在构建过程日志中出现。

    所以,先去看看这个systrace-gradle-plugin发布到哪里了。当然,最好是发布到本地,这样我们可以任意在构建部分的代码添加调试代码,发布也方便。而且作为一个sample,我猜应该是发布在本地。如图2,在项目结构中gradle目录下有一个java-publish.gradle的文件。从文件名就知道该gradle文件与发布有关,可以先点进去看看。


    Figure 2 项目结构

    同样摘一部分java-publish.gradle的跟发布有关的内容

    apply plugin: 'maven-publish'
    publishing {
        publications {
            Component(MavenPublication) {
                from components.java
                groupId = group
                artifactId = POM_ARTIFACT_ID
                version = version
    
                artifact sourcesJar
                artifact javadocJar
            }
        }
    }
    
    task buildAndPublishToLocalMaven(type: Copy, dependsOn: ['build', 'publishToMavenLocal']) {
        group = 'geektime'
    
        // save artifacts files to artifacts folder
        from configurations.archives.allArtifacts.files
        into "${rootProject.buildDir}/outputs/artifacts/"
        rename { String fileName ->
            fileName.replace("release.aar", "${version}.aar")
        }
    
        doLast {
            println "* published to maven local: ${project.group}:${project.name}:${project.version}"
        }
    }
    

    文件中有一个buildAndPublishToLocalMaven的任务,可以看到发布路径

    "${rootProject.buildDir}/outputs/artifacts/"
    
    这一点在项目的对应路径下可以得到印证。 Figure 3 相应路径下的插件

    也就是说,Chapter07项目中的自定义插件是直接发布在本地。这样的话我们就可以直接在变量下添加打印log,然后重新发布就可以了。

     String hackTransformTaskName = getTransformTaskName(
                     "",
                    "",variant.name
            )
    
     Log.i(TAG, "hackTransformTaskName:%s"+hackTransformTaskName)
    

    在AndroidStudio右侧Gradle标签栏下找到geektime,双击buildAndPublishToLocalMaven任务运行。


    Figure 4 运行buildAndPublishToLocalMaven
    构建成功后,单独加载工程systrace-sample-android。直接build,在build日志中得到变量hackTransformTaskName的值。 Figure 5 hackTransformTaskName的值
    这样得知,hackTransformTaskNameForWrapper的值为transformClassesWithDexBuilderFor[Variant],(Variant取值为Debug和Release)。

    那么if判断逻辑为:名称为transformClassesWithDexBuilderFor[Variant]或transformClassesWithDexFor[Variant]的任务该任务的transform字段类型不为SystemTraceTransform。

       if ((task.name.equalsIgnoreCase(hackTransformTaskName) ||
             task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))&& 
              !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)) {...}
    
    这里,我也把构建过程中执行的task打印出来, Figure 6 构建过程中task(debug)执行次序

    从图6可以看到确实有一个名为transformClassesWithDexBuilderForDebug的任务,当然,图6中输出的是构建类型为debug的task,当然还有一个transformClassesWithDexBuilderForRelease的任务。 if块中的逻辑则是把transformClassesWithDexBuilderForDebug/transformClassesWithDexBuilderForRelease的transform字段替换为SystemTraceTransform类型的对象。貌似这样就完成了注册了,因为方法到此已经结束。所以...
    等等!!!注意if判断中最后一个判断条件

    !(((TransformTask) task).getTransform() instanceof SystemTraceTransform)
    
    TransformTask又是什么任务?它与Transform有什么关系?

    额...
    EMMM...
    让我回头看看,我是不是遗漏了什么...
    好吧,确实有。
    TaskManager#createPostCompilationTasks中有一句,

    dexTask = transformManager.addTransform(tasks, variantScope, transform);
    

    我们应该看一下TransformManager#addTransform逻辑,如下,我只保留了对本文来说最重要的部分。

    
    public <T extends Transform> AndroidTask<TransformTask> addTransform(TaskFactory taskFactory, BaseScope scope, T transform, ConfigActionCallback<T> callback) {
         if(...){
            ...
         } else {           
            String taskName = scope.getTaskName(getTaskNamePrefix(transform));  
            ...
            if (...) {
                ...
            } else {
                ...
                this.transforms.add(transform);             
                //create transform task
                AndroidTask<TransformTask> task = this.taskRegistry.create(taskFactory, new ConfigAction(scope.getVariantConfiguration().getFullName(), taskName, transform, inputStreams, referencedStreams, outputStream, callback));
                ...
                return task;
             }
         }
    }
    

    transforms为TransformManager的一个类型为List<Transform>全局容器,将传入方法的transform添加到容器,然后!!!你没看错,TransformManager根据taskName, transform, variantConfiguration等元素创建了TransformTask类型的任务,最后返回了该任务。明白了?Transform最后转为了相应的TransformTask。

    所以,理到这里,其中的脉络有些清晰了。那Transform注册到底有几种方式呢?实质上是一种,只不过常规注册是官方提供的标准方式。这里我们总结一下常规流程:apply plugin xxxPluginId会触发createPostCompilationTasks执行;接着TransformManager#addTransform执行,其中添加的transform就是调用registerTransform方法注册的transform;addTransform方法根据transform创建相应的TransformTask。至于Transform的逻辑什么时候执行,本文没有涉及。

    而《Android高手课》的sample是一种customized,即定制化的方式。注册发生在TransformTask创建完成且TaskGraph构建好了以后,通过taskName找到TransformTask,利用反射替换TransformTask中的transform字段为项目中自定义的SystemTraceTransform,完成符合条件的字节码文件修改目的。

    至于那个朋友问的,哪种方法好?我觉得选择适合自己的就行,如果你对构建过程及Gradle生命周期很熟悉,那就在合适的时机注入Transform。如果还在学习构建及Gradle,那就老老实实的用常规方法注册,毕竟成长为大佬还是需要时间和实践的。

    后记

    本文是对Gradle Transform部分及《Android高手课》Chapter07的sample的一个浅显片面的解读,或许还没有Get到项目中的真正细节和精髓,如有错误,欢迎指出,板砖轻拍😀。

    相关文章

      网友评论

          本文标题:Transform注册到底有几种方式?一段代码引发的疑问。

          本文链接:https://www.haomeiwen.com/subject/wwdekctx.html