9.1注解设计思想和原理
目录介绍
- 01.什么是注解
- 1.1 注解分类
- 1.2 自定义注解分类
- 1.3 实际注解案例
- 02.运行注解案例
- 2.1 创建一个注解
- 2.2 注解解析
- 2.3 案例总结
- 03.使用注解替代枚举
- 04.使用注解限定类型
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 创建一个注解
- 如下所示
@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 注解解析
- 注解申明好了,但具体是怎么识别这个注解并使用的呢?
@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代码
public 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.使用注解替代枚举
- 代码如下所示
- 具体的案例,可以看我视频播放器开源库:https://github.com/yangchong211/YCVideoPlayer
/** * 播放模式 * -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.使用注解限定类型
- 代码如下所示
- 具体的案例,可以看我视频播放器开源库:https://github.com/yangchong211/YCVideoPlayer
- 枚举最大的作用是提供了类型安全。为了弥补Android平台不建议使用枚举的缺陷,官方推出了两个注解,IntDef和StringDef,用来提供编译期的类型检查。
- 倘若,传入的值不是IjkPlayerType中的类型,则会导致编译提醒和警告。
/** * 通过注解限定类型 * 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; }