參考資料:
大力感謝如下文章的作者:
Dagger2 最清晰的使用教程:https://blog.csdn.net/heng615975867/article/details/80355090
前言
由于之前一直主要從事SDK研發(fā),屬于重邏輯輕UI的設計。
正好去年用Kotlin研發(fā)了個應用,接上峰要求 使用MVVP框架,配合Dagger2實現(xiàn),并使用了很多比較新的技術。
硬著頭皮,依葫蘆畫瓢,項目是完成了,但是很多技術沒有深入了解。
這如鯁在喉,一直想弄明白。
本著 "程序員不能讓自己出現(xiàn)明顯的技術短板"(不知道哪位大神說的),現(xiàn)在正好有時間,先從Dagger2下手。
為啥寫這篇文檔:
- 網(wǎng)上有挺多資料都是用Kotlin來闡述的,所以換個Java語言,以示區(qū)分。
- 看別人的例子,屬于一看就會,一寫就廢。很少有循序漸進的演示例子。因此本文提供完整的Demo演示。
- 同時我會說一下創(chuàng)建工程依賴版本的注意點。
- Demo里面增加了很多注釋,應該能幫助到有需要的人。
- 里面的有些注釋或者內(nèi)容,對于某些人而言,可能比較啰嗦,抱歉了~
小建議:最佳學習方式就是自己寫Demo,哪怕是把參考的照著敲一遍也行。
- 這樣會有一個循序漸進的過程。
- 能深入理解某行代碼添加所帶來的改變。
- 對Dagger2 的注解而言,你就會發(fā)現(xiàn)我上面的說的 "一看就會,一寫就廢"。因為很容易就編譯報錯。
本文檔對應的Demo地址,可以 下載 提取碼:pjyk
對Dagger2 的自我理解:
不使用Dagger2 時,我們一般如何創(chuàng)建提供對象:
- 創(chuàng)建一個靜態(tài)對象,類似: XXXHelper.getInstance.getData()。這是全局靜態(tài)的對象。
- 還可以通過實現(xiàn)MyApplication類來創(chuàng)建對象,通過類似 (MyApplication) getApplication() 來獲取MyApplication對象,這樣就可以提供全局單例了。
- 類似FragmentActivity 需要提供對象多個Fragment時,我們還可以通過參數(shù)傳遞的方式,把FragmentActivity的某個數(shù)據(jù)對象傳遞給 Fragment,這樣多個Fragment可以使用相同的FragmentActivity的數(shù)據(jù)。這是局部單例。
- 當然上面所說的對象,不要局限于類似POJO類或者JavaBean之類的,也會是某些復雜的邏輯類。比如NetManager,DBManager等。
Dagger2 的主要作用:
就是為了簡化上面的提供獲取對象的方式
說的再高大上一些,就是使用MVP模式,解耦合,因為上面創(chuàng)建對象的方式,調(diào)用的地方和實現(xiàn)的地方基本是完全耦合的,一但數(shù)據(jù)對象發(fā)生改變,調(diào)用的地方也要調(diào)整。
學習Dagger2 都是為了DaggerAndroid擴展庫打基礎的,如果要單獨使用Dagger2來進行Android研發(fā),請放棄這個念頭,會寫很多重復的代碼,而且任何的修改,一旦引發(fā)編譯錯誤,查找和定位問題,會讓你有砸電腦的沖動?。?!
DaggerAndroid 擴展庫,看完Dagger2 的基礎知識后,可以繼續(xù)看這篇文章,我詳細介紹了?DaggerAndroid擴展庫的使用方式。
進入Dagger2的世界:
1. 創(chuàng)建工程,引入Dagger依賴。
由于我的Demo里面會有很多Module,因此把Dagger2 的依賴提取到了外面統(tǒng)一調(diào)用。
- 在項目的build.gradle同級目錄,創(chuàng)建config.gradle。目前能查到的最新版本為 2.32.2。如果還有更新的,請自行替換。如果要查找com.google.dagger下dagger最新版本。http://central.maven.org/maven2/com/google/dagger 就能找到最新版本。
不要添加Dagger_Android擴展庫,我下一篇會單獨講該擴展庫的用法
//使用Dagger2,具體的版本在http://central.maven.org/maven2上查找
//目前采用最新的2.23.2版本,如果有最新版本,請自行替換
version = [
dagger : "2.23.2"
]
//這幾個dagger的版本,最好保持一致。
dependencies = [
"dagger" : "com.google.dagger:dagger:${version.dagger}",
"dagger_compiler" : "com.google.dagger:dagger-compiler:${version.dagger}",
]
- 在項目的 build.gradle 中添加如下代碼。
apply from: "config.gradle"
- 在Module下的build.gradle 中添加依賴
implementation rootProject.ext.dependencies.dagger
annotationProcessor rootProject.ext.dependencies.dagger_compiler
- 當然,也可以不需要config.gradle文件,直接在Module下引入完整的Dagger依賴。
- 如果AndroidStudio無法同步工程,或者依賴下載超時后者失敗啥的??梢允褂妹钚衼硐螺d依賴,在同步一下就可以了。
./gradlew clean build
2. 學習@Inject 和@Component
Demo例子來自 DaggerTest 工程的 dagger1。這是Phone Module,可單獨運行。
1. @Inject 說明:
1. 標記在對象類中,標記在某個類的構(gòu)造函數(shù)上,說明如下(下面的接口在不同的類中):
/**
* @Inject 告訴Dagger 可以使用這個構(gòu)造器來構(gòu)造Rose對象。
*/
@Inject
public Rose(){
}
/**
* @Inject 是告訴Dagger使用該接口構(gòu)造Pot類。
* 同時Dagger會自動找到Rose的構(gòu)造器。
* 如果存在Rose的構(gòu)造器了,參數(shù)就不需要添加@Inject了。
* @param rose
*/
@Inject
public Pot(@Inject Rose rose){
this.rose = rose;
}
2. 標記在需要使用該對象的類中,比如某個Activity,有兩個用法:
/**
* @Inject 標注在屬性上。就是屬性注入。
* Dagger會自動生成該Pot對象。
* 不能為private,編譯不通過。
*/
@Inject
public Pot pot;
/**
* 方法注入:
* Dagger2會在構(gòu)造器執(zhí)行之后,立即調(diào)用這個方法。
* @param pot
*/
@Inject
public void setPot(Pot pot){
this.pot = pot;
Logger.logI("setPot pot:%s",pot);
}
2. @Component類
1. 寫法很簡單:
@Component
public interface MainActivityComponent {
void inject(MainActivity activity);
}
2. 作用:
- 提供給需要使用注入對象的類來使用的,比如Activity,Application,F(xiàn)ragment等。
- 命名時 采用XXX + Component的方式,Dagger會自動生成 DaggerXXXComponent。
- 其他任意命名也可以,只是上面的方式,便于區(qū)分。
- inject接口,是最常見的注入器的入口方法。參數(shù)為MainActivity,就是告訴MainActivity,你可以用我提供的方法了。
3. MainActivity的調(diào)用
/**
* @Component 定義的類,Dagger會從inject方法,開始查找MainActivity中被@Inject標注的類。
* 1. 先找到Pot類,再找Pot類中被@Inject標注的構(gòu)造方法。
* 2. 發(fā)現(xiàn)Pot構(gòu)造方法中有Rose類,再找Rose類中被@Inject標注的方法。
* 類都找全后。
* 1. 先構(gòu)造Rose類,在構(gòu)造Pot類。
* 2. 然后在MainActivity中的@Inject標注的Pot就有了實例。
*/
DaggerMainActivityComponent.create().inject(this);
String show = pot.show();
Logger.logI("test dagger show:%s",show);
3. 學習 @Module 和@Provide
Demo例子來自 DaggerTest 工程的 dagger2。這是Phone Module,可單獨運行。
1. 兩者的關系
- @Module注解類,@Provide 注解方法。因此@Provide 必須在@Module注解的類中使用。
- @Module 里面 必須至少包含@Provide 或者@Binds(@Binds 后面說)中的一個。
- 看個例子,該例子中,提供了一個Rose對象。其實和上節(jié)例子中的 Rose的構(gòu)造函數(shù)中增加@Inject 效果完全一致。
@Module
public class FlowerModule {
//此處的返回值,只能使用Rose,不能使用Flower。
@Provides
public Rose provideFlower(){
Logger.logD("provideFlower is call");
return new Rose();
}
}
//同時,@Component標注的類需要增加該Module的應用
@Component(modules = {FlowerModule.class})
public interface MainActivityComponent {...}
2. 用途:代替@Inject。為啥要代替?
- @Inject 只能標記有源碼的類。如果這個類沒有源碼,是個三方庫,就沒法使用@Inject。
- @Module 會讓代碼更加清晰,因為@Module 可以提供多個對象的構(gòu)建,減少了每個對象都用@Inject標注。
- @Component 可以依賴多個Module,有需要可以自行驗證。
建議:少用@Inject來標注構(gòu)造函數(shù),統(tǒng)一采用Module來提供。
4. 學習@Qualifier 和 @Named用法
Demo例子來自 DaggerTest 工程的 dagger3。這是Phone Module,可單獨運行。
1. @Qualifier 是用來自定義注解用的,有個統(tǒng)一的名字叫限定符,@Named 是@Qualifier的String類型的實現(xiàn)??碄Named的類就能明白,依葫蘆畫瓢,也能寫出別的自定義@Qualifier注解。
2. 作用:
- 如果@Module中提供了兩個相同對象的構(gòu)建方式,那么Dagger就不知道用哪個了。因此需要區(qū)分。
- 因此,@Qualifier在提供對象的地方,和使用對象的地方都要添加。
- 例子如下:
//1. 來自@Module類的代碼片段,演示如何提供對象。
@Provides
// @Named("black") //用字符串也可以。
@BlackRose //為自定義限定符。
public Rose provideRoseBlack(){
Logger.logV("provideFlower black is call");
return new Rose("Black");
}
@Provides
// @Named("red")
@RedRose
public Rose provideRoseRed(){
Logger.logV("provideFlower Red is call");
return new Rose("Red");
}
//2. 來自MainActivity 的代碼片段,演示如何獲取對象:
@Inject
// @Named("red")
@RedRose //使用限定符Provide后,在注入的地方,也要表明,兩者必須匹配。
public Rose rose;
@Inject
// @Named("black")
@BlackRose
public Rose blackRose;
//3. RedRose的實現(xiàn),@BlackRose類似:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface RedRose {
}
5. 學習@Component中的denpendence用法和@Subcomponent的用法
Demo例子來自 DaggerTest 工程的 dagger4,演示denpendence。這是Phone Module,可單獨運行。
Demo例子來自 DaggerTest 工程的 dagger5,演示@Subcomponent。這是Phone Module,可單獨運行。
1. 用途和作用:
- 通過之前的例子可以看出來,MainActivity獲取某個注入對象的時為:MainActivity -> MainActivityComponent -> MainActivityModule -> 某個對象。就要求所有注入的對象都要在@Module類中提供。
- 如果把一部分對象放在另外Component + Module中提供,會使得邏輯更加清晰。
- 因此denpendence 和 @Subcomponent 就提供了這樣的方式,因此這兩個方法都是用來擴展Component對象的,便于MainActivity 通過注入一個Component就能實現(xiàn)所有對象的注入。
2. 先來看denpendence的用法(工程中Module dagger4):
- 我們搭建一個這樣的演示環(huán)境。
- 用 XXXComponent + XXXModule + XXX對象 的流程 分別提供 Rose 和Lily兩個對象的獲取。
- 讓LilyComponent dependence RoseComponent。
- 讓 MainActivityComponent dependence LilyComponent。
- 具體代碼演示:
//1. RoseModule 和 RoseComponent類和上面的例子相同。
//2. LilyComponent 修改如下。
@Component(modules = {LilyModule.class},dependencies = {RoseComponent.class})
public interface LilyComponent {
Lily getLily();
}
//3. MainActivityComponent 修改如下:
@Component(dependencies = LilyComponent.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}
//4. MainActivity 注入代碼修改如下:TODO注意這里寫法有了變化
//拆開寫法如下:
// RoseComponent roseComponent = DaggerRoseComponent.create();
// LilyComponent lilyComponent = DaggerLilyComponent.builder().roseComponent(roseComponent).build();
//
// DaggerMainActivityComponent.builder().lilyComponent(lilyComponent).build().inject(this);
//采用級聯(lián)寫法:推薦:
DaggerMainActivityComponent.builder()
.lilyComponent(
DaggerLilyComponent.builder().roseComponent(
DaggerRoseComponent.create()).build()
)
.build().inject(this);
- dependence使用的應用,在注入時,需要顯示的提供??瓷厦娴腗ainActivity就能發(fā)現(xiàn)。
3. 再看@Subcomponent的用法(工程中的Module dagger5)
- 環(huán)境搭建和dependence中類似,但是寫法順序發(fā)生了變化。
- 看代碼:
//1. 先創(chuàng)建MainComponent類,注意,這是個@Subcomponent類。孤零零的一個類,啥也不依賴,最底層的一個小弟。
@Subcomponent
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
}
//2. LilyModule不做修改,LilyComponent做如下改造,依然是@Subcomponent類,可以提供MainActivityComponent。算是二弟。
@Subcomponent(modules = {LilyModule.class})
public interface LilyComponent {
MainActivityComponent plus();
}
//3. RoseModule不做修改,RoseComponent做如下改造。這是@Component,提供了LilyComponent。這是大哥
//看靠上面dependence的對比,RoseComponent從對底層,現(xiàn)在變成了大哥。
@Component(modules = {RoseModule.class})
public interface RoseComponent {
LilyComponent plus(LilyModule lilyModule);
}
//4. MainActivity注入代碼發(fā)生變化,因為@Component標注的是RoseComponent,因此注入從RoseComponent發(fā)起。
//級聯(lián)寫法如下:推薦寫法。
DaggerRoseComponent.create()
//如下plus返回LilyComponent
.plus(new LilyModule())
//如下plus 返回MainActivityComponent
.plus()
.inject(this);
4. dependence和@Subcomponent用法區(qū)別:
- 兩者功能相同,都是擴展Component類。但是從上面的兩個例子,可以很明顯的發(fā)現(xiàn),dependence和@Subcomponent 觸發(fā)邏輯是相反的。
- 另外,從MainActivity來看,用了dependence,就得顯示的提供DaggerXXXComponent的調(diào)用。而@Subcomponent,就不需要提供了。
5. 何時用dependence或者@Subcomponent?
網(wǎng)上有一些人會說 何時用 dependence,何時用@Subcomponent?
我的建議是:先不用管,知道兩者的大概用途就行了。
因為就像文章上面說的,Dagger2 是為了DaggerAndroid 打基礎的。
在DaggerAndroid擴展庫中,再看兩者的具體使用位置。
6. 學習@Scope和@Singleton
Demo例子來自 DaggerTest 工程的 dagger6,演示局部單例。這是Phone Module,可單獨運行。
Demo例子來自 DaggerTest 工程的 dagger7,演示全局單例。這是Phone Module,可單獨運行。
1. @Scope 稱為作用域,也有的叫 "命名符",管理生命周期的。@Scope是用來定義注解的,Singleton是@Scope的一個實現(xiàn)類。有點像@Qualifier 和 @Named的關系
2. 作用和用途:
- 其實這就是說明一個對象是全局單例,還是局部單例。文章最上面已經(jīng)說過了。類似通過MyApplication 提供的對象是全局對象。類似FragmentActivity 通過傳遞參數(shù)提供給Fragment的,可以叫做局部單例。
- @Scope和@Singleton 就是標記了這個@Module中的@Provide方法,通過@Component提供的時候,是每次都創(chuàng)建個新的對象,還是把創(chuàng)建過的對象直接提供給MainActivity。---有點拗口。
- 還是用代碼來說更加直觀:可以看上面的任意例子中的MainActivity類。
@Inject
Lily lily;
@Inject
Lily otherLily;
//上面的 lily 和otherLily 是兩個不同的對象。因為沒有命名符,每次@Provide都會是新的對象。
3. 使用方法,依然通過代碼演示:
//1. LilyModule中增加@Singleton
@Module
public class LilyModule {
@Provides
@Singleton
public Lily provideLily(@Named("red") Rose rose){
return new Lily(rose);
}
}
//2. 同時,LilyComponent也要增加。說明了Component + Module的這對組合,只會提供一份Lily對象。
@Singleton
@Component(modules = {LilyModule.class},dependencies = {RoseComponent.class})
public interface LilyComponent {
Lily getLily();
}
//3. MainActivityComponent,依賴LilyComponent。
@ActivityScope //自定義的@Scope
@Component(dependencies = {LilyComponent.class})
public interface MainActivityComponent {
void inject(MainActivity activity);
}
//4. @ActivityScope 代碼
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
//5. MainActivity中,lily 和 otherLily的這兩個對象的地址相同。
@Inject
Lily lily;
@Inject
Lily otherLily;
4. 額外說明:
- @ActivityScope 是定義@Scope。由于LilyComponent已經(jīng)使用了@Singleton,因此MainActivityComponent就不能再使用,需要定義一個。因為產(chǎn)生了依賴關系。
- 一個 配套的Component + Module組合,通過@Scope可以提供唯一的某個對象。
- 如果此時有個SecondActivity,這個類創(chuàng)建的Lily 對象 還是和MainActivity中不相同。以為SecondActivity重新使用了新的Component + Module組合。所以這是局部單例。
5. 如果MainActivity 和Second 也要使用同一份Lily對象,怎么辦?
最簡單的辦法,引入 AppComponent ,來提供全局單例。詳見 Demo中的Module dagger7代碼。
7. 學習@Binds 用法。
Demo例子來自 DaggerTest 工程的 dagger8,演示@Binds。這是Phone Module,可單獨運行。
1. @Binds和@Provide類似,都是在@Module中使用。@Provide提供具體的實現(xiàn)接口。@Binds 提供的接口,沒有實現(xiàn)。
2. 具體的寫法:
//1. 提供Peach對象的構(gòu)造
public class Peach implements IFruit {
@Inject
public Peach(){
}
@Override
public String name() {
return "Peach";
}
}
//2. 提供一個Module
@Module
public abstract class FruitModule2 {
@Binds
abstract IFruit provideFruit(Peach peach);
}
//3. Component引入該Module。
@Component(modules = {FruitModule2.class})
public interface SecondActivityComponent {
void inject(SecondActivity activity);
}
//4. Activity中使用。這樣,可以通過IFruit直接獲取到Peach對象。
@Inject
IFruit fruit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerSecondActivityComponent.create().inject(this);
Log.i("Loner","secondActivity find fruit:"+fruit+",name:"+fruit.name());
}
3. 說明:
- @Binds提供如果提供對象。參數(shù)需要外部提供,或者Dagger可以查找到具體的構(gòu)造器。
- 本例子是演示最基本的用法,具體的用法講到DaggerAndroid時,會再次提到。
8. Component.Builder 和 @BindsInstance 的用法。
Demo例子來自 DaggerTest 工程的 dagger9。這是Phone Module,可單獨運行。
按照官網(wǎng)的說法,Component.Builder是用來簡化在某個Activity中Dagger注入代碼。
上面的例子中XXXModule中提供的方法都是帶參數(shù)的(有個例子中帶參數(shù)了,但是參數(shù)是可以直接構(gòu)造的)。
如果XXXModule中帶了參數(shù),在XXXActivity注入時,就需要顯示的申明。
Component.Builder就可以簡化這個過程。
1. 不使用Component.Builder時,情況大致如下:
//1. 提供MainActivityModule類
@Module
public class MainActivityModule {
Person person;
public MainActivityModule(String name){
this.person = new Person(name);
}
@Provides
Person providePerson(){
return person;
}
/**
* Dagger僅提供了對象的注入方式,String也是對象,所以可以提供。
*/
@Provides
String providePersonName(){
return person.name();
}
}
//2. MainActivityComponent
@Component(modules = MainActivityModule.class)
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
}
//3. MainActivity中如需要這樣寫。這樣就可以通過@Inject使用providePersonName。這是個字符串。
//由于MainActivityModule需要帶參構(gòu)造,因此Dagger不能自動生成,需要顯示的生成該對象。
DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule("Lily"))
.build()
.inject(this);
2. 使用Component.Builder 時,這樣寫:
//1. SecondActivityModule如下:
@Module
public class SecondActivityModule {
//上面提供Person的代碼全部移除,生成Name的方式,替換為參數(shù)傳入。
// 該參數(shù)在Component.buidler中保持一致。
@Provides
String providePersonName(Person person){
return person.name();
}
}
//2. SecondActivityComponent如下:
@Component(modules = SecondActivityModule.class)
public interface SecondActivityComponent {
void inject(SecondActivity mainActivity);
//如下是關鍵代碼
@Component.Builder //固定不變
interface Builder{ //固定不變
SecondActivityComponent build(); //固定不變。
//該接口的參數(shù)和@Module中@Provide的參數(shù)保持一致,目的就是為其提供實例。
@BindsInstance Builder person(Person person);
}
}
//3. SecondActivity 寫法。
DaggerSecondActivityComponent.builder().person(new Person("Lucy")).build().inject(this);
3. 說明:
- 上面兩個對比,就可以發(fā)現(xiàn)Component.Builder方式簡化一些代碼。
- 其他文檔上面寫法不太一樣,有些寫法已經(jīng)被淘汰了。
- Component.Builder 用法基本為固定套路,記住就行了。
9. 其他
- Lazy:如果某個對象的構(gòu)造器會存在復雜邏輯,可以使用Lazy延遲獲取。
//1. 寫法。
@Inject
Lazy<Lily> lily
//2. 用法:
lily.get() //此時才會真實的創(chuàng)建 lily對象。
- @IntoMap,ClassKey等等,在詳解DaggerAndroid擴展庫時,會有說明。
11. 本文檔對應的Demo地址,請 下載 提取碼:pjyk
10. Dagger-Android擴展庫的簡單說明:
- 如果覺得學完上面的Dagger2 就用在Android研發(fā)上,那就打錯特錯了,Dagger2只是基礎,只是學習DaggerAndroid擴展庫的基礎。
- Dagger2 的學習網(wǎng)上的資料還是挺多,但是到了DaggerAndroid擴展庫時,就會發(fā)現(xiàn)少了很多,或者寫法已經(jīng)用了淘汰的方法。
- 接下來去看這篇文章吧:DaggerAndroid