目录介绍
- 13.0.0.1 什么是注解?系统内置的标准注解有哪些?SuppressWarnings用过没?Android中提供了哪些与线程相关的注解?
- 13.0.0.2 什么是apt?apt的难点和优势?什么是注解处理器?抽象处理器中四个方法有何作用?annotationProcessor和apt区别?
- 13.0.0.3 注解是怎么分类的?自定义注解又是怎么分类的?运行期注解原理是什么?实际注解案例有哪些?
- 13.0.0.4 在自定义注解中,Annotation里面的方法为何不能是private?Annotation里面的方法参数有哪些?
- 13.0.0.5 @Inherited是什么意思?注解是不可以继承的,这是为什么?注解的继承这个概念该如何理解?
- 13.0.0.6 什么是依赖注入?依赖注入案例举例说明,有哪些方式,具备什么优势?依赖查找和依赖注入有什么区别?
- 13.0.0.7 路由框架为何需要依赖注入,不用的话行不行?路由用什么方式注入,这些注入方式各具何特点,为何选择注解注入?
- 13.0.0.8 实际开发中使用到注解有哪些,使用注解替代枚举?如何通过注解限定传入的类型?为何说枚举损耗性能?
注解基础系列博客
- 01.Annotation注解详细介绍
1.Annotation库的简单介绍 2.@Nullable和@NonNull 3.资源类型注释 4.类型定义注释 5.线程注释 6.RGB颜色纸注释 7.值范围注释 8.权限注释 9.重写函数注释 10.返回值注释 11.@Keep注释 12.@SuppressWarnings注解 13.其他
- 02.Dagger2深入分析,待更新
- 03.注解详细介绍
- 什么是注解,注解分类有哪些?自定义注解分类?运行注解案例展示分析,以一个最简单的案例理解注解……使用注解替代枚举,使用注解限定类型
- 04.APT技术详解
- 什么是apt?理解注解处理器的作用和用途……android-apt被替代?annotationProcessor和apt区别? 什么是jack编译方式?
- 06.自定义annotation注解
- @Retention的作用?@Target(ElementType.TYPE)的解释,@Inherited注解可以被继承吗?Annotation里面的方法为何不能是private?
- 07.注解之兼容kotlin
- 后期更新
- 08.注解之处理器类Processor
- 处理器类Processor介绍,重要方法,Element的作用,修饰方法的注解和ExecutableElement,了解修饰属性、类成员的注解和VariableElement……
- 10.注解遇到问题和解决方案
- 无法引入javax包下的类库,成功运行一次,修改代码后再运行就报错
- 11.注解代替枚举
- 在做内存优化时,推荐使用注解代替枚举,因为枚举占用的内存更高,如何说明枚举占用内存高呢?这是为什么呢?
- 12.注解练习案例开源代码
- 注解学习小案例,比较系统性学习注解并且应用实践。简单应用了运行期注解,通过注解实现了setContentView功能;简单应用了编译器注解,通过注解实现了防暴力点击的功能,同时支持设置时间间隔;使用注解替代枚举;使用注解一步步搭建简单路由案例。结合相应的博客,在来一些小案例,从此应该对注解有更加深入的理解……
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:github.com/yangchong21…
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!
13.0.0.1 什么是注解?系统内置的标准注解有哪些?SuppressWarnings用过没?Android中提供了哪些与线程相关的注解?
- 什么是注解?
- Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。基本规则:Annotation(注解)不能影响程序代码的执行,无论增加,删除Annotation(注解),代码都始终如一的执行。
- 系统内置的标准注解有哪些?
- @Override表示子类对父类方法的重写所带的注解;@Deprecated表示已经过时,不建议使用,但是依然可以使用;@SuppressWarnings("all")用来抑制编译时的警告信息,编译器在你写代码的时候,难免会出现很多的警告,有强迫症的程序猿会感到极其不爽,那么肿么办呢?@SuppressWarnings注解就可以告诉编译器,别警告啦,代码不会有问题的。
- SuppressWarnings用过没?
- @SuppressWarnings注解一定要传递一个参数给它,来表示过滤掉哪些类型的警告,笔者添加了”all”表示过滤掉所有类型的警告,很好理解吧!那么还可以传递什么参数来过滤警告呢?看看下面的表格你就会知道啦:
- Android中提供了哪些与线程相关的注解?
- @UiThread,通常可以等同于主线程,标注方法需要在UIThread执行,比如View类就使用这个注解
- @MainThread 主线程,经常启动后创建的第一个线程
- @WorkerThread 工作者线程,一般为一些后台的线程,比如AsyncTask里面的doInBackground就是这样的
- @BinderThread 注解方法必须要在BinderThread线程中执行,一般使用较少
13.0.0.2 什么是apt?apt的难点和优势?什么是注解处理器?抽象处理器中四个方法有何作用?annotationProcessor和apt区别?
- 什么是apt?
- APT(Annotation Processing Tool的简称),可以在代码编译期解析注解,并且生成新的Java文件,减少手动的代码输入。现在有很多主流库都用上了 APT,比如 Dagger2, ButterKnife等
- apt的难点和优势?
- 难点:就apt本身来说没有任何难点可言,难点一在于设计模式和解耦思想的灵活应用,二在与代码生成的繁琐,你可以手动字符串拼接,当然有更高级的玩法用squareup的javapoet,用建造者的模式构建出任何你想要的源代码
- 优点:它的强大之处无需多言,看代表框架的源码,你可以学到很多新姿势。总的一句话:它可以做任何你不想做的繁杂的工作,它可以帮你写任何你不想重复代码。
- 什么是注解处理器?
- 注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。你可以为特定的注解,注册你自己的注解处理器。
- 注解处理器可以生成Java代码,这些生成的Java代码会组成 .java 文件,但不能修改已经存在的Java类(即不能向已有的类中添加方法)。而这些生成的Java文件,会同时与其他普通的手写Java源代码一起被javac编译。
- 抽象处理器中四个方法有何作用?
- 每一个注解处理器都要继承于AbstractProcessor,如下所示:
public class MyProcessor extends AbstractProcessor{ @Override public synchronized void init(ProcessingEnvironment processingEnvironment){ super.init(processingEnvironment); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment){ return false; } @Override public Set<String> getSupportedAnnotationTypes(){ return super.getSupportedAnnotationTypes(); } @Override public SourceVersion getSupportedSourceVersion(){ return super.getSupportedSourceVersion(); } }
- 这几个方法如下
- init(ProcessingEnvironment processingEnvironment): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。后面我们将看到详细的内容。
- process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
- getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
- getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 7的话,你也可以返回SourceVersion.RELEASE_7。我推荐你使用前者。
- 每一个注解处理器都要继承于AbstractProcessor,如下所示:
- annotationProcessor和apt区别?
- Android 官方的 annotationProcessor 同时支持 javac 和 jack 编译方式,而 android-apt 只支持 javac 方式。当然,目前 android-apt 在 Android Gradle 插件 2.2 版本上面仍然可以正常运行,如果你没有想支持 jack 编译方式的话,可以继续使用 android-apt。
- 什么是jack编译方式?
- Jack (Java Android Compiler Kit)是新的Android 编译工具,从Android 6.0 开始加入,替换原有的编译工具,例如javac, ProGuard, jarjar和 dx。它主要负责将java代码编译成dex包,并支持代码压缩,混淆等。
- Jack工具的主要优势
- 完全开放源码,源码均在AOSP中,合作伙伴可贡献源码
- 加快编译源码,Jack 提供特殊的配置,减少编译时间:pre-dexing, 增量编译和Jack编译服务器.
- 支持代码压缩,混淆,重打包和multidex,不在使用额外单独的包,例如ProGuard。
13.0.0.3 注解是怎么分类的?自定义注解又是怎么分类的?运行期注解原理是什么?
- 注解是怎么分类的?
- 标准 Annotation
- 包括 Override, Deprecated, SuppressWarnings,是java自带的几个注解,他们由编译器来识别,不会进行编译, 不影响代码运行,至于他们的含义不是这篇博客的重点,这里不再讲述。
- 元 Annotation
- @Retention, @Target, @Inherited, @Documented,它们是用来定义 Annotation 的 Annotation。也就是当我们要自定义注解时,需要使用它们。
- 自定义 Annotation
- 根据需要,自定义的Annotation。而自定义的方式,下面我们会讲到。
- 标准 Annotation
- 自定义注解又是怎么分类的?
- 同样,自定义的注解也分为三类,通过元Annotation - @Retention 定义:
- @Retention(RetentionPolicy.SOURCE)
- 源码时注解,一般用来作为编译器标记。如Override, Deprecated, SuppressWarnings。
- @Retention(RetentionPolicy.RUNTIME)
- 运行时注解,在运行时通过反射去识别的注解。
- 定义运行时注解,只需要在声明注解时指定@Retention(RetentionPolicy.RUNTIME)即可。
- 运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答。
- @Retention(RetentionPolicy.CLASS)
- 编译时注解,在编译时被识别并处理的注解,这是本章重点。
- 编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。
- 运行期注解原理是什么?
- 实际注解案例有哪些?
- 运行时注解:retrofit
- 编译时注解:Dagger2, ButterKnife, EventBus3
13.0.0.4 在自定义注解中,Annotation里面的方法为何不能是private?Annotation里面的方法参数有哪些?
- Annotation里面的方法为何不能是private?
- 只能用public或默认(default)这两个访问权修饰.例如,String value();不能是private;因为它是提供给外部使用的。
- Annotation里面的方法参数有哪些
- 参数只能使用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数类型就为String;
13.0.0.5 @Inherited是什么意思?注解是不可以继承的,这是为什么?注解的继承这个概念该如何理解?
- @Inherited是什么意思?
- 该注解的字面意识是继承,但你要知道注解是不可以继承的。@Inherited是在继承结构中使用的注解。
- 如果你的注解是这样定义的:
- 当你的注解定义到类A上,此时,有个B类继承A,且没使用该注解。但是扫描的时候,会把A类设置的注解,扫描到B类上。
@Inherited @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface Test { //... }
- 注解是不可以继承的,这是为什么?
- 注解不能继承
- 注解的继承这个概念该如何理解?
- 这里讲的继承并不是通过@Inherited修饰的注解。这个“继承”是一个注解的使用技巧,使用上的感觉类似于依赖倒置,来自于ButterKnife源码。
- 这是ButterKnife的OnClick 注解。特殊的地方在于**@OnClick修饰了注解@ListenerClass**,并且设置了一些只属于@OnClick的属性。
- 那这样的作用是什么呢?凡是修饰了@OnClick的地方,也就自动修饰了@ListenerClass。类似于@OnClick是@ListenerClass的子类。而ButterKnife有很多的监听注解@OnItemClick、@OnLongClick等等。这样在做代码生成时,不需要再单独考虑每一个监听注解,只需要处理@ListenerClass就OK。
@Target(METHOD) @Retention(CLASS) @ListenerClass( targetType = "android.view.View", setter = "setOnClickListener", type = "butterknife.internal.DebouncingOnClickListener", method = @ListenerMethod( name = "doClick", parameters = "android.view.View" ) ) public @interface OnClick { /** View IDs to which the method will be bound. */ int[] value() default { View.NO_ID }; }
13.0.0.6 什么是依赖注入?依赖注入案例举例说明,有哪些方式,具备什么优势?依赖查找和依赖注入有什么区别?
- 什么是依赖注入
- 在面向对象编程中,经常处理处理的问题就是解耦,程序的耦合性越低表明这个程序的可读性以及可维护性越高。控制反转(Inversion of Control或IoC)就是常用的面向对象编程的设计原则,使用这个原则我们可以降低耦合性。其中依赖注入是控制反转最常用的实现。
- 依赖注入案例举例说明,有哪些方式,具备什么优势?
- 依赖是程序中常见的现象,比如类Car中用到了GasEnergy类的实例energy,通常的做法就是在Car类中显式地创建GasEnergy类的实例,并赋值给energy。如下面的代码
interface Energy { } class GasEnergy implements Energy { } class Car { Energy energy = new GasEnergy(); }
- 存在问题
- 类Car承担了多余的责任,负责energy对象的创建,这必然存在了严重的耦合性。举一个现实中的例子,一辆汽车使用哪种能源不是由汽车来决定,而是由汽车制造商(CarMaker)来决定,这是汽车制造商的责任。
- 可扩展性,假设我们想修改能源为电动力,那么我们必然要修改Car这个类,明显不符合开放闭合原则。
- 不利于单元测试。
- 有哪些方式解耦
- 依赖注入是这样的一种行为,在类Car中不主动创建GasEnergy的对象,而是通过外部传入GasEnergy对象形式来设置依赖。 常用的依赖注入有如下三种方式
- 构造器注入
- 将需要的依赖作为构造方法的参数传递完成依赖注入。
class Car { Energy mEnergy; public Car(Energy energy) { mEnergy = energy; } }
- Setter方法注入
- 增加setter方法,参数为需要注入的依赖亦可完成依赖注入。
class Car { Energy mEnergy; public void setEnergy(Energy energy) { mEnergy = energy; } }
- 接口注入
- 接口注入,闻其名不言而喻,就是为依赖注入创建一套接口,依赖作为参数传入,通过调用统一的接口完成对具体实现的依赖注入。
- 接口注入和setter方法注入类似,不同的是接口注入使用了统一的方法来完成注入,而setter方法注入的方法名称相对比较随意。
interface EnergyConsumerInterface { public void setEnergy(Energy energy); } class Car implements EnergyConsumerInterface { Energy mEnergy; public void setEnergy(Energy energy) { mEnergy = energy; } }
- 依赖是程序中常见的现象,比如类Car中用到了GasEnergy类的实例energy,通常的做法就是在Car类中显式地创建GasEnergy类的实例,并赋值给energy。如下面的代码
- 依赖查找和依赖注入
- 依赖查找和依赖注入一样属于控制反转原则的具体实现,不同于依赖注入的被动接受,依赖查找这是主动请求,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。
13.0.0.7 路由框架为何需要依赖注入,不用的话行不行?路由用什么方式注入,这些注入方式各具何特点,为何选择注解注入?
- 路由框架为何需要依赖注入,不用的话行不行?
- 路由的目的就是要实现跳转,但是你有没有想过,两个Activity之间的跳转肯定免不了要传入一些参数,如果我们在跳转后还要通过intent去获取参数,这样岂不是很麻烦,如果可以自动把参数赋给属性多好啊!
- 组件化中两个module之间可能有一些功能并不需要跳转页面,如支付模块要获取用户模块的用户id,并不需要跳转页面,那么我们就要持有用户模块含有获取用户id功能的类的引用,如果我们在支付模块创建一个用户模块的功能引用,显然就违背了解耦的规则。这两个问题显然用依赖注入的方式会更好些,如果你用过ARouter,你会发现ARouter中的服务(IProvider)就是通过依赖注入实现的。
- 路由用什么方式注入,这些注入方式各具何特点,为何选择注解注入?
- 可以使用注解的方式,至于为啥,接下来我分析一下……
- 为什么要用注解实现依赖注入,因为我们用了apt啊,那岂不是天生实现依赖注入的利器。如果你去写配置文件或构造方法等等,未免太复杂。
- 无法通过构造方法注入【构造器注入】
- 在多组件并行开发过程中,因为两个module没有引用关系,所以就不能通过构造方法传入要依赖的类,这个时候怎么办呢?连对方的引用都得不到,如何进行依赖呢?
- 不要通过反射方式注入
- 可能你会想到反射,我们先pass这个方案,能不用反射就能做好的前提下,我们最好不要用反射。
- 不建议通过接口方式注入【也可以,但若是开源成lib,则不建议】
- 可能有人会想到,在基类下沉一个接口功能标准,比如在基类库base模块定义获取用户id的接口,然后在用户模块实现接口的方法。那么当支付模块需要用到这个功能,就声明这个接口,然后一行注解通过框架为你创建实例,这样用户模块只需要提供功能,并不需要关心谁在用这个功能,这样岂不是大大减小了耦合。
- 但是有一点,组件化实践中,有很多个模块,那岂不是所有的都需要依赖base基类库,才能使用到接口注入,这样不易转接。
- 可以使用注解的方式,至于为啥,接下来我分析一下……
13.0.0.8 实际开发中使用到注解有哪些,使用注解替代枚举?如何通过注解限定传入的类型?为何说枚举损耗性能?
- 实际开发中使用到注解有哪些,使用注解替代枚举?
- 代码如下所示
- 具体的案例,可以看我视频播放器开源库:github.com/yangchong21…
/** * 播放模式 * -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; }
- 如何通过注解限定传入的类型?
- 代码如下所示
- 具体的案例,可以看我视频播放器开源库:github.com/yangchong21…
- 枚举最大的作用是提供了类型安全。为了弥补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; }
关于其他内容介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:github.com/yangchong21…
- 知乎:www.zhihu.com/people/yczb…
- 简书:www.jianshu.com/u/b7b2c6ed9…
- csdn:my.csdn.net/m0_37700275
- 喜马拉雅听书:www.ximalaya.com/zhubo/71989…
- 开源中国:my.oschina.net/zbj1618/blo…
- 泡在网上的日子:www.jcodecraeer.com/member/cont…
- 邮箱:yangchong211@163.com
- 阿里云博客:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
- segmentfault头条:segmentfault.com/u/xiangjian…
- 掘金:juejin.cn/user/197877…