美文网首页
Android全埋点解决方案(ASM 一 Transform)

Android全埋点解决方案(ASM 一 Transform)

作者: 旺仔_100 | 来源:发表于2021-07-18 17:07 被阅读0次

一、原理

Google从Android Gradle 1.5.0开始,提供了Transform API。通过TransformAPI,允许第三方以插件(Plugin)的形式,在Android应用程序打包成.dex文件之前的编译过程中操作.class文件。我们只要实现一套Transform,去遍历所有.class文件的所有方法,然后进行修改(在特定listener的回调方法中插入埋点代码),最后再对源文件进行替换,即可达到插入代码的目的。

二、Gradle Transform

它是在.class转化为.dex期间用来修改.class文件的一套标准API。比较经典的应用是做字节码插桩、代码注入等。主要功能就是把输入的.class转化成目标字节码文件。

  • TransformInput 输入文件的一个抽象包含两个部分

    • DirectoryInput 集合 是指以源码方式参与项目编译的所有目录结构以及目录下的源文件
    • JarInput 集合 是指以jar包(包含aar)参与编译的所有本地jar以及远程jar包
  • TransformOutoutProvider Transform的输出,通过他可以获取输出路径等。

三、写一个Tranform ,把所有文件拷贝到输出目录

1.创建一个项目
2.创建一个 Android Library module,名称叫做plugin
3.清空plugin/build.gradle里面所有的内容,改成如下所示

apply plugin: 'groovy'
apply plugin: 'maven'


dependencies {

    implementation gradleApi()
    implementation localGroovy()
    implementation 'com.android.tools.build:gradle:3.1.3'
}

repositories {
    jcenter()
}

uploadArchives{
    repositories.mavenDeployer{
        //本地仓库路径,放到项目根目录下的repo的文件为列
        repository(url:uri('../repo'))

        //groupId 自行定义
        pom.groupId = 'com.sensorsdata'
        //artifactId
        pom.artifactId = 'autotrack.android'

        //插件版本号
        pom.version = '1.0.2'
    }
}

4.删除plugin/src/main目录下所有的文件
5.新建groovy目录
插件是groovy语言开发的,需要放到groovy目录下。在groovy目录下创建一个package,比如:com.sensorsdata.analytics.android.plugin。
6.在com.sensorsdata.analytics.android.plugin下创建SensorsAnalyticsTransform.groovy类。

package com.sensorsdata.analytics.android.plugin;

import com.android.build.api.transform.Context
import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.Format
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.utils.FileUtils
import org.apache.commons.codec.digest.DigestUtils
import org.gradle.api.Project

class SensorsAnalyticsTransform extends Transform{
    private static Project project

    public SensorsAnalyticsTransform(Project project){
        this.project = project
    }

    //这个是任务Task的名称
    @Override
    String getName() {
        return "SensorsAnalyticsAutoTrack"
    }

    /**
     * 需要处理的数据类型,有两种枚举类型
     *TransformManager.CONTENT_CLASS  代表处理java的class文件
     * TransformManager.CONTENT_RESOURCES 代表java的资源文件
     * @return
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {


        return  TransformManager.CONTENT_CLASS
    }

    /**
     *
     *  PROJECT 只处理当前项目
     *  SUB——POROJECT 只处理子项目
     *  PROJECT-LOCAL-DEPS  只处理项目中的本地依赖 例如 jar,aar
     *  SUB_PROJECTS_LOCAL_DECPS 只处理子项目的本地依赖
     *  EXTERNAL_LIBRARIES 只处理外部项目
     *  PROVIDED_ONLY 只处理本地或以provided形式引入的依赖库
     *  TESTED_CODE 测试代码
     *  TransformManager.SCOPE_FULL_PROJECT
     * @return
     */
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    //是否增量构建
    @Override
    boolean isIncremental() {
        return false
    }

    static void printCopyRight(){
        println()
        println("#########################################")
        println("#########                     ###########")
        println("#########                     ###########")
        println("#########       AZY           ###########")
        println("#########                     ###########")
        println("#########                     ###########")
        println("#########################################")
        println()
    }

    @Override
    void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
       printCopyRight()
        //Transform的Inputs有两种类型,一种是目录,一种是jar包,要分开遍历
        inputs.each {TransformInput input ->
            //遍历目录
            input.directoryInputs.each { DirectoryInput directoryInput ->
                //获取output目录
                def dest = outputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes,directoryInput.scopes,
                        Format.DIRECTORY)
                //将input目录复制到output目录
                FileUtils.copyDirectory(directoryInput.file,dest)
            }
            //遍历jar
            input.jarInputs.each { JarInput jarInput ->
                //重命名输出文件
                def jarName = jarInput.name
                def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
                if(jarName.endsWith(".jar")){
                    jarName = jarName.substring(0,jarName.length() - 4)
                }
                File copyJarFile = jarInput.file
                //生成输出路径
                def dest = outputProvider.getContentLocation(jarName+md5Name,
                        jarInput.contentTypes,jarInput.scopes,Format.JAR)
                //将input的目录复制到output指定目录
                FileUtils.copyFile(copyJarFile,dest)
            }
        }
    }
}
注意:自定义Transform即使什么都不做,也需要把输入文件拷贝到目标目录下,否则下一个Task就没有TransformInput了。如果我们什么都不做,那么打包的时候apk中缺少.class文件。

7.新建SensorsAnalyticsPlugin类,用来注册SensorsAnalyticsTransform

package com.sensorsdata.analytics.android.plugin

import com.android.build.gradle.AppExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

public class SensorsAnalyticsPlugin implements Plugin<Project>{

    @Override
    void apply(Project project) {
        AppExtension appExtension = project.extensions.findByType(AppExtension.class)
        appExtension.registerTransform(new SensorsAnalyticsTransform(project))
    }
}

8.创建properties文件
在plugin/src/main目录下新建目录resources/META-INF/gradle-plugins(这是三个文件夹),然后此目录下创建一个文件:com.sensorsdata.android.properties,com.sensorsdata.android是用来指定我们的插件名称的。也就是apply plugin 'com.sensorsdata.android'。文件具体类容如下(一般是包名+类名)

implementation-class=com.sensorsdata.analytics.android.plugin.SensorsAnalyticsPlugin

9.执行plugin的uploadArchives任务构建plugin,如下图


uploadArchives.png

执行成功之后可以在项目跟路径下查看repo文件夹


repo.png

10.修改根目录下的build.gradle文件,添加对应的插件(添加备注“//新增”两处)

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
        //新增
        maven{
            url uri('repo')
        } 
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.2'
        //新增
        classpath 'com.sensorsdata:autotrack.android:1.0.2'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

11.在app/build.gradle文件中声明使用插件


apply plugin.png

12.通过执行命令:./gradlew assembleDebug
如果编译没有出错,可以看到Transform中的日志打印,也可以查看对一个的输入文件。


output.png

四、遇到的问题

1.把plugin 写成pulgin
2.把resources/META-INF/gradle-plugins 三个文件夹,写出了一个文件夹resources.META-INF.gradle-plugins 导致app中 apply 插件时候找不到对应的插件。花了我4.5个小时找这个问题。

./gradlew compileDebug --stacktrace. 查看具体报错信息
最后附上源码地址 http://github.com/yangzai100/AutoTrackTransformProject

相关文章

网友评论

      本文标题:Android全埋点解决方案(ASM 一 Transform)

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