Android Transform + ASM 初探,看完就能找到工作

Gradle 1.5:

Compile ‘com.android.tools.build:transfrom-api:1.5.0’

Gradle 2.0 开始:

implementation ‘com.android.tools.build:gradle-api:3.0.1’

每个 Transform 其实都是一个 Gradle task,他们链式组合,前一个的输出作为下一个的输入,而我们自定义的 Transform 是作为第一个 task 最先执行的。

本文是基于 buildSrc 的方式定义 Gradle 插件的,因为只在 Demo 项目中应用,所以 buildSrc 的方式就够了。需要注意一点的是,buildSrc 方式要求 library module 的名称必须为 buildSrc,在实现中注意一下。

废话少说,直接上图:

buildSrc module:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 buildSrc 中自定义一个基于 Groovy 的插件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在主项目 App 的 build.gradle 中引入自定义的 AsmPlugin

apply plugin: AsmPlugin

最后,在 settings.gradle 中加入 buildSrc module

include ‘:app’, ‘:buildSrc’

至此,我们就完成了一个自定义的插件,功能十分简陋,只是在控制台输出 “hello gradle plugin",让我们编译一下看看这个插件到底有没有生效。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好了,看到控制台的输出表明我们自定义的插件生效了,“作案地方”就此埋伏完毕。

后面会定义一个 AsmTransform,注册到 AsmPlugin 中,具体代码会在介绍 ASM 的时候贴出来。

ASM

有了搞事情的时机,怎么去修改字节码呢?此时神器 ASM 就出场了。

ASM 是一个功能比较齐全的 Java 字节码操作与分析框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接 产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类的行为。

更多细节可以去 [ASM 官网]( ) 看看。

笔者写 Demo 的时候最新的版本是 7.0。

ASM 提供一种基于 Visitor 的 API,通过接口的方式,分离读 class 和写 class 的逻辑,提供一个 ClassReader 负责读取class字节码,然后传递给 Class Visitor 接口,Class Visitor 接口提供了很多 visitor 方法,比如 visit class,visit method 等,这个过程就像 ClassReader 带着 ClassVisitor 游览了 class 字节码的每一个指令。

光有读还不够,如果我们要修改字节码,ClassWriter 就出场了。ClassWriter 其实也是继承自 ClassVisitor 的,所做的就是保存字节码信息并最终可以导出,那么如果我们可以代理 ClassWriter 的接口,就可以干预最终生成的字节码了。

好,还是废话少说,直接上代码。

先看一下插件目录的结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里新建了 AsmTransform 插件,以及 class visitor 的 adapter(TestMethodClassAdapter),使得在 visit method 的时候可以调用自定义的 TestMethodVisitor。

同时,buildSrc 的 build.gradle 中也要引入 ASM 依赖

// ASM 相关
implementation ‘org.ow2.asm:asm:7.1’
implementation ‘org.ow2.asm:asm-util:7.1’
implementation ‘org.ow2.asm:asm-commons:7.1’

下面先来看一下 AsmTransform

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.TransformInvocation
import com.android.build.api.transform.TransformOutputProvider
import com.android.build.gradle.internal.pipeline.TransformManager
import me.sure.asm.TestMethodClassAdapter
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter

class AsmTransform extends Transform {

Project project

AsmTransform(Project project) {
this.project = project
}

@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
println(“===== ASM Transform =====”)
println(“ t r a n s f o r m I n v o c a t i o n . i n p u t s " ) p r i n t l n ( " {transformInvocation.inputs}") println(" transformInvocation.inputs")println("{transformInvocation.referencedInputs}”)
println(“ t r a n s f o r m I n v o c a t i o n . o u t p u t P r o v i d e r " ) p r i n t l n ( " {transformInvocation.outputProvider}") println(" transformInvocation.outputProvider")println("{transformInvocation.incremental}”)

//当前是否是增量编译
boolean isIncremental = transformInvocation.isIncremental()
//消费型输入,可以从中获取jar包和class文件夹路径。需要输出给下一个任务
Collection inputs = transformInvocation.getInputs()
//引用型输入,无需输出。
Collection referencedInputs = transformInvocation.getReferencedInputs()
//OutputProvider管理输出路径,如果消费型输入为空,你会发现OutputProvider == null
TransformOutputProvider outputProvider = transformInvocation.getOutputProvider()
for (TransformInput input : inputs) {
for (JarInput jarInput : input.getJarInputs()) {
File dest = outputProvider.getContentLocation(
jarInput.getFile().getAbsolutePath(),
jarInput.getContentTypes(),
jarInput.getScopes(),
Format.JAR)
//将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了
transformJar(jarInput.getFile(), dest)
}
for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
println("== DI = " + directoryInput.file.listFiles().toArrayString())
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(), directoryInput.getScopes(),
Format.DIRECTORY)
//将修改过的字节码copy到dest,就可以实现编译期间干预字节码的目的了
//FileUtils.copyDirectory(directoryInput.getFile(), dest)
transformDir(directoryInput.getFile(), dest)
}
}
}

@Override
String getName() {
return AsmTransform.simpleName
}

@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS
}

@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

本文在开源项目:【GitHub 】中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-vd0v1YFP-1711134065187)]

本文在开源项目:【GitHub 】中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

  • 28
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值