Code Monkey home page Code Monkey logo

androidautotrack's Introduction

AndroidAutoTrack

State-of-the-art Shitcode

本项目主要就是给大家一个参考学习的demo而已,主要是打算简化学习gradle插件的成本,以及对于android transform的一次抽象,将增量更新等等进行一次抽象,以方便大家学习开发。

技术栈罗列

  1. compose building 混合编译,提供plugin当场调试能力
  2. Transform dependOn 依赖,通过拓扑排序解决依赖问题
  3. 通过auto-service 动态组合多个plugin
  4. 自动化埋点demo,透传参数,以及对于onHiddenChange方法覆盖,可以拿来做fragment可视化逻辑判断 (由于都是字节码操作,所以开发人员不需要感知到)
  5. 双击保护优化,解决了d8导致的java8 lambda,动态引用问题
  6. thread pool 构造动态替换成共享
  7. 简单的tree api 使用demo
  8. 多线程字节码操作
  9. transform 增量编译抽象

buildSrc 优化

之前通过buildSrc形式重构项目,不需要本地推aar,同时module可以被同一个buildSrc关联上,方便调试和代码上传。

ComposeBuilding 优化

通过项目组合编译的方式重构,同样不需要本地推aar,保留了上述所有的优点的同时,由于buildSrc是一个优先编译的工程,所以无法使用项目内的build.gradle,而ComposeBuild则由于是一个独立Project,所以可以使用当前下面的所有共用属性。

协程 路由 组件化 1+1+1>3 | 掘金年度征文,文章内有对ComposeBuilding的更详细的介绍和使用。

Tips 小贴士

直接编译你的项目,观察项目下的build/imtermediates/transform/ 文件夹下面,因为class类android studio已经帮你完成了转化,所以无需担心看不懂的问题。

最好各位可以安装一个ASM ByteCode Viewer插件,可以辅助大家快速阅读对应代码。

仔细观察编译前java代码和编译后class文件的差别。如果有插入的代码那么代表该插件已经编织代码成功。

dejavu x

这次牛逼了,通过最简单的serviceloader机制,把多个plugin通过DI的形式收拢到一起,方便多插件组合接入。

class MultiPlugin : Plugin<Project> {

    override fun apply(project: Project) {
        // 菜虾版本byteX beta版本
        val providers = ServiceLoader.load(PluginProvider::class.java).toList()
        providers.forEach {
            project.plugins.apply(it.getPlugin())
        }
    }
}

只要把不同的插件的classpath 加载进来,之后在主工程下声明你的合并插件即可直接使用。

依赖任务

这次通过有向图的方式重新计算了下plugin之间的依赖关系,这样就可以指定transform的执行顺序了。

实现参考了gradle的CachingDirectedGraphWalker,有兴趣的大佬可以自行阅读,这部分说实话,算法我不是特别熟悉。

AutoTrackPlugin 安卓无痕埋点Demo

以前使用的是ClassVisitor,由于无痕埋点相关其实有上下文以及传输数据等等的要求,所以该方案废弃了。

当前通过ClassNode方式实现,ClassNode是类似Ast语法树的一种ClassVisitor的实现类,可以通过主动访问的方式,去对当前你需要变更的类进行快速访问逻辑判断,同时由于在外层判断逻辑,所以可以更方便的添加代码组合等,让asm操作更简化。

通过编译时检索代码中是否实现了View.OnClickListener接口,然后在onClick方法尾部插入代码打点代码。

如何将参数传递给打点代码

通过标识注解的方式可以将外部的参数直接传输给埋点事件,这样就可以更丰富简单的拓展无痕埋点系统。

View.OnClickListener listener=new View.OnClickListener() {
            @Test
            private Entity mdata;

            @Override
            public void onClick(View v) {
                mdata = new Entity();
                Log.i("MainActivity", v.toString());
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });

完成Fragment hidden开发

后续会补充上给fragment activity 生命周期方法补充增强

double tap plugin 双击优化

新增功能

可以让当前双击保护只作用于Module下面,而不是App下面,让同学可以热拔插这部分代码,因为双击保护其实更针对模块开发同学,所以可以直接使用该插件,同时该插件也会对上传AAr生效,放心使用。

原理和无痕埋点相似,当前还是保留以前开发无痕埋点的visitor形式。

通过ClassVisitor的机制访问所有View.OnClickListener的子类,然后插入双击优化的代码块。但是插入的是一个类,所以有一部分逻辑代码,织入操作更为复杂,可以使用gradle插件去更好的学习。

InitBlockVisitor 这个类MethodVisitor会给当前类的init 添加一个成员变量。DoubleTapCheck doubleTap = new DoubleTapCheck(); 然后在onClick 方法前添加一个逻辑判断。

使用原则

根目录build 添加插件

buildscript {
   
   repositories {
       maven {
           url "file://${rootDir.absolutePath}/.repo"
       }
       google()
       jcenter()

   }
   dependencies {
       classpath 'com.kronos.doubleTap:double_tap_plugin:0.1.3'
   }
}

app 运行工程下引入插件 同时将你需要插入的代码的className 和functionname 标记在Extension中

apply plugin: 'doubleTap'

doubleTab {
   injectClassName = "com.a.doubleclickplugin.DoubleTapCheck"
   injectFunctionName = "isNotDoubleTap"
}

Thread Hook plugin 线程hook更换

通过字节码访问,查找项目内的线程池构造等,发现之后替换成自定义的线程构造。

方法

通过ASM的ClassNode 的方式读取了当前类的所有构造函数,然后判断当前的执行内容是否是需要变魔改的类,如果是则替换他的desc owner name相关。

class ThreadAsmHelper : AsmHelper {
    @Throws(IOException::class)
    override fun modifyClass(srcClass: ByteArray): ByteArray {
        val classNode = ClassNode(Opcodes.ASM5)
        val classReader = ClassReader(srcClass)
        //1 将读入的字节转为classNode
        classReader.accept(classNode, 0)
        //2 对classNode的处理逻辑
        val iterator: Iterator<MethodNode> =
            classNode.methods.iterator()
        while (iterator.hasNext()) {
            val method = iterator.next()
            method.instructions?.iterator()?.forEach {
                if (it.opcode == Opcodes.INVOKESTATIC) {
                    if (it is MethodInsnNode) {
                        it.hookExecutors(classNode, method)
                    }
                }
            }
        }
        val classWriter = ClassWriter(0)
        //3  将classNode转为字节数组
        classNode.accept(classWriter)
        return classWriter.toByteArray()
    }

    private fun MethodInsnNode.hookExecutors(classNode: ClassNode, methodNode: MethodNode) {
        when (this.owner) {
            EXECUTORS_OWNER -> {
                info("owner:${this.owner}  name:${this.name} ")
                ThreadPoolCreator.poolList.forEach {
                    if (it.name == this.name && this.name == it.name && this.owner == it.owner) {
                        this.owner = Owner
                        this.name = it.methodName
                        this.desc = it.replaceDesc()
                        info("owner:${this.owner}  name:${this.name} desc:${this.desc} ")
                    }
                }

            }
        }
    }
}

最后在编译阶段该类就会被替换成我们想要的类,举个例子Executors.newSingleThreadExecutor();变更成TestIOThreadExecutor.getThreadPool();

升级更新

多线程操作字节码

base plugin 代码升级,使用多线程优化,讲字节码操作执行在线程中,之后在主函数等待所有task执行完成之后在结束。

base plugin 主要是辅助后续有兴趣的同学可以快速的进行transform开发学习,在当前类基础上,可以无视繁琐的增量编译和额外的文件拷贝操作,只专注于Asm的学习。

androidautotrack's People

Contributors

leifzhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

androidautotrack's Issues

大佬,clone下来项目,没跑起来,build报错了

Task :app:dexBuilderDebug
AGPBI: {"kind":"error","text":"Type com.wallstreetcn.sample.adapter.OtherTestViewHolder$1 is defined multiple times: C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\35\com\wallstreetcn\sample\adapter\OtherTestViewHolder$1.class, C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\35\temp\com\wallstreetcn\sample\adapter\OtherTestViewHolder$1.class","sources":[{"file":"C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\35\com\wallstreetcn\sample\adapter\OtherTestViewHolder$1.class"}],"tool":"D8"}
AGPBI: {"kind":"error","text":"Type com.wallstreetcn.sample.adapter.Test is defined multiple times: C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\com\wallstreetcn\sample\adapter\Test.class, C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\temp\com\wallstreetcn\sample\adapter\Test.class","sources":[{"file":"C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\com\wallstreetcn\sample\adapter\Test.class"}],"tool":"D8"}
AGPBI: {"kind":"error","text":"Type com.wallstreetcn.sample.App is defined multiple times: C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\com\wallstreetcn\sample\App.class, C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\temp\com\wallstreetcn\sample\App.class","sources":[{"file":"C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\com\wallstreetcn\sample\App.class"}],"tool":"D8"}
AGPBI: {"kind":"error","text":"Type com.wallstreetcn.sample.utils.TestIOThreadExecutor$Companion$THREAD_POOL_SHARE$2 is defined multiple times: C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\com\wallstreetcn\sample\utils\TestIOThreadExecutor$Companion$THREAD_POOL_SHARE$2.class, C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\temp\com\wallstreetcn\sample\utils\TestIOThreadExecutor$Companion$THREAD_POOL_SHARE$2.class","sources":[{"file":"C:\Users\sxw\Desktop\AndroidAutoTrack\app\build\intermediates\transforms\NewAutoTackTransform\debug\36\com\wallstreetcn\sample\utils\TestIOThreadExecutor$Companion$THREAD_POOL_SHARE$2.class"}],"tool":"D8"}

多次点击,防抖动的插件相关问题

使用了你的点击防抖动插件,发现对Java有效,对kotlin无效。我的项目是Java和kotlin混合开发的。请问有什么解决方法,是不是没有适配kotlin?

前后插件范围不同,导致 dependOn() 排序报错

Unexpected scopes found in folder '*****/app/build/intermediates/transforms/DoubleTapTransform/debug'. Required: EXTERNAL_LIBRARIES. Found: EXTERNAL_LIBRARIES, PROJECT, SUB_PROJECTS

DoubleTapTransform 范围: Scope.PROJECT, Scope.SUB_PROJECTS, Scope.EXTERNAL_LIBRARIES
TimeLogTransform 范围: Scope.PROJECT, Scope.SUB_PROJECTS

想让 DoubleTapTransform(范围大)执行在 TimeLogTransform(范围小)之前

请教一个关于Asm捕捉全埋点时机的问题

我最近在看Android全埋点那本书,我按照书上的方式,实现了最近的按钮事件捕捉。

然后在我深入测试的过程中,我发现按钮的Click事件在Kotlin中是存在两种表达:

      //第一种
      toolbar.setOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                Log.d("toolbar", "test")
                Log.d("toolbar", "test")
                Log.d("toolbar", "test")
                Log.d("toolbar", "test")
            }

        })
       //第二种
        toolbar.setOnClickListener {
            Log.d("toolbar", "test")
            Log.d("toolbar", "test")
            Log.d("toolbar", "test")
            Log.d("toolbar", "test")
        }

第一种情况下 我只需要匹配View$OnClickListener 匿名内部类以及onClick以及方法签名就可以

但是第二种我发现编译出来内容就不是内部类了,直接使用Kotlin的Function1来替代了,所以之前的捕捉逻辑就失效了。

最近一直在看你的插件和神策插件代码,但是由于刚开始接触还是看不明白。所以想请教下作者在插件中是如何实现这块内容的

大佬 请教一个 ASM 问题

看了该项目之后启发很大自己学习扩展了一下,用 ASM 做了一些其他的尝试。

现在用ASM做一个简单的 路由框架 遇到了一些问题:

想要根据当前的 ClassReaderClassNode 判断当前 ClassActivityFragment、或者是其他类
(java 中的 instanceof Activity instanceof Fragment 操作),但是一直没有找到解决办法。

ClassReader.superName ClassNode.superName 只有当前父类 多级的情况无法满足。

求大佬指点!!!

请教一个Asm插装的问题

你的库很赞
比如我想要替换一行代码如下:

原始代码: A a = new A(param1,param2,....),

替换成: A a = B.newC(params1,param2,....) , 其中B中的newC方法是静态方法,class C extends A

使用asm core api该如何做?

重名名输出文件,因为可能同名,会覆盖

在 baseTransform 类中看到这段代码:

/* 重名名输出文件,因为可能同名,会覆盖*/
                    val hexName = DigestUtils.md5Hex(jarInput.file.absolutePath).substring(0, 8)
                    if (destName.endsWith(".jar")) {
                        destName = destName.substring(0, destName.length - 4)
                    }
                    /*获得输出文件*/
                    val dest = outputProvider!!.getContentLocation(
                            destName + "_" + hexName,
                            jarInput.contentTypes, jarInput.scopes, Format.JAR
                    )

我在项目中也碰到到 jar 名字重复的问题,主要是 classes.jar 可能是重复的

///直接使用 jar 的全路径作为名字可以吗?
                    val dest = outputProvider!!.getContentLocation(
                            jarInput.file.absolutePath,
                            jarInput.contentTypes, jarInput.scopes, Format.JAR
                    )

用这种方式可以吗? 好像不一定要获取文件的 md5,路径是唯一的就能确定了

你好,请问个问题

package com.kronos.plugin.thread.visitor

class ThreadAsmHelper : AsmHelper {
  
    private fun MethodInsnNode.hookExecutors() {
        when (this.owner) {
            EXECUTORS_OWNER -> {
                ThreadPoolCreator.poolList.forEach {
                    if (it.name == this.name && this.name == it.name && this.owner == it.owner) {
                        this.owner = Owner
                        this.name = it.methodName
                        this.desc = it.replaceDesc()
                    }
                }
            }
        }
    }
}

这一行代码

  if (it.name == this.name && this.name == it.name && this.owner == it.owner) {
.........
}

这一段判断条件中第二个是不是应该 this.desc == it.desc

第二个问题:那个ThreadPoolCreator的那个集合,我看你就写了5个元素在里面,而Executors这个类中有8个静态方法返回Executors,这样插桩后,会不会造成部分丢失。

最后一个问题:如果第三方使用自定义ThreadPool构造方法,应该也没法替换了吧。

只是我的一些想法,谢谢作者的这个demo,让我学习如何ASM插桩。

image

kotlin 点击防抖 Lambda 中使用 try/catch 报错: com.android.tools.r8.internal.r8: Different stack heights at jump target: 0 != 1

kotlin 1.6.10

问题代码:

    findViewById<View>(R.id.textview2).setOnClickListener {
        try {
            Log.e("test", ">>>>>>>>> onClick 2 ")
            // return@setOnClickListener
        } catch (e: Exception) {
            e.printStackTrace()
            //return@setOnClickListener
        }
    }

上面代码任意一个 return 放开不会有问题
使用setOnClickListener(object : View.OnClickListener {}) 也不会出现问题

    instructions?.insertBefore(
        firstNode, MethodInsnNode(
            INVOKEVIRTUAL,
            DoubleTabConfig.ByteCodeInjectClassName,
            DoubleTabConfig.ByteCodeInjectFunctionName,
            if (timeCheck != null) "(I)Z" else "()Z",
            false
        )
    )
    val labelNode = LabelNode(Label())
    instructions?.insertBefore(firstNode, JumpInsnNode(IFNE, labelNode))
    instructions?.insertBefore(firstNode, InsnNode(RETURN))
    instructions?.insertBefore(firstNode, labelNode)

应该是 JumpInsnNode 的问题 ,不知道怎么解决。 求大佬指点

ASM 语法

大佬当初是怎么学习 ASM 语法的了

gradle7.0实现自定义插件报错

报错内容:
An exception occurred applying plugin request [id: 'scan-privacyapi']

Failed to apply plugin 'scan-privacyapi'.
Could not find method onVariants() for arguments [com.zhx.plugin.privacy.PrivacyApiPlugin$_apply_closure1@25be3b51] on extension 'androidComponents' of type com.android.build.gradle.internal.plugins.AppPlugin$ApplicationAndroidComponentsExtensionImplCompat.
部分源代码:
def androidComponents = project.extensions.getByType(AndroidComponentsExtension.class)

    androidComponents.onVariants { variant ->
        variant.transformClassesWith(PrivacyApiTraceTransformNew.class,
                InstrumentationScope.ALL) {
            it.writeToStdout.set(true)
        }
        variant.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
    }

我用groovy写的,不知道错在哪里,请指教!

AsM 替换了找不到类

参考你修改字节的代码,我替换指定的静态常量为另外一个,但是很奇怪的是,一旦替换,运行之后就说找不到类。
不是替换的类找不到,是Activity 找不到,应该是整个class 都不对了,代码如下:

private static byte[] aaa(FileInputStream fileInputStream) throws Exception {
        ClassNode classNode = new ClassNode(Opcodes.ASM6);
        ClassReader classReader = new ClassReader(fileInputStream);

//        //1 将读入的字节转为classNode
        classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
        //2 对classNode的处理逻辑
        //noinspection unchecked
        for (MethodNode method : (Iterable<MethodNode>) classNode.methods) {
            //noinspection unchecked
            ListIterator<AbstractInsnNode> iterator1 = method.instructions.iterator();
            iterator1.forEachRemaining(it -> {
                if (it instanceof FieldInsnNode) {
                    FieldInsnNode fieldInsnNode = (FieldInsnNode) it;
                    //fieldInsnNode: org.objectweb.asm.tree.FieldInsnNode@630d29b8
                    // name: MODEL
                    // owner: android/os/Build
                    // desc Ljava/lang/String;
                    // type:4
                    // opcode:178
                    if (fieldInsnNode.owner.equals("android/os/Build") && fieldInsnNode.name.equals("MODEL")) {
                        fieldInsnNode.owner = "com/siyehua/replacefield.ABC";
                        fieldInsnNode.name = "MODEL";//todo
                    }
                    System.out.println("fieldInsnNode: " + fieldInsnNode.toString()
                            + " name:" + fieldInsnNode.name
                            + " owner:" + fieldInsnNode.owner
                            + " desc:" + fieldInsnNode.desc
                            + " type:" + it.getType()
                            + " opcode:" + it.getOpcode());


                }
                System.out.println("it: " + it + " type:" + it.getType() + " opcode:" + it.getOpcode());
            });
        }
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//        //3  将classNode转为字节数组
        classNode.accept(classWriter);
        return classWriter.toByteArray();
    }

其中

   if (fieldInsnNode.owner.equals("android/os/Build") && fieldInsnNode.name.equals("MODEL")) {
                        fieldInsnNode.owner = "com/siyehua/replacefield.ABC";
                        fieldInsnNode.name = "MODEL";//todo
                    }

这段代码如果执行了,运行 apk 就会报错,各种类找不到。
如果不执行,就没有什么事

找不到类,是姿势不对吗?

java.lang.NoClassDefFoundError: Failed resolution of: Lcom/a/doubleclickplugin/DoubleTapCheck;

引用情况:
classpath 'com.kronos.doubleTap:double_tap_plugin:0.1.5'

apply plugin: 'doubleTap'

doubleTab {
injectClassName = "com.a.doubleclickplugin.DoubleTapCheck"
injectFunctionName = "isNotDoubleTap"
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.