Android一步步实现无痕埋点(3)-------虎躯一震

0.前言

Android一步步实现无痕埋点(2)-------开始折磨

上一篇文章,我们讲解了无痕是个什么东西,现在我们开始解(jiao)释(bian)transform又是在其中担任了一个什么神奇的步骤。

1.寻找"作案"对象

大佬的transform讲的很好
由上篇文章,我们知道了。整体步骤要先找到.class文件之后对其进行修改
那么transform的作用就是在打包.dex文件之前的编译过程中操作找到我们的.class文件。对其进行遍历.

首先, 先添加依赖:
我这里的gradle版本的7.0.2

api “com.android.tools.build:gradle-api:7.0.2”
api “com.android.tools.build:gradle:7.0.2”

将我们的自定义的transform注册进去
那注册的地方就为我们的Plugins里面啦,在我们原本的Plugin中添加代码

class DoubleTapPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        //返回插件的类型
        val isApp = project.plugins.hasPlugin(AppPlugin::class.java)
        println("This is cool , you know -->$isApp")
        val appExtension = project.extensions.getByType(AppExtension::class.java)
        appExtension.registerTransform(DoubleTaptransfrom())
        return
    }

要知道,Transform其实是类似于一个链条的一样。
TransfromA—>TransfromB—>TransfromSystem
所有编译的中间产物都会在我们的Transforms组成的链条上进行流动。每个Transform节点可以对class进行处理之后再传递给下一位Transfrom.
是不是很像责任链模式?其实很类似。

Transform有两种输入,一种是消费型的,当前Transform需要将消费型型输出给下一个Transform,另一种是引用型的,当前Transform可以读取这些输入,而不需要输出给下一个Transform.

这样就将我们的transform插入进去了.ok,我们看一下我们的DoubleTapleTransform到底是个什么东西。


class DoubleTaptransfrom : Transform() {
    override fun getName(): String {
        return "DoubleTapTransform"

    }

    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
        return mutableSetOf<QualifiedContent.ContentType>().apply {
            addAll(TransformManager.CONTENT_JARS)
        }
    }

    override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
        return mutableSetOf<QualifiedContent.ScopeType>().apply {
            addAll(TransformManager.SCOPE_FULL_PROJECT)
        }
    }

    override fun isIncremental(): Boolean {
        return true
    }

    override fun isCacheable(): Boolean {
        return true
    }
    override fun transform(transformInvocation: TransformInvocation?) {
    //do something
}

getName,就是我们transform的名称。没什么好说的

override fun getName(): String {
        return "DoubleTapTransform"
    }

getInputTypes,看到源码只能是两种,必须是
CLASSES(0x01):包括文件夹内的编译的Java代码
RESOURCES(0x02):Java资源文件
表示该transfrom要处理的资源类型,由于我们修改所有文件,所有当然选择的是CONTENT_JARS.两者全部

 override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
        return mutableSetOf<QualifiedContent.ContentType>().apply {
            addAll(TransformManager.CONTENT_JARS)
        }
    }

getScopes,表示转换的范围,理解为作用于即可.一般是处理全部的class字节码,所以用TransformManager.SCOPE_FULL_PROJECT

 override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
        return mutableSetOf<QualifiedContent.ScopeType>().apply {
            addAll(TransformManager.SCOPE_FULL_PROJECT)
        }
    }

isIncremental,增量编译开关。
这里涉及到编译的过程,有很多大量的知识在这里可以进行优化等等…等待你们继续挖掘,本菜狗只进行简单介绍即可
当我们开启增量编译的时候,会开始检查每个文件的状态,状态以下4种不同的操作。

NOTCHANGED: 当前文件不需处理,甚至复制操作都不用
ADDED,CHANGED: 正常处理,输出给下一个任务
REMOVED: 移除outputProvider获取路径对应的文件

那在后面的操作中,去区分当次是否为增量编译,再去编译当前的变更的文件,这样会让编译速度大大提升

@Override
boolean isIncremental() {
    return true
}

Transfrom,处理文件的核心代码
其中TransformInvocation就包括了所有我们需要的资源.inputs为文件的输入.即上一个Transfrom流下来的。
那么在 transform 方法中主要做的事情就是把 Inputs 保存到 outProvider 提供的位置去

    override fun transform(transformInvocation: TransformInvocation?) {
}

好了,这些都先介绍的差不多了。
这里得出了结论,inputs.终于找到你了,我们要动手的对象,暂且休息一下。
Android一步步实现无痕埋点(3)-------虎躯一震_第1张图片

有可爱的小宝贝已经发现了在gradle7.0.2下,Transfrom已经被遗弃了…
凸(艹皿艹 )擦 <)!!!
我刚学,你就给我来个遗弃?搁着跟我玩呢?..但其实这里的基本学习流程还是很重要的。往后我会再介绍使用新的AsmClassVisitorFactory替代Transfrom当然,网上资料多的还是Transfrom,但是我觉得学习肯定学新的啊~
不行你就去看万恶之源

我们继续,看我们大佬写的transfrom.


class DoubleTaptransfrom : Transform() {
    override fun getName(): String {
        return "DoubleTapTransform"

    }

    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
        return mutableSetOf<QualifiedContent.ContentType>().apply {
            addAll(TransformManager.CONTENT_JARS)
        }
    }

    override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
        return mutableSetOf<QualifiedContent.ScopeType>().apply {
            addAll(TransformManager.SCOPE_FULL_PROJECT)
        }
    }

    override fun isIncremental(): Boolean {
        return true
    }

    override fun isCacheable(): Boolean {
        return true
    }

    override fun transform(transformInvocation: TransformInvocation?) {
        val inputs = transformInvocation?.inputs     //For the input of the previous task, get the path of the jar package and class folder from it
        val isIncremental = transformInvocation?.isIncremental  //Whether it is incremental compilation
        val outputProvider = transformInvocation?.outputProvider //Is the output path
        val injectHelper = DoubleTapClassNodeHelper()

        if (!isIncremental!!) {
            outputProvider?.deleteAll()
        }
            transformInvocation?.let {
                for(d in it.inputs) {
                    Log.info("-------------transformInvocation.inpus ${d}----------------------")
                    //jar package File
                    for(jar in d.jarInputs){
                        val dest: File = outputProvider!!.getContentLocation(
                                jar.getFile().getAbsolutePath(),
                                jar.getContentTypes(),
                                jar.getScopes(),
                                Format.JAR)
//                        Copy the modified bytecode to dest,
//                       You can achieve the purpose of interfering with the bytecode during compilation.
                        FileUtils.copyFile(jar.getFile(), dest)
                    }
//                    //dir File
                    for(dir in d.directoryInputs){
                        val dest: File = outputProvider!!.getContentLocation(
                                dir.getFile().getAbsolutePath(),
                                dir.getContentTypes(),
                                dir.getScopes(),
                                Format.DIRECTORY)
                        val dirFile = dir.file

                        if(dirFile.isDirectory){
                            FileUtils.copyDirectory(dirFile, dest)
                            dirFile.walkTopDown().filter { it.isFile }
                                    .forEach {classFile ->
                                        if (classFile.name.endsWith(".class")) {
                                            Log.info("-------------classFile.inpus ${classFile}----------------------")
                                            val bytes = IOUtils.toByteArray(FileInputStream(classFile))

                                            injectHelper.modifyClass(bytes)
                                        }
                                    }
                        }else{

                        }

                    }
                    Log.info("-------------transformInvocation.inpus ${d}----------------------")

                }
            }

    }
}

可以看到,我们的inputs里面又包含了
jarInputs:我们引入的第三方jar包
directoryInputs:我们本地内容的根目录
我们通过outputProvider!!.getContentLocation()获得不同的资源并且过滤其它非.class的文件将其转换为字节数组.这样我们就获取到了该文件的字节码!!!
之后我们对该字节码进行修改即可!虾哥宇宙第一…

2.总结

好了,咋们总算又再进一步,找到我们所需要的资源.
transformInvocation.inputs这里面也是我们的作案对象了。也大致了解了transfrom是一个什么东西,有什么用。。。以及它被遗弃了0_0
接下来就到了大佬们才会操作的东西了…字节码
这里需要你对字节码的编程需要有一定的理解才能对.class文件进行修改.而我们的最终重头戏ASM,也终于上场了…

Android一步步实现无痕埋点(4)-------无尽痛苦

Android一步步实现无痕埋点(3)-------虎躯一震_第2张图片

你可能感兴趣的:(无痕埋点,Android知识,android,gradle,java)