Android Dagger2快速入门篇

一、什么是Dagger2

     Dagger2是编译时依赖注入框架,也就是说,这个框架解决的问题是,在编译阶段动态生成依赖注入代码,有别于其他依赖注入框架是利用反射或者运行时再动态生成字节码。

二、什么是依赖注入

     通俗点解释就是,a依赖b,但a不控制b的创建和销毁,仅使用b,那么b的控制权交给a之外处理,这叫控制反转(IOC),而a要依赖b,必然要使用b的实例,那么有以下几种方法可以拿到b的实例
1)通过a的接口,把b传入;
2)通过a的构造方法,把b传入;
3)通过设置a的属性,把b传入;
     这个过程叫依赖注入(DI)

三、依赖注入有什么好处

Android Dagger2快速入门篇_第1张图片
举个栗子:我们在写面向对象程序时,往往会用到组合,即在A类中引用B类,从而可以在A类调用B类的方法完成某些功能,如下所示:

public class ClassA {
    ...
    ClassB b;
    ...
    public ClassA() {
        b = new ClassB();
    }

    public void do() {
        ...
        b.doSomething();
        ...
    }
}

【罪恶根源】这个时候就产生了依赖问题,ClassA依赖于ClassB,必须借助ClassB的方法,才能完成一些功能。这样咋一看,好像并没有什么问题,但是我们在ClassA的构造方法里面直接创建了ClassB的实例,问题就出现在这:
1)在ClassA里直接创建ClassB实例,违背了单一职责原则,ClassB实例的创建不应由ClassA来完成;
2)其次耦合度增加,扩展性差,如果需求改变,我们想在实例化ClassB的时候传入参数,那么不得不改动ClassA的构造方法,不符合开闭原则
【解决法门】 要解决上述问题,就需要用到依赖注入,将所需依赖的B类实例注入到宿主A类(或者叫目标类)中,依赖注入有以下几种方式:
1)通过接口注入

interface ClassBInterface {
    void setB(ClassB b);
}

public class ClassA implements ClassBInterface {
    ClassB classB;

    @override
    void setB(ClassB b) {
        classB = b;
    }
}

2)通过构造方法注入

public class ClassA {
    ClassB classB;

    public void ClassA(ClassB b) {
        classB = b;
    }
}

3)通过set方法注入

public class ClassA {
    ClassB classB;

    public void setClassB(ClassB b) {
        classB = b;
    }
}

     从上面可知,随着DI的频繁使用,要实现IOC,会有很多重复代码,甚至随着技术的发展,有更多新的实现方法和方案,那么有人就把这些实现IOC的代码打包成组件或框架,来避免人们重复造轮子,Dagger2就应运而生。

四、如何引入Dagger2

在对应需要用到Dagger2项目的build.gradle中添加以下配置即完成引入,可见源码地址

// Add Dagger dependencies
dependencies {
  api 'com.google.dagger:dagger:2.x'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

五、如何使用Dagger2

     一般项目开发中会使用到Mvp设计模式开发,Presenter会持有View与Model的引用,并且一般需要在View即Activity或者Fragment中new一个Presenter,那么看看使用Dagger2怎么实现
Android Dagger2快速入门篇_第2张图片
Step 1:先声明对象实例提供类,即用@Module注解的类,并在该类中用@Provides注解提供对象实例的方法,@Singleton注解表示该方法提供的对象是单例,即每次调用该方法获取到的都是第一次初始化的那个对象实例

/**
 * @Description:首页对象实例提供器
 */
@Module
public class HomeModule {//类为抽象类的话,那么方法只能是抽象方法,或者静态方法
    private Activity mActivity;
    public HomeModule(Activity activity){
        this.mActivity = activity;
    }

    /**
     * 提供当前的activity上下文环境变量
     * @return
     */
    @Provides
    @ActivityScope
    Context provideContext(){
        return this.mActivity;
    }

}

Step 2:声明依赖注入动作执行类(也叫注射器),即用@Component注解的类,并在括号中声明依赖注入时,需要用到的对象实例提供类Module,假如依赖的Module方法中有声明Scope,比如:@Singleton、自定义的@ActivityScope等,那么@Component类也需要声明相同的Scope

/**
 * @Description:首页dragger注入器
 */
@ActivityScope//依赖的component有设置scope的话,这里也需要设置scope,而且不能跟依赖的scope相同
@Component(modules = {HomeModule.class})
public interface HomeComponent{
    //从HomeFragment中查找@Inject的字段,并创建实例
    void inject(HomeFragment homeFragment);
    @Component.Builder
    interface Builder{
        @BindsInstance
        HomeComponent.Builder setView(HomeFragment homeFragment);
        HomeComponent.Builder setHomeModule(HomeModule homeModule);
        HomeComponent build();
    }
}

Step 3:重新build一下项目,就会在你的项目\build\generated\ap_generated_sources\release\out自动生成对应的依赖注入代码,规则是Dagger+Component类名,对于上述例子会自动生成一个HomeComponent接口实现类DaggerHomeComponent

public final class DaggerHomeComponent implements HomeComponent {
  private final HomeFragment setView;

  private Provider<Context> provideContextProvider;

  private DaggerHomeComponent(HomeModule homeModuleParam, HomeFragment setViewParam) {
    this.setView = setViewParam;
    initialize(homeModuleParam, setViewParam);
  }

  public static HomeComponent.Builder builder() {
    return new Builder();
  }

  private AppBannerRequestData getAppBannerRequestData() {
    return new AppBannerRequestData(provideContextProvider.get());}

  private CoreIconRequestData getCoreIconRequestData() {
    return new CoreIconRequestData(provideContextProvider.get());}

  private CmsRequestData getCmsRequestData() {
    return new CmsRequestData(provideContextProvider.get());}

  private GameRequestData getGameRequestData() {
    return new GameRequestData(provideContextProvider.get());}

  private HomePresenter getHomePresenter() {
    return injectHomePresenter(HomePresenter_Factory.newInstance(setView, new HomeModel()));}

  @SuppressWarnings("unchecked")
  private void initialize(final HomeModule homeModuleParam, final HomeFragment setViewParam) {
    this.provideContextProvider = DoubleCheck.provider(HomeModule_ProvideContextFactory.create(homeModuleParam));
  }

  @Override
  public void inject(HomeFragment homeFragment) {
    injectHomeFragment(homeFragment);}

  private HomePresenter injectHomePresenter(HomePresenter instance) {
    HomePresenter_MembersInjector.injectMAppBannerRequestData(instance, getAppBannerRequestData());
    HomePresenter_MembersInjector.injectMCoreIconRequestData(instance, getCoreIconRequestData());
    HomePresenter_MembersInjector.injectMCmsRequestData(instance, getCmsRequestData());
    HomePresenter_MembersInjector.injectMGameRequestData(instance, getGameRequestData());
    HomePresenter_MembersInjector.injectMGameTitle(instance, new GameTitle());
    return instance;
  }

  private HomeFragment injectHomeFragment(HomeFragment instance) {
    BaseFragment_MembersInjector.injectMPresenter(instance, getHomePresenter());
    return instance;
  }

  private static final class Builder implements HomeComponent.Builder {
    private HomeFragment setView;

    private HomeModule homeModule;

    @Override
    public Builder setView(HomeFragment homeFragment) {
      this.setView = Preconditions.checkNotNull(homeFragment);
      return this;
    }

    @Override
    public Builder setHomeModule(HomeModule homeModule) {
      this.homeModule = Preconditions.checkNotNull(homeModule);
      return this;
    }

    @Override
    public HomeComponent build() {
      Preconditions.checkBuilderRequirement(setView, HomeFragment.class);
      Preconditions.checkBuilderRequirement(homeModule, HomeModule.class);
      return new DaggerHomeComponent(homeModule, setView);
    }
  }
}

Step 4:最后,在需要进行依赖注入的目标类中,调用自动生成的DaggerHomeComponent,设置Builder对应属性,同时,要是目标类有属性设置@Inject声明需要依赖注入的话,也需要把目标类传入进去,一般方法名取为inject,即完成依赖注入

DaggerAppComponent
.builder()
.setAppModule(new AppModule(this))
.setApplication(this)
.build();

     上述就是一个简单的且常见的一个Dagger2使用例子,下面我们仔细分析讲解一下上述案例涉及到的概念以及引起的疑问

六、Dagger2中的概念解析

1、总的来说,Dagger2就是通过依赖注入完成依赖对象的创建初始化。
这个过程涉及到三个概念:

  • 依赖提供方(生产者),即上述@Module注解类中对应的@Provide注解的方法提供创建实例
  • 依赖注入容器(桥梁),即上述@Component注解类
  • 依赖需求方(消费者),即有@Inject注解字段属性的目标类
    Android Dagger2快速入门篇_第3张图片

七、Dagger2是怎样完成依赖注入的呢

Dagger2寻找依赖注入的规则如下:

步骤1:查找Module中是否存在创建需要依赖注入类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
步骤3:若Module中不存在创建需要依赖注入类的方法,则查找该类中Inject注解的构造函数,看构造函数是否存在参数
步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

常见的专用名词解析:

@Inject

主要有两个作用

  • @Inject注解构造方法,此时这个构造方法就作为依赖注入提供方,作用等同于@Module,但是优先级比@Module低;
    使用@Inject注解被目标类依赖的类的构造函数,让Dagger2帮我们实例化该类,并注入到目标类
  • @Inject注解类成员字段(注意,不能是private修饰),此时,该类就成为了依赖需求方,即依赖注入的目标类,那么dagger2就会按照上述规则帮我们实例化该成员类,并注入。

使用@Inject可以让IoC容器负责生成instance,如果没有这个注解,dagger将不认识,当做普通类,无法代理

@Module

@Module 注解类,负责创建提供依赖对象实例。
Module 其实是一个简单工厂模式,Module 里面的方法都是创建相应类实例的方法。
     到这里,或许你会有疑问,既然使用@Inject注解构造方法就能让Dagger2自动去创建实例,为何还需要有@Module来创建提供实例呢?
Android Dagger2快速入门篇_第4张图片
原因主要如下:
1)@Inject只能注解自己写的类,要是你的项目依赖到第三方类库的话,那么就需要@Module注解类中的@Provide方法来创建获得第三方类库的对象。
2) @Inject只能注解一个类中的一个构造方法,要注解多个会报错,这时就可以借助@Module注解类中的@Provide方法来通过不同的构造方法来创建这个类实例,不过注意,由于多个@Provide方法返回同一个类的实例,那么需要添加@Named声明别名,否则也会报错

@Provides

用于注解@Module 类中的方法
在Module中,我们定义的方法用这个注解,是告诉Dagger2我们想要构造对象并提供这些依赖

@Component

1) @Component一般用来注解接口。
2 )负责在@Inject和@Module之间建立连接。
也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。
3 )实例化目标类中@Inject注解的类成员变量时,遇到没有@Inject注解的构造函数的类依赖,则该依赖由@Module修饰的类提供。
4)一般Component需要引用到目标类的实例,比如上述第一个例子用inject(this)设置进去,这样目标类就和Component建立了联系,Component会去遍历目标类中用Inject注解标注的属性,查找到相应的属性后,会接着查找Module中是否有提供该属性实例的provide方法,没有的话,就去查找该属性对应的用Inject标注的构造函数,剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字:注入器(Injector)
Android Dagger2快速入门篇_第5张图片

@Scope

@Scope这个注解作用等同于@Singleton,也就是声明对应的依赖注入实例是单例,但是这个单例是指在Component生命周期内唯一,换句话说,就是被自定义@Scope注解之后,也就限制了被标注的实例提供者,只会在Component生命周期内实例化该对象一次,之后每次调用都是返回第一次实例化的对象,除非重新调用一次DaggerXxComponent初始化注入方法才会返回新的
【注意事项】

  • 如果在Module的provideXXX方法上加上了@Scope声明,那么在与他关联的Component上也必须加上相同的@Scope声明
    在这里插入图片描述

  • 当Module的provideXXX方法和Component都加上了@Scope声明,那么在Component实例的生命周期内,只会创建一个由provideXXX方法返回的实例。也就是说,该Component会持有之前通过provideXXX方法创建的实例的引用,如果之前创建过,那么就不再调用Module的provideXXX去创建新的实例,而是直接返回它之前持有的那一份。

  • 在依赖或者继承的组织方式中,如果其中一个Component声明了@Scope,那么其它的Component也需要声明。

  • 在依赖关系中,被依赖的Component和需要依赖的Component的@Scope不能相同
    在这里插入图片描述

  • 在依赖关系中,需要依赖的Component的@Scope不可以为@Singleton
    在这里插入图片描述

  • 在组织关系中,子Component的@Scope不可以和父Component的@Scope相同:
    在这里插入图片描述

  • 在组织关系中,如果父Component的@Scope不为@Singleton,那么子Component的@Scope可以为@Singleton

那么,怎么自定义scope呢?我们来看个示例:

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScope {
}

@Module
public class HomeModule {//类为抽象类的话,那么方法只能是抽象方法,或者静态方法
    private Activity mActivity;
    public HomeModule(Activity activity){
        this.mActivity = activity;
    }

    /**
     * 提供当前的activity上下文环境变量
     * @return
     */
    @Provides
    @FragmentScope
    Context provideContext(){
        return this.mActivity;
    }

}

@FragmentScope//依赖的component有设置scope的话,这里也需要设置scope,而且不能跟依赖component的scope相同
@Component(modules = {HomeModule.class})
public interface HomeComponent{
    //从HomeFragment中查找@Inject的字段,并创建实例
    void inject(HomeFragment homeFragment);
    @Component.Builder
    interface Builder{
        @BindsInstance
        HomeComponent.Builder setView(HomeFragment homeFragment);
        HomeComponent.Builder setHomeModule(HomeModule homeModule);
        HomeComponent build();
    }
}

@Qulifier 与 @Named

我们知道@Module注解类中的Provide方法提供需要以依赖注入的实例对象,但是如果是不同的@Provides方法返回同一类型的对象,Dagger2会选择哪个?
举个例子:provideStudent() 和 provideNewStudent(),返回的都是Student对象。同时写在Module中,会出现什么问题?

@Module
public class MainModule {


  @Provides
  static Student provideStudent() {
      return new Student();
  }


  @Provides
  static Student provideNewStudent() {
      return new Student();
  }


  @Provides
  static Teacher provideTeacher() {
      return new Teacher();
  }

}

这里有两个方法同时返回了Student,我们编译一下,报错了,提示被绑定多次
在这里插入图片描述
那么,这时候@Qulifier 与 @Named就可以派上用场了
1 .@Qulifier
这时候就需要我们提供一个别名,让 目标类成员变量的类型创建方法的返回类型 形成一对一的关系,一般来说,使用@Qulifier是比较标准的方式。
1.1 定义一个接口学生A和学生B,分别代表两个学生,然后用@Qualifier修饰

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentA{}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentB{}

然后分别在上述Module加上对应注解


    @Provides
    @StudentA
    static Student provideStudent() {
        return new Student();
    }


    @Provides
    @StudentB
    static Student provideNewStudent() {
        return new Student();
    }

最后在引用的位置也加上注解,形成一对一的关系

public class MainActivity extends AppCompatActivity {
    @Inject
    @StudentA
    Student student;

    @Inject
    @StudentB
    Student student1;

这样就可以保证,即使是返回相同的类型,也能保证一一对应的关系。
2. @Named注解
除了上面的@Qualifier注解,还可以用@Named注解达到同样的效果

  @Provides
    @Named("StudentA")
    static Student provideStudent() {
        return new Student();
    }


    @Provides
    @Named("StudentB")
    static Student provideNewStudent() {
        return new Student();
    }

使用的时候

    @Inject
    @Named("StudentA")
    Student student;

    @Inject
    @Named("StudentB")
    Student student1;

@Binds

@Binds可以理解为关联,首先它是跟@Provides使用位置一样,不同的在于@Provides 注解的方法都是有具体实现的,而@Binds修饰的只有方法定义,并没有具体的实现的,在方法定义中方法参数必须是 返回值的实现类。这样创建实体类的地方就不用在Modules 中直接new该对象了,这时,module也需要声明为抽象类,同时其他@Provide注解的方法也需要用static修饰,例如:

@Module
public abstract class AppModule {
    @Binds
    @Named("AppManager")
    abstract IAppManager bindAppManager(AppManager appManager);

    @Provides
    static Retrofit provideRetrofit() {
        return new Retrofit.Builder().build();
    }
}

public class AppManager implements IAppManager {

    @Inject
    Application application;

    @Inject
    public AppManager() {

    }

}



@Component(modules = AppModule.class)
public interface AppComponent {
    Application application();

    Retrofit retrofit();

    void inject(Application application);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
}

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // 注入
        DaggerAppComponent.builder().application(this).build().inject(this);
    }
}

上述例子中,我们还看到了一个新的注解@BindsInstance,这个注解作用效果等同于下面代码实现,大家可有细品一下

@Module
public abstract class AppModule {
    private Application application;
    public AppModule(Application application){
         this.application = application;
    }
    @Binds
    @Named("AppManager")
    abstract IAppManager bindAppManager(AppManager appManager);

    @Provides
    static Retrofit provideRetrofit() {
        return new Retrofit.Builder().build();
    }
    
    @Provides
    static Retrofit provideApplication() {
        return application;
    }
}

public class AppManager implements IAppManager {

    @Inject
    Application application;

    @Inject
    public AppManager() {

    }

}



@Component(modules = AppModule.class)
public interface AppComponent {
    Application application();

    Retrofit retrofit();

    void inject(Application application);

    @Component.Builder
    interface Builder {
        AppComponent build();
    }
}

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // 注入
        DaggerAppComponent.builder().application(this).build().inject(this);
    }
}

就是假如不在AppComponent 的Builder 中声明@BindInstance注解的方法,那么就需要在AppModule 中增加以下代码以对外提供Application实例

    private Application application;
    public AppModule(Application application){
         this.application = application;
    }
    
    @Provides
    static Retrofit provideApplication() {
        return application;
    }

你可能感兴趣的:(android)