Android编译期代码注入机制及实现方案
1 常见实现方案对比
Android开发中常用三种编译期代码注入方案:
- APT注解处理器
- Transform+ASM字节码操作
- AspectJ切面编程
1.1 APT注解处理器
通过自定义注解标记目标方法,利用APT在编译期生成辅助类实现代码注入。核心流程包含:
- 注解解析:识别被@LogAnnotation标记的方法
- 代码生成:使用JavaPoet创建日志类
- 逻辑织入:通过静态方法调用插入日志语句
适用于需要生成新类的场景,如ButterKnife等框架。
1.2 Transform+ASM方案
基于Gradle Transform API拦截编译流程,在.class→dex阶段使用ASM修改字节码。关键特性:
- 通过getScopes控制处理范围
- 使用ClassVisitor/MethodVisitor插入日志指令
- 支持增量编译优化
适合需要修改现有代码逻辑的场景,如全局埋点和性能监控。
1.3 AspectJ切面编程
通过切点表达式定义目标方法,在编译期直接插入日志逻辑。优势包括:
- 无需运行时反射,性能损耗低
- 支持复杂匹配表达式
- 实现方法级日志注入
适用于简单场景,复杂需求需结合ASM方案。
2 实战实现
2.1 APT方案实现
实现步骤:
- 定义注解:
@Retention(RetentionPolicy.CLASS) - 实现注解处理器继承AbstractProcessor
- 使用JavaPoet生成日志类
@AutoService(Processor.class)
public class LogAnnotationProcessor extends AbstractProcessor {
private Filer filer;
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment env) {
for (Element element : env.getElementsAnnotatedWith(LogAnnotation.class)) {
if (element.getKind() == ElementKind.METHOD) {
generateLogCode(...);
}
}
return true;
}
private void generateLogCode(...) {
MethodSpec logMethod = MethodSpec.methodBuilder(...)
.addStatement("$T.d(\"APT\", \"Method called: $L\")",
ClassName.get("android.util", "Log"), method.getSimpleName())
.build();
}
}
2.2 Transform+ASM方案
实现要点:
- 创建Gradle插件项目
- 实现LogInsertTransform类
- 使用ASM修改字节码
class LogInsertTransform extends Transform {
@Override
void transform(TransformInvocation transformInvocation) {
transformInvocation.inputs.each { input ->
input.directoryInputs.each { dir ->
processDirectory(dir.file)
}
}
}
private void processDirectory(File directory) {
directory.eachFileRecurse { file ->
if (file.name.endsWith('.class')) {
processClassFile(file)
}
}
}
private void processClassFile(File classFile) {
def fis = new FileInputStream(classFile)
def classBytes = processClass(fis)
fis.close()
def fos = new FileOutputStream(classFile)
fos.write(classBytes)
fos.close()
}
}
2.3 AspectJ方案
实现步骤:
- 添加AspectJ依赖
- 定义切面类
- 配置插件实现编译期织入
@Aspect
public class LogAspect {
@Pointcut("execution(* *.*(..))")
public void logMethods() {}
@Before("logMethods()")
public void beforeMethod(JoinPoint joinPoint) {
Log.d("LogAspect", "Before method: " + joinPoint.getSignature().getName());
}
@After("logMethods()")
public void afterMethod(JoinPoint joinPoint) {
Log.d("LogAspect", "After method: " + joinPoint.getSignature().getName());
}
}