{"id":14979942,"url":"https://github.com/leifzhang/androidautotrack","last_synced_at":"2025-05-16T18:05:41.514Z","repository":{"id":38345936,"uuid":"182066527","full_name":"Leifzhang/AndroidAutoTrack","owner":"Leifzhang","description":"Android Asm 插桩 教学","archived":false,"fork":false,"pushed_at":"2022-09-13T04:12:35.000Z","size":836,"stargazers_count":1060,"open_issues_count":3,"forks_count":148,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-03T17:12:31.247Z","etag":null,"topics":["android","asm","gradle","gradle-plugin","kotlin","tracker"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Leifzhang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-18T10:22:18.000Z","updated_at":"2025-03-11T05:32:41.000Z","dependencies_parsed_at":"2022-07-11T02:51:23.379Z","dependency_job_id":null,"html_url":"https://github.com/Leifzhang/AndroidAutoTrack","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leifzhang%2FAndroidAutoTrack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leifzhang%2FAndroidAutoTrack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leifzhang%2FAndroidAutoTrack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Leifzhang%2FAndroidAutoTrack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Leifzhang","download_url":"https://codeload.github.com/Leifzhang/AndroidAutoTrack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248598065,"owners_count":21131008,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","asm","gradle","gradle-plugin","kotlin","tracker"],"created_at":"2024-09-24T14:00:58.344Z","updated_at":"2025-04-12T16:39:11.492Z","avatar_url":"https://github.com/Leifzhang.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AndroidAutoTrack\n\n[![State-of-the-art Shitcode](https://img.shields.io/static/v1?label=State-of-the-art\u0026message=Shitcode\u0026color=7B5804)](https://github.com/trekhleb/state-of-the-art-shitcode)\n\n\n本项目主要就是给大家一个参考学习的demo而已，主要是打算简化学习gradle插件的成本，以及对于`android transform`的一次抽象，将增量更新等等进行一次抽象，以方便大家学习开发。\n\n# 技术栈罗列\n\n1. compose building  混合编译，提供plugin当场调试能力\n2. Transform dependOn 依赖，通过拓扑排序解决依赖问题\n3. 通过`auto-service` 动态组合多个plugin\n4. 自动化埋点demo，透传参数，以及对于onHiddenChange方法覆盖，可以拿来做fragment可视化逻辑判断 (由于都是字节码操作，所以开发人员不需要感知到)\n5. 双击保护优化，解决了d8导致的java8 lambda，动态引用问题\n6. thread pool 构造动态替换成共享\n7. 简单的tree api 使用demo\n8. 多线程字节码操作\n9. transform 增量编译抽象\n\n## ~~buildSrc 优化~~\n\n~~之前通过buildSrc形式重构项目，不需要本地推aar，同时module可以被同一个buildSrc关联上，方便调试和代码上传。~~\n\n## ComposeBuilding 优化\n\n通过项目组合编译的方式重构，同样不需要本地推aar，保留了上述所有的优点的同时，由于buildSrc是一个优先编译的工程，所以无法使用项目内的build.gradle,而`ComposeBuild`则由于是一个独立Project，所以可以使用当前下面的所有共用属性。\n\n[协程 路由 组件化 1+1+1\u003e3 | 掘金年度征文](https://juejin.cn/post/6908232077200588814)，文章内有对`ComposeBuilding`的更详细的介绍和使用。\n\n## Tips 小贴士\n\n直接编译你的项目,观察项目下的build/imtermediates/transform/ 文件夹下面，因为class类android studio已经帮你完成了转化，所以无需担心看不懂的问题。\n\n最好各位可以安装一个ASM ByteCode Viewer插件，可以辅助大家快速阅读对应代码。\n\n仔细观察编译前java代码和编译后class文件的差别。如果有插入的代码那么代表该插件已经编织代码成功。\n\n## dejavu x\n\n这次牛逼了，通过最简单的serviceloader机制，把多个plugin通过DI的形式收拢到一起，方便多插件组合接入。\n\n```java\nclass MultiPlugin : Plugin\u003cProject\u003e {\n\n    override fun apply(project: Project) {\n        // 菜虾版本byteX beta版本\n        val providers = ServiceLoader.load(PluginProvider::class.java).toList()\n        providers.forEach {\n            project.plugins.apply(it.getPlugin())\n        }\n    }\n}\n\n```\n\n只要把不同的插件的classpath 加载进来，之后在主工程下声明你的合并插件即可直接使用。\n\n\n\n### 依赖任务\n\n这次通过有向图的方式重新计算了下plugin之间的依赖关系，这样就可以指定transform的执行顺序了。\n\n实现参考了gradle的`CachingDirectedGraphWalker`，有兴趣的大佬可以自行阅读，这部分说实话，算法我不是特别熟悉。\n\n\n## AutoTrackPlugin 安卓无痕埋点Demo\n\n~~以前使用的是`ClassVisitor`,由于无痕埋点相关其实有上下文以及传输数据等等的要求，所以该方案废弃了。~~\n\n当前通过`ClassNode`方式实现，ClassNode是类似Ast语法树的一种`ClassVisitor`的实现类，可以通过主动访问的方式，去对当前你需要变更的类进行快速访问逻辑判断，同时由于在外层判断逻辑，所以可以更方便的添加代码组合等，让asm操作更简化。\n\n通过编译时检索代码中是否实现了View.OnClickListener接口,然后在onClick方法尾部插入代码打点代码。\n\n### 如何将参数传递给打点代码\n\n通过标识注解的方式可以将外部的参数直接传输给埋点事件，这样就可以更丰富简单的拓展无痕埋点系统。\n\n```java\nView.OnClickListener listener=new View.OnClickListener() {\n            @Test\n            private Entity mdata;\n\n            @Override\n            public void onClick(View v) {\n                mdata = new Entity();\n                Log.i(\"MainActivity\", v.toString());\n                Intent intent = new Intent();\n                intent.setClass(MainActivity.this, SecondActivity.class);\n                startActivity(intent);\n            }\n        });\n```\n\n### 完成Fragment hidden开发\n\n后续会补充上给fragment activity 生命周期方法补充增强\n\n## double tap plugin 双击优化\n\n### 新增功能\n\n可以让当前双击保护只作用于Module下面，而不是App下面，让同学可以热拔插这部分代码，因为双击保护其实更针对模块开发同学，所以可以直接使用该插件，同时该插件也会对上传AAr生效，放心使用。\n\n原理和无痕埋点相似，当前还是保留以前开发无痕埋点的visitor形式。\n\n通过`ClassVisitor`的机制访问所有View.OnClickListener的子类，然后插入双击优化的代码块。但是插入的是一个类，所以有一部分逻辑代码，织入操作更为复杂，可以使用gradle插件去更好的学习。\n\n`InitBlockVisitor` 这个类MethodVisitor会给当前类的init 添加一个成员变量。`DoubleTapCheck doubleTap = new DoubleTapCheck();` 然后在onClick 方法前添加一个逻辑判断。\n\n### 使用原则\n\n根目录build 添加插件\n\n ```gradle\nbuildscript {\n    \n    repositories {\n        maven {\n            url \"file://${rootDir.absolutePath}/.repo\"\n        }\n        google()\n        jcenter()\n\n    }\n    dependencies {\n        classpath 'com.kronos.doubleTap:double_tap_plugin:0.1.3'\n    }\n}\n```\n\napp 运行工程下引入插件 同时将你需要插入的代码的className 和functionname 标记在Extension中\n\n ```gradle\napply plugin: 'doubleTap'\n\ndoubleTab {\n    injectClassName = \"com.a.doubleclickplugin.DoubleTapCheck\"\n    injectFunctionName = \"isNotDoubleTap\"\n}\n\n```\n\n\n\n## Thread Hook plugin 线程hook更换\n\n通过字节码访问，查找项目内的线程池构造等，发现之后替换成自定义的线程构造。\n\n### 方法\n\n通过ASM的ClassNode 的方式读取了当前类的所有构造函数，然后判断当前的执行内容是否是需要变魔改的类，如果是则替换他的desc owner name相关。\n\n~~~kotlin\nclass ThreadAsmHelper : AsmHelper {\n    @Throws(IOException::class)\n    override fun modifyClass(srcClass: ByteArray): ByteArray {\n        val classNode = ClassNode(Opcodes.ASM5)\n        val classReader = ClassReader(srcClass)\n        //1 将读入的字节转为classNode\n        classReader.accept(classNode, 0)\n        //2 对classNode的处理逻辑\n        val iterator: Iterator\u003cMethodNode\u003e =\n            classNode.methods.iterator()\n        while (iterator.hasNext()) {\n            val method = iterator.next()\n            method.instructions?.iterator()?.forEach {\n                if (it.opcode == Opcodes.INVOKESTATIC) {\n                    if (it is MethodInsnNode) {\n                        it.hookExecutors(classNode, method)\n                    }\n                }\n            }\n        }\n        val classWriter = ClassWriter(0)\n        //3  将classNode转为字节数组\n        classNode.accept(classWriter)\n        return classWriter.toByteArray()\n    }\n\n    private fun MethodInsnNode.hookExecutors(classNode: ClassNode, methodNode: MethodNode) {\n        when (this.owner) {\n            EXECUTORS_OWNER -\u003e {\n                info(\"owner:${this.owner}  name:${this.name} \")\n                ThreadPoolCreator.poolList.forEach {\n                    if (it.name == this.name \u0026\u0026 this.name == it.name \u0026\u0026 this.owner == it.owner) {\n                        this.owner = Owner\n                        this.name = it.methodName\n                        this.desc = it.replaceDesc()\n                        info(\"owner:${this.owner}  name:${this.name} desc:${this.desc} \")\n                    }\n                }\n\n            }\n        }\n    }\n}\n~~~\n\n最后在编译阶段该类就会被替换成我们想要的类，举个例子`Executors.newSingleThreadExecutor();`变更成`TestIOThreadExecutor.getThreadPool();`。\n\n## 升级更新\n\n### 多线程操作字节码\n\nbase  plugin 代码升级，使用多线程优化，讲字节码操作执行在线程中，之后在主函数等待所有task执行完成之后在结束。\n\nbase plugin 主要是辅助后续有兴趣的同学可以快速的进行transform开发学习，在当前类基础上，可以无视繁琐的增量编译和额外的文件拷贝操作，只专注于Asm的学习。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleifzhang%2Fandroidautotrack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleifzhang%2Fandroidautotrack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleifzhang%2Fandroidautotrack/lists"}