9.1 注解
从JDK 5开始,Java增加了注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过 使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、 开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。
9.1.1 注解分类
注解分为标准注解和元注解。
1.标准注解
标准注解有以下4种:
- @Override:
- 对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编 译器会发出错误警告。
- @Deprecated:
- 对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,将会在编译 时显示提示信息。
- @SuppressWarnings:
- 选择性地取消特定代码段中的警告。
- @SafeVarargs:J
- DK 7新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题。
2.元注解
元注解,它用来注解其他注解,从而创建新的注解。元注解有以下几种。
- @Targe:
- 注解所修饰的对象范围。
- @Inherited:
- 表示注解可以被继承。
- @Documented:
- 表示这个注解应该被JavaDoc工具记录。 * @Retention:用来声明注解的保留策略。
- @Repeatable:
- JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次使用。
其中@Targe注解取值是一个ElementType类型的数组,其中有以下几种取值,对应不同的对象范围。
- ElementType.TYPE:能修饰类、接口或枚举类型。
- ElementType.FIELD:能修饰成员变量。
- ElementType.METHOD:能修饰方法。
- ElementType.PARAMETER:能修饰参数。
- ElementType.CONSTRUCTOR:能修饰构造方法。
- ElementType.LOCAL_VARIABLE:能修饰局部变量。
- ElementType.ANNOTATION_TYPE:能修饰注解。
- ElementType.PACKAGE:能修饰包。
- ElementType.TYPE_PARAMETER:类型参数声明。
- ElementType.TYPE_USE:使用类型。
其中@Retention注解有3种类型,分别表示不同级别的保留策略。
- RetentionPolicy.SOURCE:源码级注解。
- 注解信息只会保留在.java源码中,源码在编译后,注解信息被丢弃,不会保留在.class中。
- RetentionPolicy.CLASS:编译时注解。
- 注解信息会保留在.java 源码以及.class 中。当运行Java程序时, JVM会丢弃该注解信息,不会保留在JVM中。
- RetentionPolicy.RUNTIME:运行时注解。
- 当运行Java程序时,JVM也会保留该注解信息,可以通过反射获取该注解信息。
9.1.2 定义注解
1)基本定义
定义新的注解类型使用@interface关键字
//定义
public @interface Swordsman{}
//使用
@Swordsman
public class AnnotationTest{}
2)定义成员变量
注解只有成员变量,没有方法。注解的成员变量在注解定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,其返回值定义了该成员变量的类型。还可以使用default关键字为其指定默认值。
//定义注解和成员变量
public @interface Swordsman{
String name();
int age();
}
//使用
public class AnnotationTest{
@Swordsman(name="张无忌",age=23)
public void fighting(){ }
}
//default指定默认值
public @interface Swordsman{
String name() default "张无忌";
int age() default 23;
}
3)定义运行时/编译时注解
可以用@Retention来设定注解的保留策略,这 3个策略的生命周期长度为 SOURCE <CLASS< RUNTIME。生命周期短的能起作用的地方,生命周期长的一定也能起作用。
- 一般如果需要在运行时去动态获取注解信息,那只能用RetentionPolicy.RUNTIME;
- 如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 RetentionPolicy.CLASS;
- 如果只是做一些检查性的操作,比如@Override 和 @SuppressWarnings,则可选用RetentionPolicy.SOURCE。当设定为RetentionPolicy.RUNTIME时,这个注解就是运行时注解。
9.1.3 注解处理器
对于不同的注解有不同的注解处理器。 虽然注解处理器的编写会千变万化,但是其也有处理标准,比如:针对运行时注解会采用反射机制处理, 针对编译时注解会采用 AbstractProcessor 来处理。
1.运行时注解处理器
处理运行时注解需要用到反射机制。通过反射获得Field、Method等信息,调用getAnnotation()
方法即可获得注解信息。
2.编译时注解处理器
- 定义注解
- 编写注解处理器
- 注解处理器ClassProcessor,它继承AbstractProcessor
- 方法有init、process、getSupportedAnnotationTypes、getSupportedSourceVersion等。
- 注册注解处理器
- 使用Google 开源的AutoService
- 在ClassProcessor中添加@AutoService(Processor.class)
- 应用注解
- 使用android-apt插件
- 仅仅在编译时期去依赖注解处理器所在的函数库并进行工作,但不会打包到APK中。
- 为注解处理器生成的代码设置好路径,以便Android Studio能够找到它。
9.2 依赖注入的原理
9.2.1 控制反转与依赖注入
1.控制反转
IoC是Inversion of Control的缩写,即控制反转,借助于“第三方”实现具有依赖关系的对象之间的解耦。
- 引入IoC容器之前:对象A依赖于对象B,那么对象A在初始化的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建对象B还是使用对象B,控制权都在自己手上。
- 引入IoC容器之后:由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IoC容器会主动创建一个对象B注入到对象A需要的地方。
- 通过引入Ioc容器前后的对比,可以看出:对象A获得依赖对象B的过程,由主动行为变为被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。
2.依赖注入
Martin Fowler提问:
控制反转是“哪些方面的控制被反转了呢?”
答:“获得依赖对象的过程被反转了”。
控制被反转之后,获得依赖对象的过程由自身管理变为由IoC容器主动注入。于是,他给控制反转取了一个更合适的名字,叫作依赖注入(Dependency Injection),简称DI。所谓依赖注入,是指由IoC容器在运行期间,动态地将某种依赖关系注入到对象中。
9.2.2 依赖注入的实现方式
这里举一个汽车的例子,汽车类Car包含了引擎Engine等组件:
public class Car{
private Engine mEngine;
public Car(){
mEngine = new PetrolEngine();
}
}
构造方法注入
public class Car{ private Engine mEngine;
public Car(Engine mEngine){
this.mEngine = mEngine;
}
}Setter方法注入
public class Car{ private Engine mEngine;
public void set(Engine mEngine){
this.mEngine = mEngine;
}
}接口注入
public interface ICar{ public void setEngine(Engine engine);
}
public class Car implements ICar{
private Engine mEngine;
@Override
public void setEngine(Engine engine){
this.mEngine = engine;
}
}
9.3 依赖注入框架
9.3.1 为何使用依赖注入框架
9.3.2 解析ButterKnife
1.ButterKnife的注解使用方法
- 添加依赖库
- 绑定控件
- @BindView
- 绑定资源
- @BindString、@BindArray、@BindBool、@BindColor、@BindDimen、@BindDrawable和@BindBitmap
- 绑定监听
- @OnClick、@OnLongClick、@OnTextChanged、@OnTouch、@OnItemClick
- 可选绑定
- @Nullable防止找不到资源
2. ButterKnife原理解析
- ButterKnifeProcessor源码分析
- ButterKnifeProcessor#process#findAndParseTargets:查找所有注解并解析
- brewJava:方法将使用注解的类生成一个JavaFile
- ButterKnife的bind方法
1. 得到Activity的 DecorView,findBindingConstructorForClass方法获得构造器并生成实例 - 生成的辅助类分析
- DecorView传入这个类中,通过findViewById将View返回。
9.3.3 解析Dagger2
Dagger2是一个基于JSR-330(Java依赖注入)标准的依赖注入框架,在编译期间自动生成代码,负责依赖对象的创建。
1.注解使用方法
- 添加依赖库
@Inject和@Component
- @Inject:用于标记需要注入的依赖
- @Component:注入器,可以编译生成类,调用注入方法
@Component public interface MainActivityComponent {
void inject (MainActivity activity) ;
}
public class MainActivity extends AppCompatActivity {
@Inject
Watch watch;
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Dagger MainActivityComponent.create().inject(this);
watch.work();
}
}@Module和@Provides
- 如果项目中使用了第三方的类库,或需要注入的类是抽象类,可以采用@Module和@Provides提供注入器。
@Module public class GsonModule {
@Provides
public Gson provideGson() {
return new Gson();
}
}
@Component (modules = GsonModule.class)
public interface MainActivityComponent {
void inject (MainActivity activity);
}@Named和@Qualifier
- @Qualifier 是限定符,@Named 则是@Qualifier 的一种实现。
- 当有两个相同的依赖时,它们都继承同一个父类或者均实现同一个接口。当它们被提供给高层时,Component 就不知道我们到底要提供哪一个依赖 对象了,因为它找到了两个。
- 或者通过自定义注解来实现。
@Module public class Eng ineModule {
@Provides
@Named("Gasoline")
public Engine provideGasoline() {
return new GasolineEngine() ;
}
@Provides
@Named("Diesel")
public Engine provideDiesel() {
return new DieselEngine() ;
}
}
public class Car {
private Engine engine;
@Inject
public Car(@Named("Diesel") Engine engine) {
this.engine = engine;
}
}@Qualifier @Retention(RUNTIME)
public dinterface Gasoline {}
@Qualifier
@Retention(RUNTIME)
public dinterface Diesel {}
@Module
public class EngineModule {
@Provides
@Gasoline
public Engine provideGasoline() {
return new Gasol ineEngine();
}
@Provides
@Diesel
public Engine provideDiesel() {
return new DieselEngine();
}
}
public class Car {
private Engine engine;
@Inject
public Car(@Gasoline Engine engine) {
this.engine = engine;
}
public String run() {
return engine.work();
}
}@Singleton和@Scope
- @Scope是用来自定义注解的,而@Singleton则是用来配合实现局部单例和全局单例的。@Singleton本身不具备创建单例的能力。
- 如果想实现全局单例,我们可以用@Scope结合Application来实现。
@Component的dependencies
- @Component也可以用dependencies依赖于其他Component。
2.懒加载
Dagger2提供了懒加载模式,在@Inject的时候不初始化,而是使用的时候,调用get方法来获取实例。
3.Dagger2原理解析
- WatchModule_ProvideWatchFactory 用来生成 Watch 实例;
- Dagger2Activity_MemberInject 将 Watch 实例赋值 给 MainActivity 的成员变量 Watch;
- DaggerActivityComponent则作为程序入口和桥梁,负责初始化 WatchModule_ProvideWatchFactory和Dagger2Activity_MemberInject,并将它们串联起来。