ASM+Gradle Transfrom API 實(shí)現(xiàn)編譯期間代碼的修改

ASM 是什么葡缰?

AOP(面向切面編程),是一種編程思想忱反,但是它的實(shí)現(xiàn)方式有很多泛释,比如:APT、AspectJ温算、JavaAssist怜校、ASM 等。


常見的幾種AOP區(qū)別

ASM 和 Javassist類似注竿,也是一個(gè) Java 字節(jié)碼操控框架茄茁。它能被用來動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件巩割,也可以在類被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類行為裙顽。Java class 被存儲(chǔ)在嚴(yán)格格式定義的 .class 文件里,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱宣谈、方法愈犹、屬性以及 Java 字節(jié)碼(指令)。ASM 從類文件中讀入信息后蒲祈,能夠改變類行為甘萧,分析類信息萝嘁,甚至能夠根據(jù)用戶要求生成新類梆掸。

簡單點(diǎn)說,通過 javac 將 .java 文件編譯成 .class 文件牙言,.class 文件中的內(nèi)容雖然不同酸钦,但是它們都具有相同的格式,ASM 通過使用訪問者(visitor)模式咱枉,按照 .class 文件特有的格式從頭到尾掃描一遍 .class 文件中的內(nèi)容卑硫,在掃描的過程中徒恋,就可以對(duì) .class 文件做一些操作了,有點(diǎn)黑科技的感覺

所以ASM 就是一個(gè)字節(jié)碼操作庫欢伏,可以大大降低我們操作字節(jié)碼的難度

Android 的打包過程

android 打包流程

如圖所示是Android打包流程入挣,.java文件->.class文件->.dex文件,只要在紅圈處攔截住硝拧,拿到所有方法進(jìn)行修改完再放行就可以了径筏,而做到這一步也不難,Google官方在Android Gradle的1.5.0 版本以后提供了 Transfrom API, 允許第三方 Plugin 在打包 dex 文件之前的編譯過程中操作 .class 文件障陶,我們做的就是實(shí)現(xiàn)Transform進(jìn)行.class文件遍歷拿到所有方法滋恬,修改完成對(duì)原文件進(jìn)行替換。


對(duì)應(yīng)細(xì)節(jié)圖

原理概述

我們可以自定義一個(gè)Gradle Plugin抱究,然后注冊(cè)一個(gè)Transform對(duì)象恢氯,在tranform方法里,可以分別遍歷目錄和jar包鼓寺,然后我們就可以遍歷當(dāng)前應(yīng)用程序的所有.class文件勋拟,然后在利用ASM框架的相關(guān)API,去加載響應(yīng)的.class 文件侄刽,并解析指黎,就可以找到滿足特定條件的.class文件和相關(guān)方法,最后去修改相應(yīng)的方法以動(dòng)態(tài)插入埋點(diǎn)字節(jié)碼州丹,從而達(dá)到自動(dòng)埋點(diǎn)的效果醋安。

DEMO

本范例嘗試對(duì)點(diǎn)擊android中的普通點(diǎn)擊事件進(jìn)行一個(gè)攔截,并在其中插入代碼墓毒。

1吓揪、創(chuàng)建android工程,只寫一個(gè)簡單點(diǎn)擊事件即可(

代碼..略

2所计、創(chuàng)建plugin lib module

1柠辞、修改plugin的gradle

apply plugin: 'groovy'
apply plugin: 'maven'
dependencies {
    compile gradleApi()
    compile localGroovy()

    compile 'org.ow2.asm:asm:6.0'
    compile 'org.ow2.asm:asm-commons:6.0'
    compile 'org.ow2.asm:asm-analysis:6.0'
    compile 'org.ow2.asm:asm-util:6.0'
    compile 'org.ow2.asm:asm-tree:6.0'
    compileOnly 'com.android.tools.build:gradle:3.2.1', {//這里注意需要保持版本一致,否則會(huì)報(bào)錯(cuò)
        exclude group:'org.ow2.asm'
    }
}
repositories {
    jcenter()
}

//調(diào)試模式下在本地生成倉庫(也可推入自己已有的maven倉庫)
uploadArchives {
    repositories.mavenDeployer {
        //本地倉庫路徑主胧,以放到項(xiàng)目根目錄下的 repo 的文件夾為例
        repository(url: uri('../repo'))

        //groupId 叭首,自行定義
        pom.groupId = 'com.canzhang.android'

        //artifactId
        pom.artifactId = 'bury-point-com.canzhang.plugin'

        //插件版本號(hào)
        pom.version = '1.0.0-SNAPSHOT'
    }
}

2、在main目錄下新建groovy包

groovy 是一種語言踪栋,和java語法比較類似

image.png

3焙格、創(chuàng)建transform類
這個(gè)類的作用就是在被編譯成dex之前能夠攔截到.class文件,然后找到匹配我們需求的夷都,進(jìn)行修改調(diào)整眷唉。

/**
 * Google官方在Android Gradle的1.5.0 版本以后提供了 Transfrom API,
 * 允許第三方 Plugin 在打包 dex 文件之前的編譯過程中操作 .class 文件,
 * 我們做的就是實(shí)現(xiàn)Transform進(jìn)行.class文件遍歷拿到所有方法,修改完成對(duì)原文件進(jìn)行替換冬阳。
 */
class AnalyticsTransform extends Transform {
    private static Project project
    private AnalyticsExtension analyticsExtension

    AnalyticsTransform(Project project, AnalyticsExtension analyticsExtension) {
        this.project = project
        this.analyticsExtension = analyticsExtension
    }

    /**
     * /返回該transform對(duì)應(yīng)的task名稱(編譯后會(huì)出現(xiàn)在build/intermediates/transform下生成對(duì)應(yīng)的文件夾)
     * @return
     */
    @Override
    String getName() {
        return AnalyticsSetting.PLUGIN_NAME
    }

    /**
     * 需要處理的數(shù)據(jù)類型蛤虐,有兩種枚舉類型
     * CLASSES 代表處理的 java 的 class 文件,RESOURCES 代表要處理 java 的資源
     * @return
     */
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * 指 Transform 要操作內(nèi)容的范圍肝陪,官方文檔 Scope 有 7 種類型:
     * 1. EXTERNAL_LIBRARIES        只有外部庫
     * 2. PROJECT                   只有項(xiàng)目內(nèi)容
     * 3. PROJECT_LOCAL_DEPS        只有項(xiàng)目的本地依賴(本地jar)
     * 4. PROVIDED_ONLY             只提供本地或遠(yuǎn)程依賴項(xiàng)
     * 5. SUB_PROJECTS              只有子項(xiàng)目驳庭。
     * 6. SUB_PROJECTS_LOCAL_DEPS   只有子項(xiàng)目的本地依賴項(xiàng)(本地jar)。
     * 7. TESTED_CODE               由當(dāng)前變量(包括依賴項(xiàng))測(cè)試的代碼
     * @return
     */
    @Override
    Set<QualifiedContent.Scope> getScopes() {
        //點(diǎn)進(jìn)去可以看到這個(gè)包含(項(xiàng)目氯窍、項(xiàng)目依賴嚷掠、外部庫)
        //Scope.PROJECT,
        //Scope.SUB_PROJECTS,
        //Scope.EXTERNAL_LIBRARIES
        return TransformManager.SCOPE_FULL_PROJECT
//        return Sets.immutableEnumSet(
//                QualifiedContent.Scope.PROJECT,
//                QualifiedContent.Scope.SUB_PROJECTS)
    }

    @Override
    boolean isIncremental() {//是否增量構(gòu)建
        return false
    }

    //這里需要注意,就算什么都不做荞驴,也需要把所有的輸入文件拷貝到目標(biāo)目錄下不皆,否則下一個(gè)Task就沒有TransformInput了,
    // 如果是此方法空實(shí)現(xiàn),最后會(huì)導(dǎo)致打包的APK缺少.class文件
    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        _transform(transformInvocation.context, transformInvocation.inputs, transformInvocation.outputProvider, transformInvocation.incremental)
    }

    void _transform(Context context, Collection<TransformInput> inputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
        if (!incremental) {
            outputProvider.deleteAll()
        }

        /**Transform 的 inputs 有兩種類型熊楼,一種是目錄霹娄,一種是 jar 包,要分開遍歷 */
        inputs.each { TransformInput input ->
            /**遍歷目錄*/
            input.directoryInputs.each { DirectoryInput directoryInput ->
                /**當(dāng)前這個(gè) Transform 輸出目錄*/
                File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
                File dir = directoryInput.file

                if (dir) {
                    HashMap<String, File> modifyMap = new HashMap<>()
                    /**遍歷以某一擴(kuò)展名結(jié)尾的文件*/
                    dir.traverse(type: FileType.FILES, nameFilter: ~/.*\.class/) {
                        File classFile ->
                            if (AnalyticsClassModifier.isShouldModify(classFile.name, analyticsExtension)) {
                                File modified = AnalyticsClassModifier.modifyClassFile(dir, classFile, context.getTemporaryDir())
                                if (modified != null) {
                                    /**key 為包名 + 類名鲫骗,如:/cn/data/autotrack/android/app/MainActivity.class*/
                                    String ke = classFile.absolutePath.replace(dir.absolutePath, "")
                                    modifyMap.put(ke, modified)//修改過后的放到一個(gè)map中然后在寫回源目錄犬耻,覆蓋原來的文件
                                }
                            }
                    }
                    FileUtils.copyDirectory(directoryInput.file, dest)
                    modifyMap.entrySet().each {
                        Map.Entry<String, File> en ->
                            File target = new File(dest.absolutePath + en.getKey())
                            if (target.exists()) {
                                target.delete()
                            }
                            FileUtils.copyFile(en.getValue(), target)
                            en.getValue().delete()
                    }
                }
            }

            /**遍歷 jar*/
            input.jarInputs.each { JarInput jarInput ->
                String destName = jarInput.file.name

                /**截取文件路徑的 md5 值重命名輸出文件,因?yàn)榭赡芡?會(huì)覆蓋*/
                def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath).substring(0, 8)
                /** 獲取 jar 名字*/
                if (destName.endsWith(".jar")) {
                    destName = destName.substring(0, destName.length() - 4)
                }

                /** 獲得輸出文件*/
                File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)

                def modifiedJar = AnalyticsClassModifier.modifyJar(jarInput.file, context.getTemporaryDir(), true, analyticsExtension)
                if (modifiedJar == null) {
                    modifiedJar = jarInput.file
                }
                FileUtils.copyFile(modifiedJar, dest)
            }
        }
    }

  
}

3、創(chuàng)建插件類

/**
 * 可以通過配置主工程目錄中的gradle.properties 中的
 * canPlugin.disablePlugin字段來控制是否開啟此插件
 */
class AnalyticsPlugin implements Plugin<Project> {
    void apply(Project project) {

        //這個(gè)AnalyticsExtension 以及canPlugin名稱执泰,可以提供我們?cè)谕鈱优渲靡恍﹨?shù)枕磁,從而支持外層擴(kuò)展
        AnalyticsExtension extension = project.extensions.create("canPlugin", AnalyticsExtension)

        //這個(gè)可以讀取工程的gradle.properties 里面的can.disablePlugin 字段,控住是否注冊(cè)此插件
        boolean disableAnalyticsPlugin = false
        Properties properties = new Properties()
        if (project.rootProject.file('gradle.properties').exists()) {
            properties.load(project.rootProject.file('gradle.properties').newDataInputStream())
            disableAnalyticsPlugin = Boolean.parseBoolean(properties.getProperty("disablePlugin", "false"))
        }

        if (!disableAnalyticsPlugin) {
            println("------------您開啟了全埋點(diǎn)插樁插件--------------")
            AppExtension appExtension = project.extensions.findByType(AppExtension.class)
            //注冊(cè)我們的transform類
            appExtension.registerTransform(new com.canzhang.plugin.AnalyticsTransform(project, extension))
        } else {
            println("------------您已關(guān)閉了全埋點(diǎn)插樁插件--------------")
        }
    }
}

到這里插件和gradle的tranform類我們都創(chuàng)建好了术吝,下面需要看該怎么修改我們想修改的類了计济。
4、ASM中的ClassVisitor
ClassVisitor:主要負(fù)責(zé)遍歷類的信息排苍,包括類上的注解沦寂、構(gòu)造方法、字段等等淘衙。
所以我們可以在這個(gè)類中篩選出符合我們條件的類或者方法传藏,然后去修改,實(shí)現(xiàn)我們的目的彤守。
比如我們本例子就是為了找到實(shí)現(xiàn)了View$OnClickListener接口的類毯侦,然后遍歷這個(gè)類,并找到重寫后的onClick(View v)方法具垫。

這里就細(xì)節(jié)貼代碼了侈离,不懂得地方可以看注釋

/**
 * 使用ASM的ClassReader類讀取.class的字節(jié)數(shù)據(jù),并加載類做修,
 * 然后用自定義的ClassVisitor霍狰,進(jìn)行修改符合特定條件的方法,
 * 最后返回修改后的字節(jié)數(shù)組
 */
class AnalyticsClassVisitor extends ClassVisitor implements Opcodes {

//插入的外部類具體路徑
    private String[] mInterfaces
    private ClassVisitor classVisitor
    private String mCurrentClassName

    AnalyticsClassVisitor(final ClassVisitor classVisitor) {
        super(Opcodes.ASM6, classVisitor)
        this.classVisitor = classVisitor
    }

    private
    static void visitMethodWithLoadedParams(MethodVisitor methodVisitor, int opcode, String owner, String methodName, String methodDesc, int start, int count, List<Integer> paramOpcodes) {
        for (int i = start; i < start + count; i++) {
            methodVisitor.visitVarInsn(paramOpcodes[i - start], i)
        }
        methodVisitor.visitMethodInsn(opcode, owner, methodName, methodDesc, false)
    }

    /**
     * 這里可以拿到關(guān)于.class的所有信息饰及,比如當(dāng)前類所實(shí)現(xiàn)的接口類表等
     * @param version 表示jdk的版本
     * @param access 當(dāng)前類的修飾符 (這個(gè)和ASM 和 java有些差異蔗坯,比如public 在這里就是ACC_PUBLIC)
     * @param name 當(dāng)前類名
     * @param signature 泛型信息
     * @param superName 當(dāng)前類的父類
     * @param interfaces 當(dāng)前類實(shí)現(xiàn)的接口列表
     */
    @Override
    void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces)
        mInterfaces = interfaces
        mCurrentClassName = name

        AnalyticsUtils.logD("當(dāng)前的類是:" + name)
        AnalyticsUtils.logD("當(dāng)前類實(shí)現(xiàn)的接口有:" + mInterfaces)
    }

    /**
     * 這里可以拿到關(guān)于method的所有信息,比如方法名燎含,方法的參數(shù)描述等
     * @param access 方法的修飾符
     * @param name 方法名
     * @param desc 方法簽名(就是(參數(shù)列表)返回值類型拼接)
     * @param signature 泛型相關(guān)信息
     * @param exceptions 方法拋出的異常信息
     * @return
     */
    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions)

        String nameDesc = name + desc

        methodVisitor = new AnalyticsDefaultMethodVisitor(methodVisitor, access, name, desc) {

            @Override
            void visitEnd() {
                super.visitEnd()
            }

            @Override
            void visitInvokeDynamicInsn(String name1, String desc1, Handle bsm, Object... bsmArgs) {
                super.visitInvokeDynamicInsn(name1, desc1, bsm, bsmArgs)
            }

            @Override
            protected void onMethodExit(int opcode) {//方法退出節(jié)點(diǎn)
                super.onMethodExit(opcode)
            }

            @Override
            protected void onMethodEnter() {//方法進(jìn)入節(jié)點(diǎn)
                super.onMethodEnter()

                if ((mInterfaces != null && mInterfaces.length > 0)) {
                    //如果當(dāng)前類實(shí)現(xiàn)的接口有View$OnClickListener宾濒,并且當(dāng)前進(jìn)入的方法是onClick(Landroid/view/View;)V
                    //這里如果不知道怎么寫,可以寫個(gè)demo打印一下屏箍,就很快知道了绘梦,這里涉及一些ASM和Java中不同的寫法。
                    if ((mInterfaces.contains('android/view/View$OnClickListener') && nameDesc == 'onClick(Landroid/view/View;)V')) {
                        AnalyticsUtils.logD("插樁:OnClickListener nameDesc:" + nameDesc + " currentClassName:" + mCurrentClassName)
                         //這里就是插代碼邏輯了
                        methodVisitor.visitVarInsn(ALOAD, 1)
                        methodVisitor.visitMethodInsn(INVOKESTATIC, "com/canzhang/asmdemo/sdk/MySdk", "onViewClick", "(Landroid/view/View;)V", false)
                    }
                }
            }

            @Override
            AnnotationVisitor visitAnnotation(String s, boolean b) {
                return super.visitAnnotation(s, b)
            }
        }
        return methodVisitor
    }
}

要插入的代碼

public class MySdk {
    /**
     * 常規(guī)view 被點(diǎn)擊赴魁,自動(dòng)埋點(diǎn)
     *
     * @param view View
     */
    @Keep
    public static void onViewClick(View view) {
        Log.e("Test","成功插入 666666:"+view);
    }
}

核心代碼分析

            @Override
            protected void onMethodEnter() {//方法進(jìn)入節(jié)點(diǎn)
                super.onMethodEnter()

                if ((mInterfaces != null && mInterfaces.length > 0)) {
                    //如果當(dāng)前類實(shí)現(xiàn)的接口有View$OnClickListener卸奉,并且當(dāng)前進(jìn)入的方法是onClick(Landroid/view/View;)V
                    //這里如果不知道怎么寫,可以寫個(gè)demo打印一下颖御,就很快知道了榄棵,這里涉及一些ASM和Java中不同的寫法。
                    if ((mInterfaces.contains('android/view/View$OnClickListener') && nameDesc == 'onClick(Landroid/view/View;)V')) {
                        AnalyticsUtils.logD("插樁:OnClickListener nameDesc:" + nameDesc + " currentClassName:" + mCurrentClassName)

                        //這里就是插代碼邏輯了
                        methodVisitor.visitVarInsn(ALOAD, 1)
                        methodVisitor.visitMethodInsn(INVOKESTATIC, "com/canzhang/asmdemo/sdk/MySdk", "onViewClick", "(Landroid/view/View;)V", false)
                    }
                }
            }

當(dāng)方法進(jìn)入的時(shí)候潘拱,如果判斷符合我們的條件疹鳄,則進(jìn)行方法插入。

  • 問題1:nameDesc為啥這么寫芦岂。
    nameDesc == 'onClick(Landroid/view/View;)V'為什么是這樣寫的瘪弓,后面的V是個(gè)什么東東。
    首先grovvy中是可以使用==號(hào)來判斷字符串是否相等的禽最,其次方法名是和java有一些差異腺怯,這個(gè)我們可以深入去了解這些差異學(xué)習(xí),就可以理解為何這么寫川无。還有一種簡單的方法瓢喉,可以直接打印日志的方式來快速知道我們需要的方法應(yīng)該怎么寫。
    入?yún)?duì)應(yīng)關(guān)系表
    image.png

例子

image.png

  • 問題2: 這插入的是什么鬼舀透,怎么有點(diǎn)看不懂栓票,如何知道怎么插。
    ASM就是幫助我們操作字節(jié)碼的愕够,封裝了一些api可供我們調(diào)用走贪,這個(gè)轉(zhuǎn)換可以使用一個(gè)插件 ASM Bytecode outline ,android studio 可以下載此插件(參考教程
    )。

5惑芭、創(chuàng)建配置文件
按照如圖所示創(chuàng)建對(duì)應(yīng)路徑和配置文件com.canzhang.plugin.properties坠狡,這里需要注意

  • 配置文件的名字:com.canzhang.plugin就是插件的名稱,就是稍后我們生成插件后遂跟,引用此插件的module需要聲明的那個(gè):apply plugin: 'com.canzhang.plugin'
  • 配置內(nèi)容就是我們插件的的包名和類名
# 此文件名為插件引用名逃沿,下面這行則是對(duì)應(yīng)的插件路徑
implementation-class=com.canzhang.plugin.AnalyticsPlugin
image.png

6婴渡、然后我們就可以運(yùn)行構(gòu)建plugin了


image.png

構(gòu)建好之后我們就可以在本地看到這樣一個(gè)文件夾


image.png

這里如果想開放此插件給到其他工程使用,則可以提交repo到githup,然后按照下方配置流程進(jìn)行配置(步驟7)凯亮,另外需要額外配置倉庫地址

 maven { url "https://raw.githubusercontent.com/gudujiucheng/ASMDemo/master/repo" }

其中:https://raw.githubusercontent.com/為固定路徑边臼,gudujiucheng為Github用戶名,ASMDemo為項(xiàng)目名假消,master/repo為倉庫相對(duì)路徑柠并。

7、使用插件

  • 項(xiàng)目gradle配置(配置本地倉庫富拗、并引入插件)
buildscript {
    
    repositories {
        google()
        jcenter()
        //本地調(diào)試倉庫
        maven {
            url uri('repo')
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.0'

        //引用插件
        classpath 'com.canzhang.android:canzhang_plugin:1.0.0-SNAPSHOT'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
  • 主module gradle 配置
apply plugin: 'com.canzhang.plugin'

然后運(yùn)行編譯之后臼予,就可以看到我們插樁的代碼了。
插樁前的代碼:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.tv_test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "普通點(diǎn)擊事件", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

如下圖所示啃沪,可以看到具體插樁后的字節(jié)碼粘拾,可以點(diǎn)擊查看(注意如果插樁的class是jar包內(nèi)的,則需要自行反編譯jar進(jìn)行查看(推薦一個(gè)簡單易用的反編譯工具:https://github.com/linchaolong/ApkToolPlus)创千,或者調(diào)整插件半哟,使輸出一份class到指定文件夾查看)。

插樁后

更多細(xì)節(jié)待續(xù)....

注意事項(xiàng):

  • 沒有生成插件之前签餐,要把依賴去掉寓涨,不然跑不起來
    主module屏蔽
apply plugin: 'com.canzhang.plugin'

主工程的gradle屏蔽

classpath 'com.canzhang.android:canzhang_plugin:1.0.0-SNAPSHOT'

屏蔽之后先build項(xiàng)目成功后,在觸發(fā)生成插件氯檐,然后在放開屏蔽的兩項(xiàng)戒良,就可以了


生成插架

生成的插件
  • 關(guān)于混淆:關(guān)于混淆可以不用擔(dān)心」谏悖混淆其實(shí)是個(gè)ProguardTransform糯崎,在自定義的Transform之后執(zhí)行。

  • 插件插入不存在的代碼也是不會(huì)報(bào)錯(cuò)的河泳,因?yàn)槭窃诰幾g后插入的沃呢,直到運(yùn)行的時(shí)候才會(huì)報(bào)錯(cuò),所以要注意插入代碼的正確性拆挥。

  • 出現(xiàn)莫名其妙的錯(cuò)誤薄霜,如RuntimeException
    這里asm不同版本的api,有時(shí)候會(huì)做api版本限制纸兔,要檢查下惰瓜,自己的api版本是否錯(cuò)誤:(發(fā)生這些錯(cuò)誤的原因,主要是因?yàn)槲覀儗懰赖陌姹竞嚎螅晚?xiàng)目實(shí)際應(yīng)用的asm版本不相同導(dǎo)致的)

    我們的代碼

比如下面這些版本限制觸發(fā)的異常:(這里只是拋出了異常崎坊,并沒有很細(xì)致的提示,所以需要留意看錯(cuò)誤日志)


asm api

asm api

其他細(xì)節(jié)

  • 上文是用groovy來寫的(groovy的編譯錯(cuò)誤提示不是很好洲拇,建議用其他語言寫)奈揍,也可以使用java或者kotlin來寫曲尸,可以選擇自己熟悉的語法,這幾種語言最后都會(huì)轉(zhuǎn)換成字節(jié)碼男翰,通過jvm來執(zhí)行另患。
  • 如果用于項(xiàng)目,可以考慮參考其他框架進(jìn)行一些增量編譯和多線程并發(fā)處理文件等方面的優(yōu)化奏篙,提高編譯速度,可參考:https://github.com/Leaking/Hunter

參考文章:

本文主要是用于記錄迫淹,參考自神策全埋點(diǎn)教程
http://www.reibang.com/p/9039a3e46dbc
http://www.reibang.com/p/c2c1d350d245
http://www.reibang.com/p/16ed4d233fd1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秘通,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敛熬,更是在濱河造成了極大的恐慌肺稀,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件应民,死亡現(xiàn)場(chǎng)離奇詭異话原,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)诲锹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門繁仁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人归园,你說我怎么就攤上這事黄虱。” “怎么了庸诱?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵捻浦,是天一觀的道長。 經(jīng)常有香客問我桥爽,道長朱灿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任钠四,我火速辦了婚禮枫匾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘获茬。我一直安慰自己砌些,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布朵耕。 她就那樣靜靜地躺著炫隶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阎曹。 梳的紋絲不亂的頭發(fā)上伪阶,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天煞檩,我揣著相機(jī)與錄音,去河邊找鬼栅贴。 笑死斟湃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的檐薯。 我是一名探鬼主播凝赛,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼坛缕!你這毒婦竟也來了墓猎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤赚楚,失蹤者是張志新(化名)和其女友劉穎毙沾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宠页,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡左胞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了举户。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烤宙。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖俭嘁,靈堂內(nèi)的尸體忽然破棺而出门烂,到底是詐尸還是另有隱情,我是刑警寧澤兄淫,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布屯远,位于F島的核電站,受9級(jí)特大地震影響捕虽,放射性物質(zhì)發(fā)生泄漏慨丐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一泄私、第九天 我趴在偏房一處隱蔽的房頂上張望房揭。 院中可真熱鬧,春花似錦晌端、人聲如沸捅暴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蓬痒。三九已至,卻和暖如春漆羔,著一層夾襖步出監(jiān)牢的瞬間梧奢,已是汗流浹背狱掂。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亲轨,地道東北人趋惨。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像惦蚊,于是被迫代替她去往敵國和親器虾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容