Android基础知识:Dagger2入门

3,836 阅读19分钟

1、前言

Dagger2作为一个上手难度较高的框架,我也是看了许多相关的文章,经历了无数次的从入门到放弃。放弃的多了好像也有一点懂了,于是乎我也总结一下自己对Dagger2使用的相关知识的理解。

2、依赖注入

关于Dagger2首先要理解的就是依赖注入(DI)和控制反转(IOC),对这两个概念你如果已经有所了解,可以直接跳到下一节。

在理解依赖注入之前先了解依赖注入的目的,也是使用Dagger2框架的目的,知道了目的才能更好地理解过程。依赖注入的目的就是二个字:解耦

高内聚,低耦合,是面向对象编程提倡遵循的设计原则,而通过依赖注入的方式能实现控制反转,从而实现解耦的目的。

光这样说还是太理论了,不易理解,举个例子来帮助理解下,最近复联4大火,举个漫威英雄的例子。

首先想象一下,你是个自带柯南属性普通人,每次有外星人入侵或者是超能力变种人搞破坏,你都好巧不巧的能出现在现场,然而你并没有能力打败他们,你只有托尼史塔克的联系方式,所以你每次都联系他,由他来解决这些麻烦。然而托尼突然有一天告诉你他要带着小辣椒去度假,会有一段时间联系不上。你没办法出于求生本能,你得去寻找另一个超级英雄,你找到了美国队长,队长一口答应,说没问题,下次有事联系我,我来搞定。于是你获得了美国队长的联系方式。又过一段时间,队长也和你说他要和冬兵去度假,然而托尼还度假没回来。你没办法,又只能自己去找寡姐,获得了他的联系方式,下次遇到袭击就联系她。发现了没有,这里你每次都依赖与某一个超级英雄,一旦发生变故,你只能自己去找新的英雄获得更新他的联系方式。这样对某个英雄依赖非常严重,现在换一种方法,不具体依赖某个英雄,我直接去神盾局找尼克弗瑞得到他的联系方式,以后有事都联系他,至于是哪个超级英雄来解决又或者怎么去联系英雄,我都不用知道,交给尼克弗瑞去处理就行。

下面通过代码加深理解,先定义三个具体英雄类:

//抽象英雄类
public abstract class Hero {
    public abstract String call();
}
public class IronMan extends Hero {
    @Override
    public String call() {
        return "贾维斯:已收到您的求救消息,正在联系托尼";
    }
}
public class CaptainAmerica extends Hero {
    @Override
    public String call() {
        return "美国队长:我已收到您的求救消息,正在赶来的路上";
    }
}
public class BlackWidow extends Hero {
    @Override
    public String call() {
        return "黑寡妇:我已收到您的求救消息,正在赶来的路上";
    }
}

最后是自己类:

public class Self {
    private IronMan ironMan;
    public Self() {
        ironMan = new IronMan();
    }
    public void help() {
        String call = ironMan.call();
        Log.d("callMessage", call);
    }
}

调用:

   //首先要有一个我
   Self self = new Self();
   //遇到危险
   self.help();

执行日志:

D/callMessage: 贾维斯:已收到您的求救消息,正在联系托尼

托尼去度假了,于是我们只能联系美队,所以要修改Self类:

public class Self {
//    private IronMan ironMan;
    private CaptainAmerica captainAmerica;

    public Self() {
//        ironMan = new IronMan();
        captainAmerica = new CaptainAmerica();
    }

    public void help() {
//        String call = ironMan.call();
        String call = captainAmerica.call();
        Log.d("callMessage", call);
    }
}

执行日志:

D/callMessage: 美国队长:我已收到您的求救消息,正在赶来的路上

美队也去度假了,再次修改Self类:

public class Self {
//    private IronMan ironMan;
//    private CaptainAmerica captainAmerica;
    private BlackWidow blackWidowa;

    public Self() {
//        ironMan = new IronMan();
//        captainAmerica = new CaptainAmerica();
        blackWidowa = new BlackWidow();
    }

    public void help() {
//        String call = ironMan.call();
//        String call = captainAmerica.call();
        String call = blackWidowa.call();
        Log.d("callMessage", call);
    }
}

执行日志:

D/callMessage: 黑寡妇:我已收到您的求救消息,正在赶来的路上

看到这里发现每次变动都要修改Self类,这里Self一直依赖一个英雄类,英雄更换了要修改,英雄的构造函数变了也要修改Self类,这样耦合就非常严重。现在我们采用通过尼克弗瑞来联系英雄:

public class NickFury {
    private Hero hero;
    public Hero call() {
        hero = new CaptainAmerica();
        return hero;
    }
}

修改Self类:

public class Self {
    private NickFury nickFury;
    public Self() {
        nickFury = new NickFury();
    }
    public void help() {
        Hero hero = nickFury.call();
        String call = hero.call();
        Log.d("callMessage", call);
    }
}

执行日志:

 D/callMessage:美国队长:我已收到您的求救消息,正在赶来的路上  

这下Self类不依赖于具体某个英雄类,而是通过三方NickFury类来实现英雄对象的注入。一旦有所变动更换英雄,只需要修改NickFury类的方法即可。其实这里的NickFury类,类似于工厂模式。

还记的依赖注入的目的吗?解耦,这里通过第三方工厂类使具体英雄类与Self类不再耦合,原来是Self主动去new实例化一个英雄类,修改后变为被动通过调用第三方类方法注入一个英雄类,由主动到被动实现了控制反转,实现了解耦,达成了这个目的。

关于依赖注入的方法有以下几种:

  • 基于接口注入
  • 基于构造函数注入
  • 基于 set 方法注入
  • 基于注解注入

基于接口:

public interface InjectInterface {
    void injectHero(Hero hero);
}
public class MySelf implements InjectInterface {
    Hero hero;
    public void help() {
        String call = hero.call();
        Log.d("callMessage", call);
    }
    @Override
    public void injectHero(Hero hero) {
        this.hero = hero;
    }
}

定义一个接口,实现类实现接口方法注入。

基于构造函数:

public class MySelf  {
    private Hero hero;
    public MySelf(Hero hero) {
        this.hero = hero;
    }
    public void help() {
        String call = hero.call();
        Log.d("callMessage", call);
    }
}

在构造函数时传入。

基于set方法:

public class MySelf  {
    private Hero hero;
    public void setHero(Hero hero) {
        this.hero = hero;
    }
    public void help() {
        String call = hero.call();
        Log.d("callMessage", call);
    }
}

通过set方法注入对象。

基于注解注入:

Dagger2中就用到了注解。所以这里用Dagger2来实现一个了依赖注入的例子。再想象一个吃麻辣烫场景,麻辣烫里要加很多食材,比如牛肉、豆腐、香肠、鱼丸等而香肠又是由肠衣和肉馅组成,鱼丸是由鱼肉做成。所以代码一般是这样写:

public class TestActivity extends AppCompatActivity {
     Fish fish;
     FishBall fishBall;
     Doufu doufu;
     Potato potato;
     Meat meat;
     Casings casings;
     SeeYouTomrrow seeYouTomrrow;
     Sausage sausage;
     SpicyHotPot spicyHotPot;
     Beef beef;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        
        beef = new Beef();
        fish = new Fish();
        fishBall = new FishBall(fish);
        doufu = new Doufu();
        potato = new Potato();
        meat = new Meat();
        casings = new Casings();
        sausage = new Sausage(casings, meat);
        seeYouTomrrow = new SeeYouTomrrow();
        spicyHotPot = new SpicyHotPot(potato, doufu, sausage, seeYouTomrrow, fishBall,beef);

        spicyHotPot.eat();
    }
}

这里就是初始化了很多对象,其中有些对象中还引用了其他对象,像这样的初始化代码在平常开发中还是比较常见的,而使用了Dagger2就可以这样写:

public class TestActivity extends AppCompatActivity {
    @Inject
    SpicyHotPot spicyHotPot;

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

        DaggerTestComponent.builder().build().inject(this);

        spicyHotPot.eat();
    }
}

使用了Dagger2是不是简单了不少,只要一个注解加一行代码,就完成了多个对象的初始化,而且无论对象如何修改,这里的代码都无需变动,完成了解耦。

3、Dagger2注解使用

既然Dagger2是通过注解实现的依赖注入,那么学习使用Dagger2就是要学习Dagger2中的注解的使用。不过,在具体看Dagger2中的注解之前,先要在项目中引入Dagger2的依赖,按照GithubDagger2的文档引入:

dependencies {
  implementation 'com.google.dagger:dagger:2.22.1'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.22.1'
}

接下来来看Dagger2具体的注解。

3.1 @Inject和@Component注解

@Inject注解的作用主要有两个:

  • 一是标注在成员变量上,表示需要通过Dagger2为它提供依赖。
  • 二是标注在构造函数上,表示为这个类型的成员变量提供依赖。

所以我们要使用Dagger2初始化一个类的依赖首先要在这个类的构造函数上加上@Inject注解,然后在需要依赖的地方的对应变量上也加上@Inject注解。但是光加上@Inject注解完成所谓的依赖注入吗?答案是否定的,@Inject只是标注了依赖的需求方和依赖的提供方,但是它们俩之间还没有建立关系桥梁。而@Component就是干这个的,具体来看下面这个例子:

public class Cola {
    @Inject
    public Cola() {
    }

    public String returnName() {
        return "百事可乐";
    }
}

先定义一个可乐类,在他的构造函数上加上@Inject注解,表示提供该类的依赖。再在Activity中定义一个Cola类型变量同样加上@Inject注解,表示需要该类依赖:

public class InjectAndComponentActivity extends AppCompatActivity {

    private TextView mTextContent;
    @Inject
    Cola cola;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inject_and_component);
        mTextContent = findViewById(R.id.textContent);
    }
}

接着新建一个接口ColaComponent

@Component
public interface ColaComponent {
    void inject(InjectAndComponentActivity injectAndComponentActivity);
}

接口上标注了@Component注解,接着点击AndroidStudio上的Make Project编译项目,此时Dagger2会自动生成这个接口的实现类DaggerColaComponent,由这个实现类的方法来完成依赖注入。接着在Activity中通过实现调用他的inject方法完成注入。

public class InjectAndComponentActivity extends AppCompatActivity {

    private TextView mTextContent;
    @Inject
    Cola cola;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inject_and_component);
        mTextContent = findViewById(R.id.textContent);

        DaggerColaComponent.builder().build().inject(this);

        mTextContent.setText(cola.returnName());
    }
}

注入之后就可以调用该对象的方法,运行效果:

上面举的麻辣烫的例子的实现也是一样的,每个食材类的构造函数上加了@Inject注解,创建一个Component接口。贴上部分代码:

//肠衣
public class Casings {
    @Inject
    public Casings() {
    }
}
//肉
public class Meat {
    @Inject
    public Meat() {
    }
}
//香肠
public class Sausage {
    Casings casings;
    Meat meat;
    @Inject
    public Sausage(Casings casings, Meat meat) {
        this.casings = casings;
        this.meat = meat;
    }

}
//麻辣烫
public class SpicyHotPot {
    Potato potato;
    Doufu doufu;
    Sausage sausage;
    SeeYouTomorrow seeYouTomorrow;
    FishBall fishBall;
    Beef beef;
    @Inject
    public SpicyHotPot(Potato potato, Doufu doufu, Sausage sausage, SeeYouTomorrow seeYouTomorrow, FishBall fishBall, Beef beef) {
        this.potato = potato;
        this.doufu = doufu;
        this.sausage = sausage;
        this.seeYouTomorrow = seeYouTomorrow;
        this.fishBall = fishBall;
        this.beef = beef;
    }
    public void eat() {
        Log.d("Dagger2", "我开动了");
    }
}

3.2 @Module和@Provides注解

无论是3.1中Cola类还是之前的各种食材类都是我们自己定义的类,所以可以自己修改,想使用Dagger2只需要在类的构造函数上加上@Inject注解。但是实际开发中会遇到使用三方类库的情况,这些三方类库中的类代码我们无法修改,没法在其构造函数上加@Inject注解,那么是不是没法使用Dagger2了呢?答案还是否定的,Dagger2中的@Module@Provides注解就是用来处理只种情况。
看下面这个例子,这回不吃麻辣烫了,来吃炸鸡,于是定义了一个德克士类。

public class Dicos {
    String friedDrumstick;
    public Dicos() {
        friedDrumstick = "脆皮手枪腿";
    }
    public String returnDicos() {
        return "德克士:" + friedDrumstick;
    }
}

这回不添加任何注解,就是一个正常的Dicos对象类。接着同样新建一个DicosComponent接口:

@Component
public interface DicosComponent {
    void inject(ModuleAndProvidesActivity moduleAndProvidesActivity);
}

接着在Activity中定义Dicos类型变量:

public class ModuleAndProvidesActivity extends AppCompatActivity {
    private TextView mTextContent;
    @Inject
    Dicos dicos;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_module_and_provides);
        mTextContent = findViewById(R.id.textContent);
    }
}

至此为止都和之前是一样的,接下来新建一个DicosModule类,因为没法在三方类的构造函数上加@Inject注解,所以要通过Module类来提供依赖。

@Module
public class DicosModule {

    @Provides
    public Dicos getDicos() {
        return new Dicos();
    }

}

这里首先创建了一个DicosModule类,并在DicosModule类上加上@Module注解,接着在这个类中只写了一个getDicos()方法,调用Dicos的构造方法创建对象然后返回。通过在方法上加上@Provides注解,表示由这个方法为Dicos类型提供依赖。最后记得还要在DicosComponent接口注解上加上DicosModule。这样在Activity@Inject标记需要依赖时,才能找到。

@Component(modules = DicosModule.class)
public interface DicosComponent {
    void inject(ModuleAndProvidesActivity moduleAndProvidesActivity);
}

Make Project后在Activity中同样调用DaggerDiscosComponentinject方法注入依赖即可。

public class ModuleAndProvidesActivity extends AppCompatActivity {
    private TextView mTextContent;
    @Inject
    Dicos dicos;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_module_and_provides);
        mTextContent = findViewById(R.id.textContent);

        DaggerDicosComponent.builder().dicosModule(new DicosModule()).build().inject(this);
        mTextContent.setText(dicos.returnDicos());
    }
}

运行结果:

3.3 @Named和@Qualifier注解

接下来考虑一下这种情况,如果一个类有多个构造方法,或者有两个相同依赖时,它们都继承同一个父类或者实现同一个接口,那么怎么区分呢?这就要用到@Named或者@Qualifier注解了。这次定义一个小肥羊火锅类。

public class LittleSheep {
    String mutton = "没有肉";
    String vegetables = "没有菜";

    public LittleSheep(String mutton) {
        this.mutton = mutton;
    }

    public LittleSheep(String mutton, String vegetables) {
        this.mutton = mutton;
        this.vegetables = vegetables;
    }
    public String retrunCotent() {
        return "小肥羊火锅:" + mutton + " " + vegetables;
    }
}

这个类有两个构造函数,接着同样是创建ComponentModule类。

@Component(modules = LittleSheepModule.class)
public interface LittleSheepComponent {
    void inject(NamedActivity namedActivity);
}
@Module
public class LittleSheepModule {
    @Named("all")
    @Provides
    public LittleSheep provideLittleSheepAll() {
        return new LittleSheep("十盘羊肉", "一盘蔬菜");
    }

    @Named("mutton")
    @Provides
    public LittleSheep provideLittleSheepSingle() {
        return new LittleSheep("二十盘羊肉吃个够");
    }
}

LittleSheepComponent和之前的没什么区别,看到LittleSheepModule,这个Module有两个@Provides注解方法,分别对应两个不同传参的构造函数,但是这两个方法返回类型都是LittleSheep都提供同一类型的依赖,在需求依赖的时候Dagger2就分不清是哪个了,所以这里要用@Named注解来做个区分,这里分别设置两个不同的name,allmutton做区分。除此之外在Activity中,需求依赖的时候也要使用@Named做区分。

public class NamedActivity extends AppCompatActivity {
    private TextView mTextContent;
    @Named("all")
    @Inject
    LittleSheep allLittleSheep;
    @Named("mutton")
    @Inject
    LittleSheep littleSheep;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_named);
        mTextContent = findViewById(R.id.textContent);

        DaggerLittleSheepComponent.builder().build().inject(this);

        mTextContent.setText(allLittleSheep.retrunCotent() + "\n" + littleSheep.retrunCotent());
    }
}

NamedActivity中同样在@Inject注解上还要加上@Named区分。运行结果:

接下来看继承同一个父类的情况,首先定义一个快餐类FastFood,再定义他的两个子类KFCBurgerKing

public abstract class FastFood {
    public abstract String returnContent() ;
}
public class KFC extends FastFood {

    public KFC() {
    }

    @Override
    public String returnContent() {
        return "KFC全家桶";
    }
}
public class BurgerKing extends FastFood {

    String beefBurger = "三层牛肉汉堡";

    public BurgerKing() {
    }
    @Override
    public String returnContent() {
        return "汉堡王:" + beefBurger;
    }
}

接着还是创建ComponentModule类。

@Component(modules = FastFoodQualifierModule.class)
public interface FastFoodQualifierComponent {
   void inject(FastFoodQualifierActivity fastFoodQualifierActivity);
}
@Module
public class FastFoodQualifierModule {

    @Provides
    public FastFood getKFC() {
        return new KFC();
    }

    @Provides
    public FastFood getBurgerKing() {
        return new BurgerKing();
    }
}

主要的问题还是在Module中,这样写还是无法区分到底是KFC还是BurgerKing的依赖。这回不使用@Named使用@Qualifier处理。@Qualifier@Named更加灵活和强大,用于自定义注解。接下来我们定义两个注解。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface KFC {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BurgerKing {
}

一个KFC和一个BurgerKing注解,存活时长都选择RUNTIME,再添加上@Qualifier注解限定,接下来就可以用这两个自定义注解来区分Module中方法的类型。

@Module
public class FastFoodQualifierModule {

    @com.sy.dagger2demo.annotations.KFC
    @Provides
    public FastFood getKFC() {
        return new KFC();
    }
    @com.sy.dagger2demo.annotations.BurgerKing
    @Provides
    public FastFood getBurgerKing() {
        return new BurgerKing();
    }

}

FastFoodQualifierModule中两个方法上分别添加刚才的两个注解,接下来Activity中和使用@Named并没什么不同,只是把@Named注解分别换成刚才定义的注解。

public class FastFoodQualifierActivity extends AppCompatActivity {

    private TextView mTextContent;
    @KFC
    @Inject
    FastFood kfc;
    @BurgerKing
    @Inject
    FastFood burgerKing;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fast_food_qualifier);
        mTextContent = (TextView) findViewById(R.id.textContent);

        DaggerFastFoodQualifierComponent.builder().build().inject(this);

        mTextContent.setText(kfc.returnContent()+"\n"+burgerKing.returnContent());
    }
}

运行结果:

3.4 @Singleton和@Scope注解

@Singleton看名字就知道这个注解是用来实现单例的。再次定义一个NetworkClient类。采用@Module@Provides注解实现依赖注入。

public class NetworkClient {
    String baseUrl;
    public NetworkClient() {
        baseUrl = "http://www.baidu.com/";
    }
    public void init() {
        Log.d("NetworkClient", "网络初始化");
    }
}
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
   void inject(SingletonAndScopeActivity singletonAndScopeActivity);
}
@Module
public class NetworkModule {
    @Provides
    public NetworkClient getClient() {
        return new NetworkClient();
    }
}
public class SingletonAndScopeActivity extends AppCompatActivity {

    private TextView mContentTextView;
    @Inject
    NetworkClient networkClient1;
    @Inject
    NetworkClient networkClient2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singleton_and_scope);
        mContentTextView = (TextView) findViewById(R.id.contentTextView);

        DaggerNetworkComponent.create().inject(this);

        mContentTextView.setText(networkClient1.toString() + "\n" + networkClient2.toString());

    }

}

这里的代码和之前的完全相同,只是在Activity中添加了一个对象,同时有两个NetworkClient对象,调用hashCode方法查看是否为同一个对象。 运行结果:

可以看到这里hashCode不同,明显是两个对象。接下来使用@Singleton实现单例。首先在NetworkComponent上加上@Singleton注解:

@Singleton
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
   void inject(SingletonAndScopeActivity singletonAndScopeActivity);
}

接着在NetworkModule中的方法上也加上@Singleton注解:

@Module
public class NetworkModule {
    @Singleton
    @Provides
    public NetworkClient getClient() {
        return new NetworkClient();
    }
}

这样就ok了,就是这么简单,再次运行程序查看hashCode。这时hashCode相同已经是同一个对象了。
运行结果:

注意这里其实@Singleton实现的单例只是在同个Activity下的单例,在其他Activity下,再次创建这了类的对象就不再是同一个对象了。这里我们在新建一个Activity测试下:

public class SecondActivity extends AppCompatActivity {

    private TextView mContentTextView;
    @Inject
    NetworkClient networkClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mContentTextView = (TextView) findViewById(R.id.contentTextView);

        DaggerNetworkComponent.create().inject(this);

        mContentTextView.setText(networkClient.toString());

    }

}

Component中添加inject方法类型为SecondActivity

@Singleton
@Component(modules = NetworkModule.class)
public interface NetworkComponent {
   void inject(SingletonAndScopeActivity singletonAndScopeActivity);
   void inject(SecondActivity secondActivity);
}

运行结果:

可以看到虽然使用了@Singleton注解,但是也只能保证在同一个Activity中是单例同一个对象,在多个Activity中就无法保证了。那么怎么实现全局的单例呢?这可以用@Scope注解。

@Scope同样用来自定义注解用来限定注解,因为我们知道Application是单例的,所以可以使用@Scope结合Application实现全局的单例模式。先定义一个新注解ApplicationScope

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

这个和上面类似,只是用了@Scope注解,接着修改之前的NetworkModule,将@Singleton换成新注解@ApplicationScope

@Module
public class NetworkModule {
    @ApplicationScope
    @Provides
    public NetworkClient getClient() {
        return new NetworkClient();
    }
}

接着创建一个新的ActivityComponent

@ApplicationScope
@Component(modules = NetworkModule.class)
public interface ActivityComponent {
   void inject(SingletonAndScopeActivity singletonAndScopeActivity);
   void inject(SecondActivity secondActivity);
}

这里同样是使用了@ApplicationScope注解,接着创建MyApplicatin类在其中去获取ActivityComponent的实例。

public class MyApplication extends Application {

    ActivityComponent activityComponent;
    @Override
    public void onCreate() {
        super.onCreate();
        activityComponent = DaggerActivityComponent.builder().build();
    }

    public static MyApplication getApplication(Context context){
        return (MyApplication) context.getApplicationContext();
    }

   public ActivityComponent getActivityComponent(){
        return activityComponent;
    }
}

这里看到在MyApplication中通过Dagger2获取到ActivityComponet的实例再给出public方法获取这个ActivityComponent实例。这样在Activity中就可以通过这个ActivityComponent注入,获得单例的对象了。修改Activity中代码:

public class SingletonAndScopeActivity extends AppCompatActivity {
    private TextView mContentTextView;

    @Inject
    NetworkClient networkClient1;
    @Inject
    NetworkClient networkClient2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singleton_and_scope);
        mContentTextView = (TextView) findViewById(R.id.contentTextView);
//        DaggerNetworkComponent.create().inject(this);
        //通过Application中的Component注入
        MyApplication.getApplication(this).getActivityComponent().inject(this);
        mContentTextView.setText(networkClient1.hashCode() + "\n" + networkClient2.hashCode());

        mContentTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SingletonAndScopeActivity.this,SecondActivity.class));
            }
        });

    }

}

public class SecondActivity extends AppCompatActivity {

    private TextView mContentTextView;

    @Inject
    NetworkClient networkClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mContentTextView = (TextView) findViewById(R.id.contentTextView);

//        DaggerNetworkComponent.create().inject(this); 
        //通过Application中的Component注入
        MyApplication.getApplication(this).getActivityComponent().inject(this);

        mContentTextView.setText(networkClient.hashCode()+"");

    }
}

运行结果:

再次运行查看结果,发现这是已经全是同一个对象了。

3.5 @Component的dependencies

Component还可以通过dependencies依赖于别的Component。这里再重新定义一个PizzaHut类:

public class PizzaHut {
    String SuperSupremePizza = "超级至尊披萨";

    public PizzaHut() {
    }

    public String returnContent() {
        return "必胜客超级至尊套餐:" + SuperSupremePizza;
    }
}
@Module
public class PizzaHutModule {
    @Provides
    public PizzaHut getPizzaHut() {
        return new PizzaHut();
    }
}
@Component(modules = PizzaHutModule.class)
public interface PizzaHutComponent {
   void inject(SingletonAndScopeActivity singletonAndScopeActivity);
   void inject(SecondActivity secondActivity);
}

同时还创建了对应的ModuleComponet,还是和之前没什么区别。接下来在ActivityComponent中使用dependenciesPizzaHutComponent引入。

@ApplicationScope
@Component(modules = NetworkModule.class,dependencies = PizzaHutComponent.class)
public interface ActivityComponent {
   void inject(SingletonAndScopeActivity singletonAndScopeActivity);
   void inject(SecondActivity secondActivity);
}

接着到MyApplication中引入PizzaHutComponent

public class MyApplication extends Application {

    ActivityComponent activityComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        activityComponent = DaggerActivityComponent.builder().pizzaHutComponent(DaggerPizzaHutComponent.builder().build()).build();
    }

    public static MyApplication getApplication(Context context) {
        return (MyApplication) context.getApplicationContext();
    }

    public ActivityComponent getActivityComponent() {
        return activityComponent;
    }
}

最后再到刚才的Activity中添加一个PizzaHut类型变量:

public class SingletonAndScopeActivity extends AppCompatActivity {

    private TextView mContentTextView;

    @Inject
    NetworkClient networkClient1;
    @Inject
    NetworkClient networkClient2;
    @Inject
    PizzaHut  pizzaHut;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singleton_and_scope);
        mContentTextView = (TextView) findViewById(R.id.contentTextView);

//        DaggerNetworkComponent.create().inject(this);
        MyApplication.getApplication(this).getActivityComponent().inject(this);
        mContentTextView.setText(networkClient1.hashCode() + "\n" + networkClient2.hashCode()+"\n"+pizzaHut.returnContent());

        mContentTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SingletonAndScopeActivity.this,SecondActivity.class));
            }
        });

    }

}

运行结果:

可以看到这里已经成功注入PizzaHut,并且调用了returnContent方法。

3.6 懒加载

Dagger2也支持懒加载模式,就是@Inject的时候不初始化,而到使用的时候调用get方法获取实例。

public class SingletonAndScopeActivity extends AppCompatActivity {

    private TextView mContentTextView;

    @Inject
    NetworkClient networkClient1;
    @Inject
    NetworkClient networkClient2;
    @Inject
    Lazy<PizzaHut> pizzaHut;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singleton_and_scope);
        mContentTextView = (TextView) findViewById(R.id.contentTextView);

//        DaggerNetworkComponent.create().inject(this);
        MyApplication.getApplication(this).getActivityComponent().inject(this);
       //使用的时候调用get方法获取处理
        mContentTextView.setText(networkClient1.hashCode() + "\n" + networkClient2.hashCode()+"\n"+pizzaHut.get().returnContent());

        mContentTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(SingletonAndScopeActivity.this,SecondActivity.class));
            }
        });
    }
}

4、Dagger2与MVP

在对Dagger2中的注解有了一定的了解之后,继续学习Dagger2+MVP。将Dagger2MVP结构结合起来,可以使MVP架构中的依赖更加清晰更加易于管理。学习MVP+Dagger2自然是去看Google官方提供的Demo

先看一下工程目录:

看到具体模块包下除了基础MVPPresenterContractFragment等类之外,还有ComponentModule这些类都是使用了Dagger2会用到的。接着先来具体看ToDoApplication类:

public class ToDoApplication extends Application {
    private TasksRepositoryComponent mRepositoryComponent;
    @Override
    public void onCreate() {
        super.onCreate();
        mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
                .applicationModule(new ApplicationModule((getApplicationContext())))
                .tasksRepositoryModule(new TasksRepositoryModule()).build();
    }
    public TasksRepositoryComponent getTasksRepositoryComponent() {
        return mRepositoryComponent;
    }
}

看到这里就是构建了一个TasksRepositoryComponent,并提供了一个获得的方法。先找到TaskRepositoryComponent

@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
    TasksRepository getTasksRepository();
}

首先看到这里用了单例的注解,接着看到有两个Module,还提供了一个获取TaskRepository的方法,这个TaskRepository是用来获取数据的,在Presenter构造中传入,Presenter调用其中方法获得数据。先来看TaskRepositoryModule

@Module
public class TasksRepositoryModule {

    @Singleton
    @Provides
    @Local
    TasksDataSource provideTasksLocalDataSource(Context context) {
        return new TasksLocalDataSource(context);
    }

    @Singleton
    @Provides
    @Remote
    TasksDataSource provideTasksRemoteDataSource() {
        return new FakeTasksRemoteDataSource();
    }
}

看到这个Module中有两个@Provides标注的方法,是用来提供测试数据返回的。一个方法是本地数据,一个是模拟远程数据。返回的都是TasksDataSource类型,所以用了自定义注解@Local@Remote做了区分。点进去看这两个注解,其定义时都用了@Qualifier注解。

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Local {
}

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {
}

接着返回看ApplicationModule

@Module
public final class ApplicationModule {

    private final Context mContext;

    ApplicationModule(Context context) {
        mContext = context;
    }

    @Provides
    Context provideContext() {
        return mContext;
    }
}

看到其中只提供了一个上下文的mContext方法。回到ToDoApplication中看到这里创建ApplicationModule传入的是ApplicationContext。下面进入tasks这个具体模块页面查看Dagger2具体是怎么和MVP结合的。

public class TasksActivity extends AppCompatActivity {

    private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";

    private DrawerLayout mDrawerLayout;

    @Inject TasksPresenter mTasksPresenter;

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

        ......
        
        TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

        // Create the presenter
        DaggerTasksComponent.builder()
                .tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
                .tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
                .inject(this);

        ......
    }
    ......
}

这段是TasksActivity中的代码,其中省略掉了一些无关的代码。可以看到这和Google官方MVPDemo一样,是在Activity里放了一个FragmentFragment作为View使用。看到这里在TasksPresenter上加了@Inject注解,也就是说这里是要用Dagger2初始化Presenter。在onCreate方法中通过DaggerTasksComponentinject方法注入TasksPresenter依赖,创建TasksPresenter。接下来来看TasksComponent接口的代码:

@FragmentScoped
@Component(dependencies = TasksRepositoryComponent.class, modules = TasksPresenterModule.class)
public interface TasksComponent {
    void inject(TasksActivity activity);
}

其中除了设置了TasksPresenterModule而且还依赖了TasksRepositoryComponent这个Component。这就让之前的TasksRepository在这里也可以使用。接着进入TasksPresenterModule查看:

@Module
public class TasksPresenterModule {

    private final TasksContract.View mView;

    public TasksPresenterModule(TasksContract.View view) {
        mView = view;
    }

    @Provides
    TasksContract.View provideTasksContractView() {
        return mView;
    }

}

TasksPresenterModule中标注了mView@Provides方法,为注入View提供了方法。最后看到TasksPresenter

final class TasksPresenter implements TasksContract.Presenter {

    private final TasksRepository mTasksRepository;
    private final TasksContract.View mTasksView;
    private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS;
    private boolean mFirstLoad = true;

    @Inject
    TasksPresenter(TasksRepository tasksRepository, TasksContract.View tasksView) {
        mTasksRepository = tasksRepository;
        mTasksView = tasksView;
    }

    @Inject
    void setupListeners() {
        mTasksView.setPresenter(this);
    }
    
    ......
    
      }
    }

TasksPresenter的构造方法上加上@Inject注解提供了依赖。至此所有对象具能由Dagger2提供依赖,在TaskActivity注入依赖,完成了Dagger2MVP的结合,完成了解耦。

  DaggerTasksComponent.builder()
                .tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
                .tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
                .inject(this);

5、总结

1. 使用Dagger2的目的是解耦。使用Dagger2更好更清晰地管理项目中类之间的依赖。
2. 关于Dagger2的使用我的理解是:

  • 在大型项目中,Dagger2无疑是解耦利器,特别是在项目由多人合作开发时,无需关注个各类的依赖和构造初始化等实现和变动,全部交给Dagger2处理。
  • 在小型项目中,考虑到类之间的相互依赖关系简单,并且多为单人开发,开发周期较短等因素,可以不引入Dagger2。
  • 如果项目的业务逻辑复杂,各个类之间的相互依赖复杂,初始化构造复杂,还是应该使用Dagger2。虽然有可能开始不熟练,但是多用用就会了。