簡介
Dagger2
是一個 Dependency Injection(DI) 依賴注入框架。它提供給 Java
和 Android
使用,主要用于模塊間解耦、提高代碼的健壯性和可維護性。
使用了 IOC (控制反轉(zhuǎn))的思想,在編譯階段使用 APT 利用 Java 注解生成 Java 代碼,然后結(jié)合部分手寫代碼來完整依賴注入工作。
運行前需要先編譯一次項目,目的是用 APT 生成中間代碼。Dagger2
不使用反射,在編譯階段生成代碼,所以不會程序性能有影響。
Dagger2 官網(wǎng)
Dagger2 Github
IOC
解釋:
IOC
全名為 Inversion of Control ,概念大體是借助于“第三方”實現(xiàn)具有依賴關(guān)系的對象之間的解耦。
IOC和DI的區(qū)別:
Martin Fowler探討既然 IOC 是控制反轉(zhuǎn),到底哪些方面的控制被反轉(zhuǎn)了?其實是獲得依賴對象的過程被反轉(zhuǎn)了,控制反轉(zhuǎn)后,獲得依賴對象的過程從自身管理變成了 IOC 容器主動注入,后來給控制反轉(zhuǎn)取了更適合的名字“依賴注入”。實際上給出了實現(xiàn) IOC 的方法:依賴注入
Dagger2的注解
@Inject
@Inject
這個注解本身并沒有作用,它需要依賴于注入框架才具有意義,可以用來標記構(gòu)造函數(shù)、屬性和方法。
1.1 標記構(gòu)造函數(shù)
- 被標記的構(gòu)造函數(shù)可以有 0個或多個依賴作為參數(shù)
- 同一個類中最多只可以標記一個構(gòu)造函數(shù)
class People @Inject constructor(val name:String = "Tom")
注意在 Kotlin 中這種寫法是不被允許的,因為這等價于 Java 中的多個構(gòu)造方法 People(String name), People()
,正確的寫法應(yīng)該是這樣:
data class People constructor(val name: String) {
@Inject
constructor() : this("Tom")
}
1.2 標記屬性
- 被標記的屬性不能是
final
的,Kotlin中不能是val
- 被注入進的屬性不能用
private
修飾(是 Dagger2 不支持,而非 @Inject 不支持)
@Inject
lateinit var people:People
1.3 標記方法
- 被標記的方法可以有 0個或多個依賴作為參數(shù)。
- 方法不能是抽象的。
class HomeActivity : AppCompatActivity() {
private lateinit var people:People
@Inject
fun setPeople(people:People){
this.people = people
}
}
這種方法注入和屬性注入并沒有什么本質(zhì)上的不同,實現(xiàn)效果也基本一樣。還有一種做法是 @Inject
標記被注入類的某個方法,該方法會在類的構(gòu)造方法之后接著被調(diào)用:
data class People constructor(val name: String) {
@Inject
constructor() : this("Tom")
init {
println("init:$name")
}
@Inject
fun hello(){
println("hello:$name")
}
}
class HomeActivity : AppCompatActivity() {
@Inject
lateinit var people:People
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
//執(zhí)行相關(guān)注入操作
...
println(people.toString())
}
}
運行結(jié)果是這樣的:
01-02 11:57:30.995 16601-16601/? I/System.out: init:Tom
01-02 11:57:30.995 16601-16601/? I/System.out: hello:Tom
01-02 11:57:30.995 16601-16601/? I/System.out: People(name=Tom)
@Component
可以理解為一個注射器,可以算是 Dagger2
中最核心的一個注解,用來標記一個接口或者抽象類。使用 @Component
標記的接口,會在編譯時自動生成一個 Dagger+類名的實現(xiàn)類實現(xiàn)依賴注入。在 Component
中一般可以定義兩種方法:
@Module
用來標記類,為 Component
提供依賴,相當于告訴 Component
,如果需要依賴可以來找我,當然前提是在 Component
中配置了該Module
。同時 Module 可以通過 includes
依賴其他的 Module。
@Provides
用來標記 Module中的方法,該方法的返回類型是你需要提供的依賴類型。
舉個自己項目中的例子,我需要在presenter中創(chuàng)建一個pl2303對象,pl2303對象的創(chuàng)建又需要context和pl2303Interface,所以我們需要提供三個依賴,因為context在其他地方也要用,我們單獨提出來:
@Singleton
默認情況下,@Inject
獲取到的依賴對象是非單例的,要想實現(xiàn)單例,需要用 @Singleton
對Module中的provide方法和Conponent接口進行標注。
Dagger&Hilt的使用
模擬場景:假設(shè) App 需要使用 Retrofit 進行網(wǎng)絡(luò)訪問
1.1 添加依賴
在 build.gradle(:app)
里添加以下依賴庫
plugins {
...
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
dependencies {
...
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
//Dagger - Hilt
implementation "com.google.dagger:hilt-android:2.38.1"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
kapt "com.google.dagger:hilt-android-compiler:2.37"
kapt "androidx.hilt:hilt-compiler:1.0.0"
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.3'
}
在 build.gradle(Dagger&Hilt)
里添加以下代碼
buildscript {
....
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:2.38.1"
}
}
1.2 添加一個應(yīng)用程序類
/**
* 這是一個應(yīng)用程序類,用于傳遞上下文
* 需要在注冊清單中聲明這個文件
*/
@HiltAndroidApp
class MyApp :Application()
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyApp"
...
>
...
</application>
</manifest>
1.3 給 MainActivity 添加入口注解
@AndroidEntryPoint// 入口點注釋
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DaggerHiltTheme {
val viewModel = hiltViewModel<MyViewModel>()
}
}
}
}
1.4 創(chuàng)建網(wǎng)絡(luò)訪問接口
interface MyApi {
//模擬網(wǎng)絡(luò)請求
@GET("test")
suspend fun doNetworkCall()
}
1.5 創(chuàng)建存儲庫接口
interface MyRepository {
suspend fun doNetworkCall()
}
1.6 創(chuàng)建存儲庫實現(xiàn)類
/**
* 問題:
* 1、如何把 MyApi 放入我們的存儲庫中,因為存儲庫中需要 MyApi 這個依賴
* 2、如何讓 Dagger-Hilt 知道我們想在這里使用 MyApi
*
* 解決方案:
* 1、直接在構(gòu)造器里添加我們的 MyApi
* 2、通過創(chuàng)建依賴注入的容器(Module)來幫助,使用@Inject注解通過構(gòu)造器注入
*/
class MyRepositoryImpl @Inject constructor(
private val api: MyApi,
private val appContext: Application
) : MyRepository {
init {
val appName = appContext.getString(R.string.app_name)
println("Hello from the MyRepositoryImpl. The app name's $appName")
}
override suspend fun doNetworkCall() {
//模擬網(wǎng)絡(luò)請求
}
}
1.7 創(chuàng)建全局單例模塊
@Module
@InstallIn(SingletonComponent::class)// 安裝到單例組件
object AppModule {
/**
* 每當我們提出請求時,例如在存儲庫中嘗試注入 MyApi 實例,
* 然后 DaggerHilt 會在其 Module 中查找是否能找到對應(yīng)的實例,
* 如果找到,它將獲取實例并傳遞給存儲庫。
*/
@Provides//告知這里提供了一個依賴
@Singleton//單例范圍注釋
fun provideMyApi(): MyApi {
return Retrofit.Builder()
.baseUrl("https://test.com")
.build()
.create(MyApi::class.java)
}
@Provides
@Singleton
fun provideMyRepository(
api: MyApi,
app: Application,
@Named("hello1") hello: String
): MyRepository {
return MyRepositoryImpl(api, app)
}
/**
* 常見問題:
* 如果我們所需要的依賴項的類型一樣,
* DaggerHilt 如何知道傳遞哪個參數(shù)給我們?
*
* 解決方案:
* 使用 @Named("") 注解標記,在傳參時也對應(yīng)加上這個注解即可
*/
@Provides
@Singleton
@Named("hello1")
fun provideString1() = "Hello 1"
@Provides
@Singleton
@Named("hello2")
fun provideString2() = "Hello 2"
}
1.8 單獨創(chuàng)建接口和抽象類的模塊
最佳實踐:如果想注入接口或者抽象類,有更簡單的方法實現(xiàn),在 di
包新建一個模塊
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
// 因為這里時提供依賴項的不同方式,所以這里不稱為提供,叫<綁定>
@Binds
@Singleton
abstract fun bindMyRepository(
myRepository: MyRepositoryImpl
): MyRepository
}
1.9 常見問題
2.1 相同類型的依賴項無法識別
當我們需要注入依賴時,在生產(chǎn)實例的模塊中查找到多個類型相同的實例時,又或者我們的依賴項的類型一樣時 DaggerHilt 如何知道傳遞哪個參數(shù)給我們。解決方案:使用 @Named("")
注解,代碼如下:
//使用 @Named("") 注解標記,在傳參時也對應(yīng)加上這個注解即可
@Provides
@Singleton
fun provideMyRepository(
api: MyApi,
app: Application,
@Named("hello1") hello: String
): MyRepository {
return MyRepositoryImpl(api, app,hello)
}
@Provides
@Singleton
@Named("hello1")
fun provideString1() = "Hello 1"
@Provides
@Singleton
@Named("hello2")
fun provideString2() = "Hello 2"
2.2 如何注入依賴項到 Service
想把依賴項注入到后臺運行的 Service 中,如果給 Service 創(chuàng)建構(gòu)造器注入是不起作用的,因為不能給 Service 構(gòu)造器,所以用(字段注入)注意字段注入不能是 Private,解決方案:使用字段注入代碼如下:文章來源:http://www.zghlxwxcb.cn/news/detail-725947.html
@AndroidEntryPoint
class MyService :Service(){
@Inject
lateinit var repository: MyRepository
override fun onCreate() {
super.onCreate()
// 在 onCreate 執(zhí)行完后,可以直接調(diào)用 repository 進行網(wǎng)絡(luò)訪問不需要自己進行初始化
//repository.doNetworkCall()
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
}
rvice 構(gòu)造器,所以用(字段注入)注意字段注入不能是 Private,解決方案:使用字段注入代碼如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-725947.html
@AndroidEntryPoint
class MyService :Service(){
@Inject
lateinit var repository: MyRepository
override fun onCreate() {
super.onCreate()
// 在 onCreate 執(zhí)行完后,可以直接調(diào)用 repository 進行網(wǎng)絡(luò)訪問不需要自己進行初始化
//repository.doNetworkCall()
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
}
到了這里,關(guān)于Android之Dagger&Hilt依賴注入使用指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!