编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1App启动流程梳理
  • 1.2ActivityThread分析
  • 1.3Context设计思想
  • 2.1Activity基础介绍
  • 2.2Activity启动流程
  • 2.3Activity布局创建
  • 2.4Activity布局绘制
  • 2.5Service基础介绍
  • 2.6Service启动流程
  • 2.7Receiver广播基础
  • 2.8Receiver深入原理
  • 2.9ContentProvider分析
  • 2.10Fragment实践
  • 2.11Intent深入思考
  • 3.1Paint和Canvas
  • 3.2View的绘制基础
  • 3.3onMeasure流程设计
  • 3.4onLayout流程设计
  • 3.5onDraw流程设计
  • 3.6View工作原理
  • 3.7View刷新设计流程
  • 3.8自定义View控件
  • 3.9自定义ViewGroup控件
  • 4.1Handler基础使用
  • 4.2消息机制流程分析
  • 4.3Handler深度解析
  • 4.4Message深度理解
  • 4.5MessageQueue解析
  • 4.6Looper深度解析
  • 4.7理解Handler同步屏障
  • 4.8ThreadLocal分析
  • 4.9ThreadLocal场景
  • 5.1View事件设计思考
  • 5.2View滑动冲突处理
  • 5.3View事件源码分析
  • 5.4View事件总结案例
  • 6.1DecorView设计思想
  • 6.2视图的载体View
  • 6.3视图管理者Window
  • 6.4窗口管理服务WMS
  • 6.5布局解析者Inflater
  • 7.1AsyncTask深入介绍
  • 7.2HandlerThread设计
  • 7.3IntentService设计
  • 8.1IPC通信方式介绍
  • 8.2AIDL进程间通信
  • 8.7Binder通信机制设计
  • /zh/android/basic/9.1注解设计思想和原理.html
  • 9.2APT技术设计详解
  • 9.3APT多种案例实践

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。而自定义的方式,下面我们会讲到。

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源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。

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”)

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;
    }
贡献者: yangchong211