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
的依賴,按照Github
上Dagger2的文件引入:
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
中同樣呼叫DaggerDiscosComponent
的inject
方法注入依賴即可。
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;
}
}
複製程式碼
這個類有兩個建構函式,接著同樣是建立Component
和Module
類。
@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
,all
和mutton
做區分。除此之外在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
,再定義他的兩個子類KFC
和BurgerKing
。
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;
}
}
複製程式碼
接著還是建立Component
和Module
類。
@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);
}
複製程式碼
同時還建立了對應的Module
和Componet
,還是和之前沒什麼區別。接下來在ActivityComponent
中使用dependencies
將PizzaHutComponent
引入。
@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
。將Dagger2
與MVP
結構結合起來,可以使MVP
架構中的依賴更加清晰更加易於管理。學習MVP+Dagger2
自然是去看Google
官方提供的Demo
。
先看一下工程目錄:
看到具體模組包下除了基礎MVP
的Presenter
、Contract
、Fragment
等類之外,還有Component
和Module
這些類都是使用了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
官方MVP
的Demo
一樣,是在Activity
裡放了一個Fragment
將Fragment
作為View
使用。看到這裡在TasksPresenter
上加了@Inject
註解,也就是說這裡是要用Dagger2
初始化Presenter
。在onCreate
方法中通過DaggerTasksComponent
的inject
方法注入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
注入依賴,完成了Dagger2
與MVP
的結合,完成了解耦。
DaggerTasksComponent.builder()
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
.inject(this);
複製程式碼
5、總結
1. 使用Dagger2的目的是解耦。使用Dagger2更好更清晰地管理專案中類之間的依賴。
2. 關於Dagger2的使用我的理解是:
- 在大型專案中,Dagger2無疑是解耦利器,特別是在專案由多人合作開發時,無需關注個各類的依賴和構造初始化等實現和變動,全部交給Dagger2處理。
- 在小型專案中,考慮到類之間的相互依賴關係簡單,並且多為單人開發,開發週期較短等因素,可以不引入Dagger2。
- 如果專案的業務邏輯複雜,各個類之間的相互依賴複雜,初始化構造複雜,還是應該使用Dagger2。雖然有可能開始不熟練,但是多用用就會了。