Android Transform + ASM 初探

这里新建了 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
}

@Override
boolean isIncremental() {
return true
}

private static void transformJar(File input, File dest) {
println(“=== transformJar ===”)
FileUtils.copyFile(input, dest)
}

private static void transformDir(File input, File dest) {
if (dest.exists()) {
FileUtils.forceDelete(dest)
}
FileUtils.forceMkdir(dest)
String srcDirPath = input.getAbsolutePath()
String destDirPath = dest.getAbsolutePath()
println("=== transform dir = " + srcDirPath + ", " + destDirPath)
for (File file : input.listFiles()) {
String destFilePath = file.absolutePath.replace(srcDirPath, destDirPath)
File destFile = new File(destFilePath)
if (file.isDirectory()) {
transformDir(file, destFile)
} else if (file.isFile()) {
FileUtils.touch(destFile)
transformSingleFile(file, destFile)
}
}
}

private static void transformSingleFile(File input, File dest) {
println(“=== transformSingleFile ===”)
weave(input.getAbsolutePath(), dest.getAbsolutePath())
}

private static void weave(String inputPath, String outputPath) {
try {
FileInputStream is = new FileInputStream(inputPath)
ClassReader cr = new ClassReader(is)
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
TestMethodClassAdapter adapter = new TestMethodClassAdapter(cw)
cr.accept(adapter, 0)
FileOutputStream fos = new FileOutputStream(outputPath)
fos.write(cw.toByteArray())
fos.close()
} catch (IOException e) {
e.printStackTrace()
}
}
}

我们的 InputTypes 是 CONTENT_CLASS, 表明是 class 文件,Scope 先无脑选择 SCOPE_FULL_PROJECT 在 transform 方法中主要做的事情就是把 Inputs 保存到 outProvider 提供的位置去。生成的位置见下图:

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

对照代码,主要有两个 transform 方法,一个 transformJar 就是简单的拷贝,另一个 transformSingleFile,我们就是在这里用 ASM 对字节码进行修改的。 关注一下 weave 方法,可以看到我们借助 ClassReader 从 inputPath 中读取输入流,在 ClassWriter 之前用一个 adapter 进行了封装,接下来就让我们看看 adapter 做了什么。

public class TestMethodClassAdapter extends ClassVisitor implements Opcodes {

public TestMethodClassAdapter(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return (mv == null) ? null : new TestMethodVisitor(mv);
}
}

这个 adapter 接收一个 classVisitor 作为输入(即 ClassWriter),在 visitMethod 方法时使用自定义的 TestMethodVisitor 进行访问,再看看 TestMethodVisitor:

public class TestMethodVisitor extends MethodVisitor {

public TestMethodVisitor(MethodVisitor methodVisitor) {
super(ASM7, methodVisitor);
}

@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
System.out.println(“== TestMethodVisitor, owner = " + owner + “, name = " + name);
//方法执行之前打印
mv.visitLdcInsn(” before method exec”);
mv.visitLdcInsn(" [ASM 测试] method in " + owner + " ,name=" + name);
mv.visitMethodInsn(INVOKESTATIC,
“android/util/Log”, “i”, “(Ljava/lang/String;Ljava/lang/String;)I”, false);
mv.visitInsn(POP);

super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

//方法执行之后打印
mv.visitLdcInsn(" after method exec");
mv.visitLdcInsn(" method in " + owner + " ,name=" + name);
mv.visitMethodInsn(INVOKESTATIC,
“android/util/Log”, “i”, “(Ljava/lang/String;Ljava/lang/String;)I”, false);
mv.visitInsn(POP);
}

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下~

加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下~

加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值