Dagger2的使用
Dagger2是什麼?
Dagger2是Dagger的升級版,是一個依賴注入框架,第一代由大名鼎鼎的Square公司共享出來,第二代則是由谷歌接手後推出的,現在由Google接手維護。(Dagger2是Dagger1的分支,但兩個框架沒有嚴格的繼承關係,亦如Struts1 和Struts2 的關係!)
那麼,什麼是依賴注入?
依賴注入是物件導向程式設計的一種設計模式,其目的是為了降低程式耦合,這個耦合就是類之間的依賴引起的.
舉個例子:
public class ClassA{ private ClassB b public ClassA(ClassB b){ this.b = b } }
這裡ClassA的建構函式里傳了一個引數ClassB,隨著後續業務增加也許又需要傳入ClassC,ClassD.試想一下如果一個工程中有5個檔案使用了ClassA那是不是要改5個檔案?
這既不符合開閉原則, 也太不軟工了.這個時候大殺器Dagger2就該出場了.
public class ClassA{ @inject private ClassB b public ClassA(){ } }
透過註解的方式將ClassB b注入到ClassA中, 可以靈活配置ClassA的屬性而不影響其他檔案對ClassA的使用.
那就有人問了,為什麼要用Dagger2?
回答:解耦(DI的特性),易於測試(DI的特性),高效(不使用反射,google官方說名比Dagger快13%),易混淆(apt方式生成程式碼,混淆後依然正常使用)
如何使用Dagger2
環境配置
這裡以Gradle配置為例子,實用得是AndroidStudio3.2:
當AndroidStudio升級到3.0後,同時也更新了gradle到4.1後,需要 去除 掉project的build.gradle配置(本文都是kotlin寫法)
//classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
app module 的 build.gradle裡的
//apply plugin: 'com.neenbedankt.android-apt'apply plugin: 'kotlin-kapt'
開啟app module 的 build.gradle ,新增
apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-kapt'//...dependencies { //... implementation 'com.google.dagger:dagger:2.16' //annotationProcessor 'com.google.dagger:dagger-compiler:2.16' kapt 'com.google.dagger:dagger-compiler:2.16'}
Dagger2常用的註解:
@Inject
@Module, @Provides
@Component
@Singleton
@Named, @Qualifier
Lazy, Provider
@Scope
示例
下面我們來看一個示例,實用Dagger2到底是怎麼依賴注入的。
現在有一個Person類,然後MainActivity中又一個成員變數person。
class Person { constructor() { Log.i("dagger2: ", "a person created") } }
class MainActivity : AppCompatActivity() { var person: Person? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) person = Person() } }
如果不適用依賴注入,那麼我們只能在MainActivity中自己new一個Person物件,然後使用。
使用依賴注入:
class MainActivity : AppCompatActivity() { @Inject @JvmField var person: Person? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
kotlin寫法需要新增@JvmField,或者使用關鍵字 lateinit 修飾,不然會報錯:
Dagger does not support injection into private fields private com.example.ghp.dagger2testdemo.Person person;
那麼問題來了,就一個@Inject 註解,系統就會自動給我建立一個物件? 當然不是,這個時候我們需要一個Person類的提供者Module
@Moduleclass MainModule { @Provides fun providesPerson(): Person { Log.i("dagger2: ","a person created from MainModule") return Person() } }
裡面兩個註解,@Module 和 @Provides,Module標註的物件,你可以把它想象成一個工廠,可以向外提供一些類的物件。
同時需要引入component容器。
可以把它想成一個容器, module中產出的東西都放在裡面,然後將component與我要注入的MainActivity做關聯,MainActivity中需要的person就可以衝 component中去去取出來。
@Component(modules = [(MainModule::class)])interface MainComponent { fun inject(mainActivity: MainActivity)//表示怎麼和要注入的類關聯}
看到一個新注入 @Component 表示這個介面是一個容器,並且與 MainModule.class 關聯,它生產的東西都在這裡。
然後在MainActivity中將component 關聯進去:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var component: MainComponent = DaggerMainComponent.builder() .mainModule(MainModule()).build() component.inject(this) }
然後執行專案,檢視log:
"a person created from MainModule""a person created"
說明建立了物件,並且注入到MainActivity中。
上面有一個DaggerMainComponent,是在build的過程中,APT(就是dagger-compiler)掃描到註解(@Component@Module)生成的具體的component類(命名方式是Dagger+類名).這個過程用下面這張圖表示:
dagger build
單例模式 @Singleton(基於Component)
上面的MainActivity程式碼不變,我們再在MainActivity中新增一個 @Inject @JvmField var person: Person? = null,並列印兩個 person物件,結果如下:
"a person created from MainModule""a person created""a person created from MainModule""a person created"
發現person會被建立兩次,並且兩個person物件也不同,如果我們希望只有一個 person 和 person2 都指向同一個Person物件了, 使用 @Singleton 註解
兩個地方需要新增:
@Moduleclass MainModule { @Singleton @Provides fun providesPerson(): Person { Log.i("dagger2: ","a person created from MainModule") return Person() } } @Singleton @Component(modules = [(MainModule::class)])interface MainComponent { fun inject(mainActivity: MainActivity) }
再執行,發現只建立了一次,並且兩個person指向同一個物件。
需要非常注意的是:
單例是基於Component的,所以不僅 Provides 的地方要加 @Singleton,Component上也需要加。並且如果有另外一個OtherActivity,並且建立了一個MainComponent,也注入Person,這個時候 MainActivity和OtherActivity中的Person是不構成單例的,因為它們的Component是不同的。
帶有引數的依賴物件
如果構造Person類,需要一個引數Context,我們怎麼注入呢? 要知道注入的時候我們只有一個 @Inject 註解,並不能帶引數。所以我們需要再 MainModule 中提供context,並且由 providesXXX 函式自己去構造。如:
class Person { var context: Context? = null constructor(context: Context) { this.context = context Log.i("dagger2: ", "a person created with context") } }@Moduleclass MainModule { var context: Context constructor(context: Context){ this.context = context } @Provides fun providersContext(): Context { return this.context } @Singleton @Provides fun providesPersonWithContext(context: Context): Person { Log.i("dagger2: ","a person created from WithContext") return Person(context) } }
這裡需要強調的是, providesPerson(Context context)中的 context,不能直接使用 成員變數 this.context,而是要在本類中提供一個 Context providesContext() 的 @Provides 方法,這樣在發現需要 context 的時候會呼叫 provideContext 來獲取,這也是為了解耦。
依賴一個元件
如果元件之間有依賴,比如 Activity 依賴 Application一樣,Application中的東西,Activity要直接可以注入,怎麼實現呢?
例如,現在由 AppModule 提供Context物件, ActivityModule 自己無需提供Context物件,而只需要依賴於 AppModule,然後獲取Context 物件即可。
@Moduleclass AppModule { var context: Context constructor(context: Context){ this.context = context; } @Provides fun providesContext(): Context { return context } } @Component(modules = [(AppModule::class)])interface AppComponent { fun getContext(): Context } @Moduleclass ActivityModule { @Provides @Singleton fun providePerson(context: Context): Person { return Person(context) } } @Singleton @Component(dependencies = [(AppComponent::class)], modules = [(ActivityModule::class)])interface ActivityComponent { fun inject(mainActivity: MainActivity) }
透過上面例子,我們需要注意:
ActivityModule 也需要建立Person時的Context物件,但是本類中卻沒有 providesContext() 的方法,因為它透過 ActivityComponent依賴於 AppComponent,所以可以透過 AppComponent中的 providesContext() 方法獲取到Context物件。
AppComponent中必須提供 Context getContext(); 這樣返回值是 Context 物件的方法介面,否則ActivityModule中無法獲取。
使用方法:
一定要在 activityComponent中注入 appComponent 這個它依賴的元件。我們可以看到,由於AppComponent沒有直接和 MainActivity發生關係,所以它沒有 void inject(...);這樣的介面
var appComponent: AppComponent = DaggerAppComponent.builder() .appModule(AppModule(this)) .build() var activityComponent: ActivityComponent = DaggerActivityComponent.builder() .appComponent(appComponent) .activityModule(ActivityModule()) .build() activityComponent.inject(this)
自定義標記 @Qualifier 和 @Named
如果Person中有兩個構造方法,那麼在依賴注入的時候,它怎麼知道我該呼叫哪個構造方法呢?
修改Person類,兩個不同的構造方法
class Person { var context: Context? = null var name: String? = null constructor(context: Context) { this.context = context Log.i("dagger2: ", "a person created with context") } constructor(name: String) { this.name = name Log.i("dagger2: ", "a person created with name") } }
有兩種方法可以解決這個問題:
@Named(“…”)和@Qualifier自定義標籤
使用@Named 會使用到 字串 ,如果兩邊都必須寫對才能成功,並且字串總是不那麼優雅的,容易出錯,所以我們可以自定義標籤來解決上面的問題。
下面是2者的區別使用,@Named的使用同步註釋
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class PersonWithContext@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class PersonWithName
@Moduleclass MainModule { var context: Context constructor(context: Context){ this.context = context } @Provides fun providersContext(): Context { return this.context }// @Named("context") @PersonWithContext @Singleton @Provides fun providesPersonWithContext(context: Context): Person { Log.i("dagger2: ","a person created from WithContext") return Person(context) }// @Named("string") @PersonWithName @Singleton @Provides fun providersPersonWithName(): Person { Log.i("dagger2: ","a person created from WithName") return Person("ghp") } }
分別在兩個提供Person的provides方法上新增 @Named標籤或者自定義標籤,並指定。
然後在要依賴注入的地方,同樣新增 @Name 或自定義標註表示要注入時使用哪一種
// @field:Named("context") @field:PersonWithContext @Inject @JvmField var person: Person? = null// @field:Named("string") @field:PersonWithName @Inject @JvmField var person1: Person? = null
從上面程式碼可以看出,在使用標籤時@field:,不然會報錯:
cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method
變數編譯為 Java 位元組碼的時候會對應三個目標元素,一個是變數本身、還有 getter 和 setter,Kotlin 不知道這個變數的註解應該使用到那個目標上。
要解決這個方式,需要使用 Kotlin 提供的註解目標關鍵字來告訴 Kotlin 所註解的目標是那個,上面示例中需要註解應用到 變數上,所以使用 field 關鍵字
懶載入Lazy和強制重新載入Provider
在注入時分別使用 Lazy 和 Provider 修飾要注入的物件:
@Inject var lazyPerson: Lazy<Person>? = null @Inject var providerPerson: Provider<Person>? = null
在使用的地方:
var person: Person? = lazyPerson?.value var person2: Person? = providerPerson?.get()
lazyPerson 多次get 的是同一個物件,
providerPerson多次get,每次get都會嘗試建立新的物件。
@Scope 自定義生命週期
透過前面的例子,我們遇到了 @Singleton 這個標籤,它可以保證在同一個Component中,一個物件是單例物件。其實可以跟進去看程式碼:
Singleton.java
@Scope@Documented@Retention(RUNTIME)public @interface Singleton {}
利用單例和元件間依賴的關係,我們也可以定義生命週期來滿足我們的需求呢,比如Activity 這樣的生命週期
@Scope @Retention(AnnotationRetention.RUNTIME) annotation class ActivityScope
除了名字,其他都和 @Singleton 是一樣的。
然後用ActivityScope 修飾 ActivityModule和ActivityComponent
@Moduleclass ActivityModule { @ActivityScope @Provides// @Singleton fun providePerson2(context: Context): Person2 { return Person2(context) } } @ActivityScope//@Singleton@Component(dependencies = [(AppComponent::class)], modules = [(ActivityModule::class)])interface ActivityComponent { fun inject(main2Activity: Main2Activity) }
作者:_九卿_
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2508/viewspace-2817507/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Dagger2使用指北
- 【譯】Dagger2在Android中的使用Android
- Hement:關於專案中的Dagger2的使用(三)
- 強行來一波Dagger2使用介紹
- 當Dagger2撞上ViewModelView
- Dagger2融合篇(三)
- 易於理解的Dagger2入門篇
- 聽說你還不會用Dagger2?Dagger2 For Android最佳實踐教程Android
- Dagger2基礎篇(一)
- Dagger2進階篇(二)
- MVP + Dagger2原始碼體驗MVP原始碼
- 「神兵利器Dagger2 | 掘金技術徵文 」
- Android開發知識:Dagger2入門Android
- 走馬觀花-Dagger2 - @Inject 和 @Component
- 【從零開始擼一個App】Dagger2APP
- 使用Kotlin構建MVVM應用程式—第四部分:依賴注入Dagger2KotlinMVVM依賴注入
- 出來混遲早要還的,技術債Dagger2:Android篇(上)Android
- 出來混遲早要還的,技術債Dagger2:基礎篇
- 【android】擺正姿勢,dagger2原來如此簡單Android
- 出來混遲早要還的,技術債Dagger2:Android篇(中)@Scope、@SingletonAndroid
- Dagger2 簡單入門三部曲(一)——是什麼?
- 出來混遲早要還的,技術債Dagger2:Android篇(下)進一步理解DaggerAndroid
- Scrapy框架的使用之Scrapyrt的使用框架
- Urllib庫的使用一---基本使用
- Docker框架的使用系列教程(四)容器的使用Docker框架
- ActiveMQ的使用及整合spring的使用例項MQSpring
- vmstat的使用
- char *的使用
- mount 的使用
- conda的使用
- 索引的使用索引
- pinia的使用
- netcat的使用
- jextract的使用
- postman的使用Postman
- pycnblog的使用
- Tensorboard的使用ORB
- Playwright的使用