Android 注解详细介绍
01.什么是注解
1.1 注解分类
- 首先注解分为三类:
- 标准 Annotation
- 包括 Override, Deprecated, SuppressWarnings,是java自带的几个注解,他们由编译器来识别,不会进行编译, 不影响代码运行,至于他们的含义不是这篇博客的重点,这里不再讲述。
- 元 Annotation
- @Retention, @Target, @Inherited, @Documented,它们是用来定义 Annotation 的 Annotation。也就是当我们要自定义注解时,需要使用它们。
- 自定义 Annotation
- 根据需要,自定义的Annotation。而自定义的方式,下面我们会讲到。
- 标准 Annotation
1.2 自定义注解分类
- 同样,自定义的注解也分为三类,通过元Annotation - @Retention 定义:
- @Retention(RetentionPolicy.SOURCE)
- 源码时注解,一般用来作为编译器标记。如Override, Deprecated, SuppressWarnings。
- 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;源码注解(RetentionPolicy.SOURCE)的生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时会把Java源程序上的源码注解给去掉。需要注意的是,在编译器处理期间源码注解还存在,即注解处理器Processor 也能处理源码注解,编译器处理完之后就没有该注解信息了。
- @Retention(RetentionPolicy.RUNTIME)
- 运行时注解,在运行时通过反射去识别的注解。
- 定义运行时注解,只需要在声明注解时指定@Retention(RetentionPolicy.RUNTIME)即可。
- 运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答。
- @Retention(RetentionPolicy.CLASS)
- 编译时注解,在编译时被识别并处理的注解,这是本章重点。
- 编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。
- @Retention(RetentionPolicy.SOURCE)
1.3 实际注解案例
- 实际注解案例
- 运行时注解:retrofit
- 编译时注解:Dagger2, ButterKnife, EventBus3
02.运行注解案例
2.1 创建一个注解
如下所示
1
2
3
4
5@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ContentView {
int value();
}关于代码解释
- 第一行:@Retention(RetentionPolicy.RUNTIME)
- @Retention用来修饰这是一个什么类型的注解。这里表示该注解是一个运行时注解。这样APT就知道啥时候处理这个注解了。
- 第二行:@Target({ElementType.TYPE})
- @Target用来表示这个注解可以使用在哪些地方。比如:类、方法、属性、接口等等。这里ElementType.TYPE 表示这个注解可以用来修饰:Class, interface or enum declaration。当你用ContentView修饰一个方法时,编译器会提示错误。
- 第三行:public @interface ContentView
- 这里的interface并不是说ContentView是一个接口。就像申明类用关键字class。申明枚举用enum。申明注解用的就是@interface。(值得注意的是:在ElementType的分类中,class、interface、Annotation、enum同属一类为Type,并且从官方注解来看,似乎interface是包含@interface的)
- /** Class, interface (including annotation type), or enum declaration */
- TYPE,
- 第四行:int value();
- 返回值表示这个注解里可以存放什么类型值。比如我们是这样使用的
- @ContentView(R.layout.activity_home)
- R.layout.activity_home实质是一个int型id,如果这样用就会报错:
- @ContentView(“string”)
- 第一行:@Retention(RetentionPolicy.RUNTIME)
2.2 注解解析
注解申明好了,但具体是怎么识别这个注解并使用的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13@ContentView(R.layout.activity_test_video)
public class TestActivity extends BaseActivity {
//@ContentView(R.layout.activity_test_video) 这种使用是错误的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv_video = findViewById(R.id.tv_video);
tv_video.setOnClickListener(v -> startActivity(
new Intent(TestActivity.this,VideoActivity.class)));
}
}注解的解析就在BaseActivity中。来看一下BaseActivity代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注解解析
//遍历所有的子类
for (Class c = this.getClass(); c != Context.class; c = c.getSuperclass()) {
assert c != null;
//找到修饰了注解ContentView的类
ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
if (annotation != null) {
try {
//获取ContentView的属性值
int value = annotation.value();
//调用setContentView方法设置view
this.setContentView(value);
} catch (RuntimeException e) {
e.printStackTrace();
}
return;
}
}
}
}总结一下
- 这是一个很简单的案例。现在对运行时注解的使用一定有了一些理解了。也知道了运行时注解被人呕病的地方在哪。你可能会觉得*setContentView(R.layout.activity_home)和@ContentView(R.layout.activity_home)*没什么区别,用了注解反而还增加了性能问题。
03.使用注解替代枚举
代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 播放模式
* -1 播放错误
* 0 播放未开始
* 1 播放准备中
* 2 播放准备就绪
* 3 正在播放
* 4 暂停播放
* 5 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
* 6 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区数据足够后恢复暂停
* 7 播放完成
*/
public @interface CurrentState{
int STATE_ERROR = -1;
int STATE_IDLE = 0;
int STATE_PREPARING = 1;
int STATE_PREPARED = 2;
int STATE_PLAYING = 3;
int STATE_PAUSED = 4;
int STATE_BUFFERING_PLAYING = 5;
int STATE_BUFFERING_PAUSED = 6;
int STATE_COMPLETED = 7;
}
04.使用注解限定类型
代码如下所示
- 枚举最大的作用是提供了类型安全。为了弥补Android平台不建议使用枚举的缺陷,官方推出了两个注解,IntDef和StringDef,用来提供编译期的类型检查。
- 倘若,传入的值不是IjkPlayerType中的类型,则会导致编译提醒和警告。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 通过注解限定类型
* TYPE_IJK IjkPlayer,基于IjkPlayer封装播放器
* TYPE_NATIVE MediaPlayer,基于原生自带的播放器控件
*/
@Retention(RetentionPolicy.SOURCE)
public @interface IjkPlayerType {
int TYPE_IJK = 111;
int TYPE_NATIVE = 222;
}
@IntDef({IjkPlayerType.TYPE_IJK,IjkPlayerType.TYPE_NATIVE})
public @interface PlayerType{}
//使用
/**
* 设置播放器类型,必须设置
* 注意:感谢某人建议,这里限定了传入值类型
* 输入值:ConstantKeys.IjkPlayerType.TYPE_IJK 或者 ConstantKeys.IjkPlayerType.TYPE_NATIVE
* @param playerType IjkPlayer or MediaPlayer.
*/
public void setPlayerType(@ConstantKeys.PlayerType int playerType) {
mPlayerType = playerType;
}