在了解了简单注入对象的使用后,我们将问题升级。我们平常开发中为了节省资源,在APP的生命周期内很多对象都是作为单例存在的,因此现在我们尝试解决三个问题
- 将一个对象注入到Application中,并且保证它在整个APP的生命周期内是单例的
- 这个对象应该符合依赖倒置原则,我们使用其抽象类来作为引用
- 之后为了方便在MainActivity中使用它,我们还应该能够将它注入到MainActivity中
1. 如何解决一个对象在某个生命周期内是单例的
现在我们向App中注入一个FactoryA的实例对象,并使得其在App的生命周期内是单例的,代码如下:
public class App extends Application {
@Inject
FactoryA mFactory;
// @Inject
// FactoryA mFactory2;
@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerAppComponent
.builder()
.build()
.inject(this);
mFactory.showMe();
// mFactory2.showMe();
}
}
@Module
public abstract class AppModule {
@Singleton
@Provides
public static FactoryA providesFactoryA() {
return new FactoryA();
}
}
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
void inject(App app);
}
如果你对简单注入已经熟练掌握的话,你应该发现这里使得对象成为单例只需要同时在@Provides注解的方法
和component接口
上添加@Singleton
这个注解即可。
我们看一下@Singleton
这个注解:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
事实上千万不要误将@Singleton
与单例
挂钩,真正起作用的是@Scope
这个注解,我们可以用任意被@Scope
所注解的注解来替代@Singleton
,它告诉Dagger2在自动生成的代码中,使用DoubleCheck
缓存获取到的对象实例来保证在Component组件所依附的生命周期内
对象的单例性,
2. 使用抽象类作为引用时如何注入
在大部分情况下,FactoryA应该作为Factory抽象类的具体实现存在,同时Factory还可能有其它实现类,即有如下代码:
public abstract class Factory {}
public class FactoryA extends Factory {}
public class FactoryB extends Factory {}
此时我们需要对module作出如下修改
@Module
public abstract class AppModule {
@Singleton
@Binds
public abstract Factory bindsFactory(FactoryB factoryB);
@Singleton
@Provides
public static FactoryA providesFactoryA() {
return new FactoryA();
}
@Singleton
@Provides
public static FactoryB providesFactoryB() {
return new FactoryB();
}
}
从这里可以看出
@Binds
注解用于module中的抽象方法,这个方法的参数应该是具体实现类,返回值应该是抽象类,它告诉Dagger2在自动生成的代码中注入抽象类引用对象时,应该使用哪一个具体实现类作为实例被获取- 在使用抽象类作为module时,获取实例对象的方法只能使用
static静态方法
2.1 解决@Provides
注解的方法返回类型一样的问题
在上面的示例中,可能有同学已经尝试将providesFactoryA
和providesFactoryB
的方法返回修改为Factory
,然而单纯这样修改并不能通过编译,因为Dagger2仅通过返回类型无法判断调用哪一个方法获取所需的实例,此时需要@Named
登场了:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
@Name
可以注解在参数、成员变量、方法上,事实上真正起作用的是@Qualifier
,我们将AppModule
改写成如下代码:
@Module
public abstract class AppModule {
@Singleton
@Binds
public abstract Factory bindsFactory(@Named("factoryA") Factory factory);
@Singleton
@Provides
@Named("factoryA")
public static Factory providesFactoryA(@Named("machine1") Machine machine) {
return new FactoryA(machine);
}
@Singleton
@Provides
@Named("factoryB")
public static Factory providesFactoryB(@Named("machine2") Machine machine) {
return new FactoryB(machine);
}
}
被不同value的@Named
注解标记后,Dagger2就可以正确的区分出对应的实例,除此之外我们可以通过自定义不同的被@Qualifier
注解的注解来区分,具体实现请自行实践
3. 将App中的单例对象同样注入到MainActivity中
要解决这个问题,我们可以使用@Component
的第二个属性dependencies
来依赖AppComponent
@ActivityScope
@Component(modules = {MainModule.class}, dependencies = {AppComponent.class})
public interface MainComponent {
void inject(MainActivity activity);
}
这里依赖之后会产生一个小问题,就是存在依赖关系的组件的作用域标记不能相同,因为在AppComponent
中我们标记了@Singleton
,因此在这里我们自定义了新的@Scope
,即@ActivityScope
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}
这里如果我们要使获取到的Product
在MainComponent实例的生命周期
内保持单例,我们就需要给提供实例的方法注解上同样的@ActivityScope
@Module
public class MainModule {
@ActivityScope
@Provides
public static Product provideProduct(Worker worker) {
return new Product(worker);
}
}
在完成上述改变之后,我们需要在构建MainComponent
实例时传入AppComponent
的实例
public class MainActivity extends AppCompatActivity {
@Inject
Factory mFactory;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainComponent mainComponent = DaggerMainComponent
.builder()
//这里传入appComponent实例,我们可以通过application获取到
.appComponent(((App) getApplication()).getAppComponent())
.build()
.inject(this);
}
}
最后一步,我们需要给AppComponent
接口添加一个方法,用来提供其所持有的单例对象
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
void inject(App app);
Factory factory();
}
4. 懒加载机制——Lazy
因为在实际场景中,有的对象并不是无时不刻的被使用到,我们需要它在真正被使用时才被实例化
,那么你可以使用Lazy
,这里以Product
为例,代码修改如下即可:
@Inject
Lazy<Product> mProduct;
Product product = mProduct.get();
5. 小结
成功解决问题之后(建议在每种情况下都看一下自动生成的代码,代码本身并不复杂,有利于理解Dagger2在背后是如何关联这些内容的),我们应该对三个构成部分有一定认识:
- Module类:提供注入对象的实例,如果可以使用
@Inject
注解构造函数来提供实例甚至可以不需要这一部分 - Component接口:解决直接依赖关系的中间桥梁,Dagger2会生成其实现类从而将获取到的实例赋值到目标容器中,可以认为它具有与依赖的目标容器相同的生命周期
- 目标容器:持有注入对象的引用,依赖于
Component
的实现类