Android基礎知識:Dagger2入門

一、前言

Dagger2做爲一個上手難度較高的框架,我也是看了許多相關的文章,經歷了無數次的從入門到放棄。放棄的多了好像也有一點懂了,因而乎我也總結一下本身對Dagger2使用的相關知識的理解。git

二、依賴注入

關於Dagger2首先要理解的就是依賴注入(DI)和控制反轉(IOC),對這兩個概念你若是已經有所瞭解,能夠直接跳到下一節。github

在理解依賴注入以前先了解依賴注入的目的,也是使用Dagger2框架的目的,知道了目的才能更好地理解過程。依賴注入的目的就是二個字:解耦編程

高內聚,低耦合,是面向對象編程提倡遵循的設計原則,而經過依賴注入的方式能實現控制反轉,從而實現解耦的目的。bash

光這樣說仍是太理論了,不易理解,舉個例子來幫助理解下,最近復聯4大火,舉個漫威英雄的例子。網絡

首先想象一下,你是個自帶柯南屬性普通人,每次有外星人入侵或者是超能力變種人搞破壞,你都好巧不巧的能出如今現場,然而你並無能力戰勝他們,你只有託尼史塔克的聯繫方式,因此你每次都聯繫他,由他來解決這些麻煩。然而託尼忽然有一天告訴你他要帶着小辣椒去度假,會有一段時間聯繫不上。你沒辦法出於求生本能,你得去尋找另外一個超級英雄,你找到了美國隊長,隊長一口答應,說沒問題,下次有事聯繫我,我來搞定。因而你得到了美國隊長的聯繫方式。又過一段時間,隊長也和你說他要和冬兵去度假,然而託尼還度假沒回來。你沒辦法,又只能本身去找寡姐,得到了他的聯繫方式,下次遇到襲擊就聯繫她。發現了沒有,這裏你每次都依賴與某一個超級英雄,一旦發生變故,你只能本身去找新的英雄得到更新他的聯繫方式。這樣對某個英雄依賴很是嚴重,如今換一種方法,不具體依賴某個英雄,我直接去神盾局找尼克弗瑞獲得他的聯繫方式,之後有事都聯繫他,至因而哪一個超級英雄來解決又或者怎麼去聯繫英雄,我都不用知道,交給尼克弗瑞去處理就行。架構

下面經過代碼加深理解,先定義三個具體英雄類:app

//抽象英雄類
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);
    }
}
複製代碼

調用:ide

//首先要有一個我
   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是否是簡單了很多,只要一個註解加一行代碼,就完成了多個對象的初始化,並且不管對象如何修改,這裏的代碼都無需變更,完成了解耦。

三、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));
            }
        });
    }
}
複製代碼

四、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);
複製代碼

五、總結

1. 使用Dagger2的目的是解耦。使用Dagger2更好更清晰地管理項目中類之間的依賴。
2. 關於Dagger2的使用個人理解是:

  • 在大型項目中,Dagger2無疑是解耦利器,特別是在項目由多人合做開發時,無需關注個各種的依賴和構造初始化等實現和變更,所有交給Dagger2處理。
  • 在小型項目中,考慮到類之間的相互依賴關係簡單,而且多爲單人開發,開發週期較短等因素,能夠不引入Dagger2。
  • 若是項目的業務邏輯複雜,各個類之間的相互依賴複雜,初始化構造複雜,仍是應該使用Dagger2。雖然有可能開始不熟練,可是多用用就會了。
相關文章
相關標籤/搜索