一句话总结:
编译期注解是“提前备好的工具包”,运行时注解是“现场翻说明书”——一个省时但费脑,一个省事但费电!
一、注解(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. 写一个编译期注解(需注解处理器)
步骤:
- 定义注解:
@Retention(RetentionPolicy.CLASS) // 编译期保留
@Target(ElementType.FIELD)
public @interface MyCompileAnnotation {}
- 实现注解处理器(继承
AbstractProcessor)→ 扫描注解,生成代码(用JavaPoet库)。 - 配置处理器(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());
六、避坑指南
-
混淆问题: 运行时注解的类/方法如果被混淆 → 反射找不到!需在ProGuard规则中保留。
-keep class com.example.MyClass { *; } -
APT配置: 编译期注解处理器需在
build.gradle中配置:dependencies { annotationProcessor 'com.example:my-processor:1.0' } -
性能陷阱: 避免在运行时注解中频繁反射,尤其是循环或高频调用场景。
口诀:
“编译注解效率高,代码生成要趁早;
运行注解反射调,简单方便性能耗;
高频场景用编译,动态需求运行搞!”