@BindsOptionalOf
使用dagger2的时候,会有个问题,加入我们在需要被注入的类中请求注入对象A和对象B,如果注入的时候Component没有提供对象B,那么就会编译不通过。
Dagger2中使用了BindsOptionalOf来解决这个问题。
@Module
public abstract class TestModule1 {
@BindsOptionalOf
abstract TestClass1 optionalTestClass1();
}
定义如上一个module,然后在原来的module中使用include包含这个module,或者原来component中modules增加这个module。
然后在被注入对象中增加注入
@Inject Optional<TestClass1> mTestClass1;
注入的类型需要时Optional(一个容器,java 1.8提供,或者guava提供都行)。注意,这个时候我们原来的module并没有 @Provides 方法用来提供TestClass1对象,但是编译能通过。这个时候,注入后,通过optional的get等方法将会返回空值。
我们也可以在module中提供@Provides方法来提供对象,这个时候Optional中就能取到非空的对象。
注意无论是否提供@Provides,请求注入的Optinal对象本身一定不为空,可能为空的是Optional容器内部的内容。
PS 可以注入的类型有
Optional<CoffeeCozy>
Optional<Provider<CoffeeCozy>>
Optional<Lazy<CoffeeCozy>>
Optional<Provider<Lazy<CoffeeCozy>>>
@BindsInstance
存在一种情况,我们需要注入一个对象,这个对象的构造函数需要提供参数。在Dagger2 使用(一)的注入变异3 和 注入变异4中已经有两种解决方案了。分别是通过module提供或者参数的构造函数添加@Inject。
现在,还有一种方法使用@BindsInstance
public class GameMode {
@Inject
public GameMode(String gameName){
}
}
存在一个GameMode类,构造函数需要一个gameName对象。
@Component
public interface GameComponent {
GameMode getGameMode();
@Component.Builder
interface Builder{
@BindsInstance Builder gameModeName(String str);
GameComponent build();
}
}
在GameComponent中,使用@Component.Builder定义一个接口,接口首先需要一个返回Component的方法。然后使用@BindsInstance 注解一个参数为String的方法。
GameComponent component = DaggerGameComponent.builder().gameModeName("hard").build(); component.getGameMode();
通过如上方式使用。这里我们在构建GameComponent的时候手动注入了字符串"hard"。相当于在Module中@Provides了这个字符串。
注意在调用build()
创建 Component 之前,所有@BindsInstance
方法必须先调用。如果@BindsInstance
方法的参数可能为 null,需要再用@Nullable
标记,同时标注 Inject 的地方也需要用@Nullable
标记。这时 Builder 也可以不调用@BindsInstance
方法,这样 Component 会默认设置 instance 为 null。
使用@Nullable的方法
public class GameMode {
@Inject
public GameMode(@Nullable String gameName){
}
}
@BindsInstance Builder gameModeName(@Nullable String str);
进阶进阶!!
加入GameMode的构造函数需要两个String怎么办?使用@BindInstance提供两个参数为String的方法,但是dagger如何选择用哪个方法注入到构造函数中?我们想到了@Qualifier
之前介绍的Qualifier是使用不同的参数名来区分的。其实直接使用不同的@Qualifier也能够做到同样的事情。
比如我定了两个Qualifier,然后给构造函数做上标记!
public class GameMode {
@Inject
public GameMode(@GameModeName String gameName , @PlayerName String playerName){
}
}
在component中同样使用标记
@Component
public interface GameComponent {
GameMode getGameMode();
@Component.Builder
interface Builder{
@BindsInstance Builder gameModeName(@GameModeName String str);
@BindsInstance Builder playerName(@PlayerName String str);
GameComponent build();
}
}
这样就能区分到底调用哪个参数了。
注:Subcomponent同样提供了 Subcomponet.Builder来完成同样的事情。
进阶进阶!!
关于 在上一篇@Scope中我们讲到了一个关于作用域的例子,我们复用它。
@ActivityScope
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {
ClassApplication getClassApplication();
ClassActivity getClassActivity();
ClassUser getClassUser();
@Subcomponent.Builder
interface ActivityBuilder{
@BindsInstance ActivityBuilder activity(Activity activity);
ActivityComponent build();
}
}
我们修改ActivityComponent,要求手动注入一个activity变量(在android中很正常,context还是需要的)。
这个时候如果我们在UserComponent中还是
ActivityComponent getActivityComponent(ActivityModule activityModule);
会出现错误,因为这样我们没有注入activity! 怎么办?
@UserScope
@Subcomponent(modules = UserModule.class)
public interface UserComponent {
//ActivityComponent getActivityComponent(ActivityModule activityModule);
ActivityComponent.ActivityBuilder newActivityComponentBuilder();
........
}
改成返回Buidler,然后开发者自己在Builder上调用activity注入activity,然后调用builder构建component这就没问题了。
// ActivityComponent activityComponent = mUserComponent. // newActivityComponentBuilder().activity(XXX).build(); // activityComponent.getClassApplication(); // activityComponent.getClassUser(); // activityComponent.getClassActivity();
像这样生成。
Set注入
之前介绍的内容都是单个对象的注入,那么我们是否能将多个对象注入到容器中呢?首先是Set
@Module
public class GameModule {
@Provides
@IntoSet
public Player getPlayer1(){
return new Player("1");
}
@Provides
@IntoSet
public Player getPlayer2(){
return new Player("1");
}
}
我在module中使用@Provides提供了Player,不同的是,添加了@IntoSet的注解,表示我会把这个注入到Set中。
然后在被注入的单位中请求注入,比如
@Inject Set<Player> mPlayers;
不需要在component中做其他修改,就能将player1和player2注入到mPlayers中。
注:如果存在多个Set,类型相同,就需要使用@Qualifier。
进阶!我们可以同时向Set注入多个对象
@Provides
@ElementsIntoSet
public Set<Player> getPlayers(){
HashSet set = new HashSet<>();
set.add(new Player("3"));
set.add(new Player("4"));
return set;
}
使用@ElementsIntoSet 并且返回Set<Player>对象。
Map注入
map的注入和Set有些区别,因为他需要提供key
@Module
public class GameModule {
@Provides
@IntoMap
@StringKey(value = "1")
public Player getPlayer1(){
return new Player("1");
}
@Provides
@IntoMap
@StringKey(value = "2")
public Player getPlayer2(){
return new Player("2");
}
}
@IntoSet 变成了@IntoMap,并且使用@StringKey注解提供了key值。
PS:这里的key值是String类型,所以能够注入到 Map<String,Player> 对象中。
dagger还提供了一些内置的key类型,包裹classKey,IntKey等,android辅助包中也提供了ActivityKey等。
@MapKey
StringKey的源码,StringKey的value类型为String,应该是指定了Key的数据类型为String。而StringKey又被@MapKey注解,是不是表明该注解是Map的Key的注解呢?不妨试一下,我们自定义一个AppleKey:
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@MapKey
public @interface AppleKey {
AppleBean value();
}
但是编译会报错哦!!
注释类型中声明的方法的返回类型,如果不满足指定的返回类型,那么编译时会报错:
- 基本数据类型
- String
- Class
- 枚举类型
- 注解类型
- 以上数据类型的数组
但是还可以指定Enum类型,或者特定的类的。
enum MyEnum {
ABC, DEF;
}
@MapKey
@interface MyEnumKey {
MyEnum value();
}
@MapKey
@interface MyNumberClassKey {
Class<? extends Number> value();
}
@Module
class MyModule {
@Provides @IntoMap
@MyEnumKey(MyEnum.ABC)
static String provideABCValue() {
return "value for ABC";
}
@Provides @IntoMap
@MyNumberClassKey(BigDecimal.class)
static String provideBigDecimalValue() {
return "value for BigDecimal";
}
}
进阶进阶!!!!
使用复合键值,这个厉害了,因为map的key又不能多个,如何复合键值?
@MapKey(unwrapValue = false)
@Retention(value=RUNTIME)
public @interface GameInfo {
String name();
float price();
}
@Module
public class GameModule {
@Provides
@IntoMap
@GameInfo(name = "game",price = 100f)
public String getGameInfo(){
return "gameinfo";
}
}
然后被注入对象请求注入
@Inject Map<GameInfo,String> mGameInfoStringMap;
看一下键类型,这个很关键,竟然是一个GameInfo类型的。
如果你这么做了,并且编译失败了,请不要惊讶,因为你还缺少一些依赖库:
compile 'com.google.auto.value:auto-value:1.5.1' provided 'javax.annotation:jsr250-api:1.0'
具体原因会在研习篇分析源码的时候分析。
@Binds
假设存在如下场景
public interface HomePresenter {
}
public class HomePresenterImpl implements HomePresenter{
public HomePresenterImpl(UserService userService){
}
}
非常常见,我们在使用的时候一般会使用接口类型,而不是使用实现类型。
@Inject HomePresenter mHomePresenter;
所以我们需要在Module中provides
@Provides
public HomePresenter getHomePresenter(UserService userService){
return new HomePresenterImpl(userService);
}
@Provides UserService getUserService(){
return new UserService();
}
实际上注入的是一个HomePresenterImpl没有问题。
但是,关于这类注入子类的问题,还有一种解决方案,就是使用@binds
@Module
public abstract class BindsDataModule {
@Binds
public abstract HomePresenter bindHomePresenter(HomePresenterImpl homePresenterImpl);
}
module类改为abstract类型的,使用@Binds注解一个返回为HomePresenter的方法,并且需要想要注入的实现类的类型参数。
然后需要在HomePresenterImple和UserService中使用@Inject来注解构造函数。
@Binds的使用就是如此简单,那么它到底有什么意义呢?相比原来的@Provides有什么优势呢?
仔细想想我们在最初介绍dagger的几个变种中,有一种情况是不提供provides方法,而是在构造函数上添加@Inject的方法。其实这种方法有利于书写,方便快捷的优势,但是当遇上如上这种情况,我们需要注入的是一个接口的时候,我们无法使用@Inject注解构造函数的方法来提供一个接口(我们可以把@Inject添加到实现类中,但是在注入时,dagger并不知道你想要注入的是哪个实现。)
所以@Binds就这样诞生了,可以想象的是,@Binds的作用其实就是告诉dagger,当请求注入一个接口时,我们使用某个实现类何其绑定,用实现类来进行注入。