关于Dagger2的学习,首先看的官方文档,确实看不懂。然后搜索网络上的介绍博文,不乏一些讲得比较好的,如这个。但终究不够透彻,还是得回头研究官方文档。本文不仅仅是翻译,而是记录了自己对官方文档的理解。
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
}
@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开发中更为常见。
得到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();
}
}
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
@Singleton是一个预定义的@Scope。使用@Singleton注解@Provides方法或者可注入类。Component会使用同一个类实例(也就是单例)来进行依赖注入。
@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并不会负责。
正如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之间的关系有两种,除了上面的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(modules = {BackendModule.class, FrontendModule.class})
interface MyComponent {
MyWidget myWidget();
@Component.Builder
interface Builder {
MyComponent build();
Builder backendModule(BackendModule bm);
Builder frontendModule(FrontendModule fm);
}
}
简而言之,这种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();
}
class GridingCoffeeMaker {
@Inject Lazy lazyGrinder;
public void brew() {
while (needsGrinding()) {
// Grinder会在调用get方法时才创建,然后缓存起来
lazyGrinder.get().grind();
}
}
}
class BigCoffeeMaker {
@Inject Provider filterProvider;
public void brew(int numberOfPots) {
...
for (int p = 0; p < numberOfPots; p++) {
maker.addFilter(filterProvider.get()); //每次都返回新的Filter实例
maker.addCoffee(...);
maker.percolate();
...
}
}
}
如果有多个依赖满足条件,如何注入呢?这时就需要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();
}