Dagger2 使用(二)

@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,当请求注入一个接口时,我们使用某个实现类何其绑定,用实现类来进行注入。

转载于:https://my.oschina.net/zzxzzg/blog/1541916

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值