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

一、原理

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容