Android开发进阶——使用Dagger2

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cwt8805/article/details/72848149

前言

关于Dagger2的学习,首先看的官方文档,确实看不懂。然后搜索网络上的介绍博文,不乏一些讲得比较好的,如这个。但终究不够透彻,还是得回头研究官方文档。本文不仅仅是翻译,而是记录了自己对官方文档的理解。

提供依赖的两种方式

使用@Inject注解构造器

class Thermosiphon implements Pump {
    private final Heater heater;

    @Inject
    Thermosiphon(Heater heater) {
        this.heater = heater;
    }
}

使用@Module

@Module
class DripCoffeeModule {
    @Provides static Heater provideHeater() {
        return new ElectricHeater();
    }

    @Provides static Pump providePump(Thermosiphon pump) {
        return pump;
    }
}

使用@Module替代@Inject的情况有:
- 依赖是一个接口,不能使用构造器。
- 依赖是第三方类,不能添加@Inject注解。
- 依赖在使用前需要被配置。

依照惯例,@Provides注解的方法名应该使用provide作为前缀,@Module注解的类名应该用Module作为后缀。

注入依赖的两种方式

使用@Inject和@Provides注解的类对象组成了一个有向图(graph),该图的各顶点(vertex)由它们的依赖连接。我们可以通过由@Component注解的接口来访问这个图,以满足我们的依赖。我们将Module传递给@Component的modules参数,Dagger2负责生成一个该接口的实现类。事实上我们构造一个Component的过程就是在构造一个graph。

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
    CoffeeMaker maker();
}

Dagger2生成的实现类名由Dagger加上接口名构成。如下获取一个实现类的实例:

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())
    .build();

任何具有默认构造器的Module都不需要显示地设置,builder会自动创建Module。因而上面的代码可以简化为:

CoffeeShop coffeeShop = DaggerCoffeeShop.builder().build();

如果所有的依赖并不需要构造Module就能提供(如DripCoffeeModule中的所有@Provides方法都是静态的),那么实现类会提供一个create()方法来快速地获取一个Component实例。

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

Component可以包含两种方法,分别是Provision method和Members-injection method,Provision方法没有参数并且返回需要的类型,Members-injection方法一般没有返回值,参数是需要注入成员变量的类型(一般命名为inject)。或许后者方法在Android开发中更为常见。

通过Provision方法获取依赖

得到Component实例后,通过调用Component的Provision方法获取已经准备好的对象实例:

class CoffeeMaker {
    @Inject Heater heater;
    @Inject Pump pump;

    ...
}

public class CoffeeApp {
    public static void main(String[] args) {
        CoffeeShop coffeeShop = DaggerCoffeeShop.create();
        coffeeShop.maker().brew();
    }
}

通过Members-injection方法注入依赖

class MainActivity extends Activity {
    @Inject DataSource dataSource;

    private DaggerActivityComponent component;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        component = DaggerActivityComponent.create();
        component.inject(this);
        ...
    }
}

事实上依赖不只两种来源

上面说过,Component提供了访问一个有向图的接口,有向图中的每个边(也就是上面所说的依赖)称为一个binding,binding的来源如下:
- Module中通过@Provides注解的方法,Component通过@Component.modules直接引用,如果Module中通过@Module.includes定义了子Module,也一并包括。
- 使用@Inject注解构造器的类型(没有使用@Scope或者@Scope同Component一致)。
- 该Component依赖的Component的Provision方法。
- 该Component自身。
- subcomponent的未限定builder。
- 以上binding的Provider或者Lazy包装类。
- 以上binding的Lazy包装类的Provider包装类(如Provider

单例和binding的作用域

@Singleton是一个预定义的@Scope。使用@Singleton注解@Provides方法或者可注入类。Component会使用同一个类实例(也就是单例)来进行依赖注入。

扫描二维码关注公众号,回复: 3893091 查看本文章
@Provides @Singleton static Heater provideHeater() {
    return new ElectricHeater();
}

可注入类上的@Singleton注解事实上也起到文档的作用,用于提醒代码维护者该类可能被多个线程共享。

@Singleton
class CoffeeMaker {
    ...
}

根据上面binding的第二条来源我们知道,Component只会识别未加作用域或者跟自己的作用域一致的依赖,所以如果@Provides和可注入类加上了作用域,那么Component本身一定要有@Scope进行注解(预定义的或者自定义)。

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
    CoffeeMaker maker();
}

为什么会有Scope这个东西呢?我们知道任何对象都有生命周期,包括我们创建的Component对象本身。比如我们在Activity的onCreate中创建了一个Component,然后该Component向该Activity中注入了一个DataSource,即便该DataSource被注解为@Singleton,该Activity被销毁并重新创建后,这次注入的DataSource和第一次并不是同一个实例。所以像这种场合我们一般使用一个更有意义的自定义@Scope注解,如@PerActivity。正如前面说过的,Component只会保证跟自己有相同Scope的类在自己的生命周期内只有一个实例。因此我们需要自己确保Component实例的作用域和它的注解的意义一致,Dagger并不会负责。

Component的dependencies

正如binding的第三条来源可知,Component不仅仅可以有modules也可以有dependencies。也就是依赖其他的Component。

@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
@PerActivity
public interface ActivityComponent {
    void inject(MainActivity mainActivity);
}

注意,只有被依赖的Component中的Provision方法才会参与该Component代表的有向图的构建,如上例中的ApplicationComponent中的modules提供的依赖并不能访问。如下只有DataManager对象可以被参与ActivityComponent的注入工作。

@Component(modules = ApplicationModule.class)
@Singleton
public interface ApplicationComponent {
    void inject(DemoApplication demoApplication);
    DataManager getDataManager();
}

Component的完整创建过程如下:

public static void main(String[] args) {
     OtherComponent otherComponent = ...;
     MyComponent component = DaggerMyComponent.builder()
         //必需,component dependencies一定要设置
         .otherComponent(otherComponent)
         //必需,FlagsModule有构造器参数
         .flagsModule(new FlagsModule(args))
         //可省略,MyApplicationModule有可用无参构造器
         .myApplicationModule(new MyApplicationModule())
         .build();
   }

Component的Subcomponents

Component之间的关系有两种,除了上面的dependencies还有Subcomponents。subcomponent可以完整的继承父Component的binding图,不仅仅只是访问Provision方法提供的依赖。subcomponent可以使用两种方式声明,列入@Module的subcomponents参数中,或者在Component中通过工厂方法生成,方法名随意,但是要返回一个subcomponent,参数可以包括任意数量的Module,如果Module有可用的无参构造器,那么该Module可以省略。构建原则同Component。如下示例:

@Component
@Singleton
interface ApplicationComponent {
    // component methods...

    RequestComponent newRequestComponent(RequestModule requestModule);
}

自定义Component的Builder

@Component(modules = {BackendModule.class, FrontendModule.class})
interface MyComponent {
    MyWidget myWidget();

    @Component.Builder
    interface Builder {
        MyComponent build();
        Builder backendModule(BackendModule bm);
        Builder frontendModule(FrontendModule fm);
    }
}

可重用的Scope

简而言之,这种Scope限制依赖的实例数量,但是又并不保证只有一个实例。看代码:

@Reusable //生成多少个scooper没有关系,只是不要浪费内存
class CoffeeScooper {
    @Inject CoffeeScooper() {}
}

@Module
class CashRegisterModule {
    @Provides
    @Reusable //不要这样使用,你肯定会在意现金存放在哪个特定的Register里面
              //所以这里应该使用一个特定的Scope
    static CashRegister badIdeaCashRegister() {
        return new CashRegister();
    }
}

@Reusable //不要这样使用,咖啡过滤器肯定每次都要新的
          //不需要Scope
class CoffeeFilter {
    @Inject CoffeeFilter() {}
}

可释放的引用

当一个binding使用了Scope后,Component就持有这个绑定对象的强引用。在内存敏感的环境下这样做不合适,这种情况下应该定义一个使用@CanReleaseReferences注解的Scope:

@Documented
@Retention(RUNTIME)
@CanReleaseReferences
@Scope
public @interface MyScope {}

然后注入一个ReleasableReferenceManager进行内存管理。

@Inject 
@ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;

void lowMemory() {
    myScopeReferences.releaseStrongReferences();
}

void highMemory() {
    myScopeReferences.restoreStrongReferences();
}

Lazy注入

class GridingCoffeeMaker {
    @Inject Lazy<Grinder> lazyGrinder;

    public void brew() {
        while (needsGrinding()) {
            // Grinder会在调用get方法时才创建,然后缓存起来
            lazyGrinder.get().grind();
        }
    }
}

Provider注入

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //每次都返回新的Filter实例
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}

限定符(Qualifier)

如果有多个依赖满足条件,如何注入呢?这时就需要Qualifier了。

class ExpensiveCoffeeMaker {
    @Inject @Named("water") Heater waterHeater;
    @Inject @Named("hot plate") Heater hotPlateHeater;
    ...
}

@Provides @Named("hot plate") 
static Heater provideHotPlateHeater() {
    return new ElectricHeater(70);
}

@Named是一个预定义的@Qualifier注解,我们同样可以自定义Qualifier

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
    String value() default "";
}

绑定实例

假如参与绑定的数据在构建Component的时候才能得到,如命令行参数。当然你可以将这种数据通过Module的构造器参数传递进去,然而自定义builder确是更好的选择。

@Component(modules = AppModule.class)
interface AppComponent {
    App app();

    @Component.Builder
    interface Builder {
        @BindsInstance Builder userName(@UserName String userName);
        AppComponent build();
    }
}

public static void main(String[] args) {
    if (args.length > 1) { exit(1); }
    App app = DaggerAppComponent.builder()
        .userName(args[0])
        .build()
        .app();
    app.run();
}

猜你喜欢

转载自blog.csdn.net/cwt8805/article/details/72848149
今日推荐