09 注入与依赖注入框架

2019/03/27 posted in  Android进阶之光

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.编译时注解处理器

  1. 定义注解
  2. 编写注解处理器
    1. 注解处理器ClassProcessor,它继承AbstractProcessor
    2. 方法有init、process、getSupportedAnnotationTypes、getSupportedSourceVersion等。
  3. 注册注解处理器
    1. 使用Google 开源的AutoService
    2. 在ClassProcessor中添加@AutoService(Processor.class)
  4. 应用注解
  5. 使用android-apt插件
    1. 仅仅在编译时期去依赖注解处理器所在的函数库并进行工作,但不会打包到APK中。
    2. 为注解处理器生成的代码设置好路径,以便Android Studio能够找到它。

9.2 依赖注入的原理

9.2.1 控制反转与依赖注入

1.控制反转

IoC是Inversion of Control的缩写,即控制反转,借助于“第三方”实现具有依赖关系的对象之间的解耦。

  1. 引入IoC容器之前:对象A依赖于对象B,那么对象A在初始化的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建对象B还是使用对象B,控制权都在自己手上。
  2. 引入IoC容器之后:由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IoC容器会主动创建一个对象B注入到对象A需要的地方。
  3. 通过引入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();
    }
}
  1. 构造方法注入

    public class Car{
        private Engine mEngine;
    public Car(Engine mEngine){
    this.mEngine = mEngine;
    }
    }
  2. Setter方法注入

    public class Car{
        private Engine mEngine;
    public void set(Engine mEngine){
    this.mEngine = mEngine;
    }
    }
  3. 接口注入

    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的注解使用方法

  1. 添加依赖库
  2. 绑定控件
    • @BindView
  3. 绑定资源
    • @BindString、@BindArray、@BindBool、@BindColor、@BindDimen、@BindDrawable和@BindBitmap
  4. 绑定监听
    • @OnClick、@OnLongClick、@OnTextChanged、@OnTouch、@OnItemClick
  5. 可选绑定
    • @Nullable防止找不到资源

2. ButterKnife原理解析

  1. ButterKnifeProcessor源码分析
    1. ButterKnifeProcessor#process#findAndParseTargets:查找所有注解并解析
    2. brewJava:方法将使用注解的类生成一个JavaFile
  2. ButterKnife的bind方法
    1. 得到Activity的 DecorView,findBindingConstructorForClass方法获得构造器并生成实例
  3. 生成的辅助类分析
    1. DecorView传入这个类中,通过findViewById将View返回。

9.3.3 解析Dagger2

Dagger2是一个基于JSR-330(Java依赖注入)标准的依赖注入框架,在编译期间自动生成代码,负责依赖对象的创建。

1.注解使用方法

  1. 添加依赖库
  2. @Inject和@Component

    1. @Inject:用于标记需要注入的依赖
    2. @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();
    }
    }
  3. @Module和@Provides

    1. 如果项目中使用了第三方的类库,或需要注入的类是抽象类,可以采用@Module和@Provides提供注入器。
    @Module
    public class GsonModule {
    @Provides
    public Gson provideGson() {
    return new Gson();
    }
    }
    @Component (modules = GsonModule.class)
    public interface MainActivityComponent {
    void inject (MainActivity activity);
    }
  4. @Named和@Qualifier

    1. @Qualifier 是限定符,@Named 则是@Qualifier 的一种实现。
    2. 当有两个相同的依赖时,它们都继承同一个父类或者均实现同一个接口。当它们被提供给高层时,Component 就不知道我们到底要提供哪一个依赖 对象了,因为它找到了两个。
    3. 或者通过自定义注解来实现。
    @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();
    }
    }
  5. @Singleton和@Scope

    1. @Scope是用来自定义注解的,而@Singleton则是用来配合实现局部单例和全局单例的。@Singleton本身不具备创建单例的能力。
    2. 如果想实现全局单例,我们可以用@Scope结合Application来实现。
  6. @Component的dependencies

    1. @Component也可以用dependencies依赖于其他Component。

2.懒加载
Dagger2提供了懒加载模式,在@Inject的时候不初始化,而是使用的时候,调用get方法来获取实例。
3.Dagger2原理解析

  1. WatchModule_ProvideWatchFactory 用来生成 Watch 实例;
  2. Dagger2Activity_MemberInject 将 Watch 实例赋值 给 MainActivity 的成员变量 Watch;
  3. DaggerActivityComponent则作为程序入口和桥梁,负责初始化 WatchModule_ProvideWatchFactory和Dagger2Activity_MemberInject,并将它们串联起来。

9.4 本章小结