一句话说透Android里面的编译期注解和运行时注解

233 阅读3分钟

一句话总结:
编译期注解是“提前备好的工具包”,运行时注解是“现场翻说明书”——一个省时但费脑,一个省事但费电!


一、注解(Annotation)是啥?

  • 官方解释:  代码里的特殊标记,用来给编译器或运行时提供额外信息。
  • 人话翻译:  给代码贴标签,告诉工具该干啥活。比如:
    @Override → “喂,我是重写父类的方法,帮我检查一下!”

二、编译期注解(提前准备型)

1. 特点

  • 处理时机:  代码编译时处理(生成新代码或检查错误)。
  • 性能:  无运行时开销(因为活都在编译时干完了)。
  • 代表库:  ButterKnife、Dagger2、Room。

**2. 举个栗子 **

场景:  用 ButterKnife 绑定View

// 编译前代码(你写的)  
public class MainActivity extends Activity {  
    @BindView(R.id.button)  
    Button button;  
}  

// 编译后生成的代码(ButterKnife处理器自动生成)  
public class MainActivity_ViewBinding {  
    public void bind(MainActivity activity) {  
        activity.button = activity.findViewById(R.id.button);  
    }  
}  

原理:  编译时扫描 @BindView → 生成 XXX_ViewBinding 类 → 自动完成 findViewById!

3. 优缺点

  • 优点:  运行快,不拖累性能。
  • 缺点:  实现复杂(要写注解处理器),编译时间变长。

三、运行时注解(现场翻书型)

1. 特点

  • 处理时机:  程序运行时通过反射处理。
  • 性能:  有反射开销(效率低,频繁调用可能卡顿)。
  • 代表库:  Retrofit(部分注解)、Gson。

**2. 举个栗子 **

场景:  用 Retrofit 定义API接口

public interface ApiService {  
    @GET("user/info")  
    Call<User> getUserInfo(@Query("id") String id);  
}  

// 运行时通过反射解析@GET、@Query → 动态生成HTTP请求  

原理:  运行时读取注解 → 反射获取信息 → 动态构造请求。

3. 优缺点

  • 优点:  实现简单(不用写处理器)。
  • 缺点:  反射耗性能,可能被ProGuard混淆搞崩!

四、怎么选?看场景!

场景推荐方式理由
频繁调用的UI绑定编译期注解(ButterKnife)避免反射拖累性能
动态接口(如网络请求)运行时注解(Retrofit)灵活,适合运行时动态解析
依赖注入框架编译期注解(Dagger2)生成高效代码,无运行时开销
JSON解析运行时注解(Gson)实现简单,够用

五、自己动手写注解

1. 写一个运行时注解(反射实现)

// 定义注解  
@Retention(RetentionPolicy.RUNTIME) // 运行时保留  
@Target(ElementType.METHOD)         // 用于方法  
public @interface MyRuntimeAnnotation {  
    String value() default "Hello";  
}  

// 使用注解  
public class Demo {  
    @MyRuntimeAnnotation("World")  
    public void sayHello() {  
        System.out.println("Hello World!");  
    }  
}  

// 运行时处理  
Method method = Demo.class.getMethod("sayHello");  
MyRuntimeAnnotation anno = method.getAnnotation(MyRuntimeAnnotation.class);  
System.out.println(anno.value()); // 输出 "World"  

2. 写一个编译期注解(需注解处理器)

步骤:

  1. 定义注解:
@Retention(RetentionPolicy.CLASS) // 编译期保留  
@Target(ElementType.FIELD)  
public @interface MyCompileAnnotation {}  
  1. 实现注解处理器(继承 AbstractProcessor)→ 扫描注解,生成代码(用 JavaPoet 库)。
  2. 配置处理器(METAINF配置)→ 打包成Jar。

代码生成示例(JavaPoet):

// 生成一个HelloWorld类  
JavaFile.builder("com.example",  
    TypeSpec.classBuilder("HelloWorld")  
        .addMethod(MethodSpec.methodBuilder("main")  
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)  
            .returns(void.class)  
            .addParameter(String[].class, "args")  
            .addStatement("$T.out.println($S)", System.class, "Hello World!")  
            .build())  
        .build())  
.build().writeTo(processingEnv.getFiler());  

六、避坑指南

  1. 混淆问题:  运行时注解的类/方法如果被混淆 → 反射找不到!需在ProGuard规则中保留。

    -keep class com.example.MyClass { *; }  
    
  2. APT配置:  编译期注解处理器需在 build.gradle 中配置:

    dependencies {  
        annotationProcessor 'com.example:my-processor:1.0'  
    }  
    
  3. 性能陷阱:  避免在运行时注解中频繁反射,尤其是循环或高频调用场景。


口诀:
“编译注解效率高,代码生成要趁早;
运行注解反射调,简单方便性能耗;
高频场景用编译,动态需求运行搞!”