Dagger2进阶篇(二)

452 阅读7分钟

上一篇文章讲解了无参情况两种注入方式和有参情况的两种注入方式。以及两种注入方式的优先级。这一篇我们会更加深入的去学习更多关于Dagger2的用法。

上一篇我们被注入的对象构造方法都是一个,但是如果构造方法进行了重载,我们怎么用这两种方式进行注入呢?

如果两个构造方法同时需要注入,那么第一种注入方式解决不了这个问题,因为Dagger2不允许同时是Inject注解两个构造方法,而且自定义的注解不能注解构造方法,编译会不通过。

这时候我们就需要第二种方式去注入,代码如下:

public class HotCar {

    public String name;


    public HotCar() {

    }

    public HotCar(String name) {
        this.name = name;
    }


    public void logName() {
        Log.e("rrrrrrrrr", "参数构造方法" + name);
    }
}

对应的module如下

@Module
public class A03Module {

    @Provides
    public String provideName() {
        return "eee";
    }

    @Provides
    @noParm
    public HotCar provideNullParm() {
        return new HotCar();
    }

    @Provides
    @hasParm
    public HotCar providehasParm(String name) {

        return new HotCar(name);
    }

}

这里需要说明 @noParm和 @hasParm注解都是我们自定义的注解,用来标记区分不同的构造方法创建的对象。

那么我们使用的代码如下:

public class ThereActivity extends AppCompatActivity {

    @Inject
    @hasParm
    HotCar hotCar1;

    @Inject
    @noParm
    HotCar hotCar2;

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

        DaggerA03Component.builder().build().inject(this);
        hotCar1.logName();

        hotCar2.logName();
    }
}

还是通过这两个注解去区分不同的对象。完成注入:

这个自定义注解写法也非常简单,代码如下

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface noParm {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface hasParm {
}

同时Dagger2还给我们提供了另外一种写法,就是使用@Named(“flag”)来代替自定义注解去区分不同的对象。点进去@Named的源码可以看到它是Qualifier的默认实现。当然在实际开发的时候使用自定义注解去实现还是显得很酷的。

二Dagger2怎么实现一个单例呢?

我们在开发时候很多时候用到单例模式,那么对于Dagger2怎么去创建一个单例的依赖注入对象呢?这里的单例模式还是需要通过自定义注解完成,所以这里还是只能使用第二种方式进行注入。这里需求是让Student实现单例模式,代码如下:

Scope 顾名思义是作用域,用于标注一个对象的作用域。Scope也是一个元注解,首先用Scope 来定义一个注解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentScope {
}

将这个定义好的注解 StudentScope 标注在 Component 以及 Module 提供对象的方法上。

@StudentScope
@Component(modules = A04Module.class)
public interface A04Component {
    void inject(FourActivity activity);
}
@Module
public class A04Module {
    @StudentScope
    @Provides
    public Student provideStudent() {
        return new Student();
    }
}

然后就是使用代码

public class FourActivity extends AppCompatActivity {

    @Inject
    Student student1;
    @Inject
    Student student2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_four);
        DaggerA04Component.builder().build().inject(this);
        Log.e("rrrrrrrrr",student1.toString());
        Log.e("rrrrrrrrr",student2.toString());


    }
}

通过打印代码我们可以确定这两个Student对象是同一个对象。那么我们去看一下源码是怎么实现的(因为篇幅我们只看主要代码)

@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {

  this.provideStudentProvider =
      DoubleCheck.provider(A04Module_ProvideStudentFactory.create(builder.a04Module));

  this.fourActivityMembersInjector = FourActivity_MembersInjector.create(provideStudentProvider);
}

这里和之前不同的地方就是调用了 DoubleCheck.provider 方法对provider进行了包装,那么 DoubleCheck 做了什么工作呢,继续看DoubleCheck的源码:

public T get() {   // 实现单例
    Object result = this.instance;
    if(result == UNINITIALIZED) {
        synchronized(this) {
            result = this.instance;
            if(result == UNINITIALIZED) {
                result = this.provider.get();
                this.instance = reentrantCheck(this.instance, result);
                this.provider = null;
            }
        }
    }

    return result;
}

看到这里,我们找到了单例的实现代码,也直到了这里实现了单例模式的逻辑。但是这里需要强调两点:

  • 这里的单例只是对于同一个 Component对象来说的,在同一个 Component对象的生命周期里,持有的依赖对象为同一个。不同的Component对象中的自然就是不同的了。
  • Scope的注意事项:Module 中 provide 方法的 Scope 注解和 与之绑定的 Component 的 Scope 需要保持一致,否则作用域不同会导致编译时会报错。

到了这里你一定有很多疑问:

每个Activity都有对应的 Component类,每个Component对象都持有相应的Module类,如果某个Activity希望和另外一个Activity共享依赖实例,应该如何做呢?----这也就是很多文章说的局部单例效果。

还有我们在开发中常用到全局的单例模式,例如联网的,Sp存储的,那么全局单例模式又该怎么写呢?

接下来我们学习Component的组织关系,用于解决上边局部单例和全局单例的问题。Component拥有两种关系依赖关系继承关系。

依赖关系:一个Component可以依赖于一个或多个Component,依赖关系通过Component中的dependencies属性来实现,具体用法通过一个例子来看一下;

创建三个Bean类:People类,Teacher类,Money类,FiveActivity中有的People和Money注入,TeacherActivity类中有Teacher和Money注入。他们共同有Money注入。所以我们对应的写三个Component,再写三个module,他们之间的组织关系如下:

Bean类:

public class People {
}
public class Teacher {
}
public class Money {
}

Model类:

@Module
public class A05Module {
    @Provides
    public People providePeople() {
        return new People();
    }
}
@Module
public class A06module {
    @Provides
    public Teacher provideTeacher() {
        return new Teacher();
    }
}
@Module
public class ShareModule {
    @Provides
    @MoneyScope
    public Money provideMoney() {
        return new Money();
    }
}

这里我们自定义了注解去标记这个money提供的对象,因为我们的目标是使用同一个对象,同时我们还有三个Component类代码如下:

@PeopleScope
@Component(modules = A05Module.class, dependencies = ShareComponent.class)
public interface A05Component {
    void inject(FiveActivity activity);
}
@TeacherScpoe
@Component(modules = A06module.class, dependencies = ShareComponent.class)
public interface A06TeacherComponent {
    void inject(TeacherActivity activity);
}
@MoneyScope
@Component(modules = ShareModule.class)
public interface ShareComponent {
    Money getMoney();
}
这里每个Component上边都有一个自定义注解:ShareComponent上边的注解是因为,要和对应绑定的module中作用域一样。

而对应的其他Component上的注解是因为与有作用域的Component依赖关系,所以要定义自己的作用域,否则编译报错。

然后就是我们使用的代码:

public class FiveActivity extends AppCompatActivity {
    @Inject
    People people;
    @Inject
    Money money;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);

        ShareComponent share = DaggerShareComponent.builder().build();
        DaggerA05Component.builder().shareComponent(share).build().inject(this);

        Log.e("rrrrrrrrrrrrr", people.toString());
        Log.e("rrrrrrrrrrrrr", money.toString());


    }
}
public class TeacherActivity extends AppCompatActivity {
    @Inject
    Teacher teacher;
    @Inject
    Money money;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);
        ShareComponent share = DaggerShareComponent.builder().build();
        DaggerA06TeacherComponent.builder().shareComponent(share).build().inject(this);
        Log.e("rrrrrrrrrrrrr", teacher.toString());
        Log.e("rrrrrrrrrrrrr", money.toString());
    }
}

这里运行打印两个页面的Money的地址:

com.advertising.administrator.dagger_simple.FiveActivity.Money@22c348
com.advertising.administrator.dagger_simple.FiveActivity.Money@ad51d45

发现并没有实现这两个页面的单例模式,原因在于我们每个页面都创建了一个ShareComponent对象,所以导致单例模式失效。

那么只要解决了创建同一个ShareComponent对象,就能保证单例模式的实现。

这里我们就可以在Application下去创建这个ShareComponent,然后在需要的地方注入依赖通一个ShareComponent就完成了全局的单例,如果其他不同的页面不用同一个ShareComponent,就完成了局部单例模式。

依赖模式除了可以写全局单例和局部单例,我们还可以通过依赖模式让任意的Component之间有依赖关系,减少注入代码的书写。

Component除了有依赖关系还有继承关系的写法。并且通过继承关系,继承关系也可以完成单例模式(这里单例模式写起来不那么优雅,实际开发中不常用到,所以不做书写)

这里我们先不去用继承完成这个单例,因为它的写法有点复杂。我们先学习继承的基本写法。然后再写局部单例模式。

我们去创建两个Bean分别是Melon,Noodle,如下:

public class Noodle {
}
public class Melon {
}

创建两个module如下:

@Module
public class FoodModule {
    @Provides
    public Noodle provideNoodle() {
        return new Noodle();
    }
}
@Module
public class SnackModule {
    @Provides
    public Melon provideMelon() {
        return new Melon();
    }
}

然后就是Component类:

@Component(modules = FoodModule.class)
public interface FoodComponent {
    SnackComponent getSnack();
}
@Subcomponent(modules = SnackModule.class)
public interface SnackComponent {
    void inject(SixActivity activity);
}

这里有两点需要注意:

  • SnackComponent 用这个进行注解,并且抽象方法是注入的方法。
  • FoodComponent 的抽象方法的返回值是另外一个Component。

对应的使用方法如下:

DaggerFoodComponent.builder().build().getSnack().inject(this);

上边就是继承的最简单基本的用法。

通过上边的讲解,我们解决了依赖注入迷失问题。同时学会了局部单例和全局单例的写法。而且学会了Component之间的组织关系:继承和依赖。

下篇文章介绍一下Dagger2依赖注入框架怎么和mvp框架进行配合,融入,进而提高开发效率。

代码下载地址:github.com/XiFanYin/Da…