市場上已經很多依賴注入套件(dagger , koin)的選擇 ,還有很多文章介紹了,我會選擇 hilt 很單純,就是對於各類 mvvm 的物件,有魔法般的支援,真的少非常多代碼,選擇hilt 並非絕對正確,我的文章一直提到一件事 就是 根據情境,才是正確的
依賴注入 (Dependency Injection) 的手動實作
我們每天都在 new 物件,然後像快遞員一樣,把它們從 Activity 傳到 ViewModel,再傳到 Repository,最後送到 DataSource 手中。這是在寫程式,還是在做物流?
為何需要 Hilt?
依賴注入是個好東西,它能讓我們的程式碼解耦、易於測試。但手動實現它的過程,卻充滿了大量容易出錯的建構子傳遞和工廠模式,這完全違背了我們追求「簡潔」的初衷。
Hilt 的誕生,就是為了解決這個核心矛盾。它大聲地說:「專心在你的業務邏輯上,這些無聊的體力活,我包了!」
Hilt 的三大核心武器:入門的基礎
要讓 Hilt 為你工作,你首先需要掌握三樣最基本的武器。
1. @Inject 與 @AndroidEntryPoint
這是 Hilt 的基礎。@Inject 用來告訴 Hilt「這個東西你可以幫我準備」,而 @AndroidEntryPoint 則是告訴 Hilt「這個地方需要你來服務」。
Kotlin
// UserRepository.kt - 一個普通的類別
// 在建構子上標記,Hilt 就知道如何建立它
class UserRepository @Inject constructor(
private val apiService: ApiService
) { /*...*/ }
// MainActivity.kt - 一個 Android 元件
@AndroidEntryPoint // 告訴 Hilt,這個 Activity 需要注入
class MainActivity : AppCompatActivity() {
@Inject // 提出需求:「我需要一個 UserRepository」
lateinit var userRepository: UserRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate()
// 在 super.onCreate() 之後,userRepository 就已經被 Hilt 準備好了
}
}
// 別忘了在 Application 中啟用 Hilt
@HiltAndroidApp
class MyApplication : Application() {}
2. @Provides:客製化生產說明書
當 Hilt 遇到它無法直接 new 的物件(例如來自 Retrofit、Room 等第三方庫的物件),你就需要提供一份詳細的「DIY 組裝指南」。
Kotlin
// NetworkModule.kt - 提供網路相關物件的說明書
@Module
@InstallIn(SingletonComponent::class) // 這本說明書的內容適用於整個 App
object NetworkModule {
@Provides // 這是一份指南
@Singleton // 產出的物件在整個 App 中只會有一個
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient) // Hilt 會自動把 OkHttpClient 遞過來
.build()
}
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().build()
}
}
3. @Binds:高效率職位指派令
當你只是想告訴 Hilt「某個介面(職位)由哪個具體類別(員工)來實現」時,使用 @Binds。它不做實際的建立工作,只做「綁定」,因此效能更好。
Kotlin
// UserRepository.kt - 介面 (職位)
interface UserRepository { /*...*/ }
// UserRepositoryImpl.kt - 實作 (員工)
class UserRepositoryImpl @Inject constructor(...) : UserRepository { /*...*/ }
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule { // 使用 @Binds 的模組必須是 abstract
@Binds
abstract fun bindUserRepository(
impl: UserRepositoryImpl
): UserRepository
}
InstallIn and @Qualifier
物件的生命週期管理是避免記憶體洩漏的關鍵。Hilt 透過作用域 (Scope) 和限定符 (Qualifier) 提供了精準的控制。
- @InstallIn 與作用域 (@…Scoped): @InstallIn 決定了依賴被安裝在哪個 Hilt 元件中(SingletonComponent、ActivityComponent 等),決定了其生命週期的上限。而作用域註解(@Singleton、@ActivityScoped、@ViewModelScoped)
// 此物件的生命週期與單一 ViewModel 實例綁定
@Module
@InstallIn(ViewModelComponent::class)
object SessionModule {
@Provides
@ViewModelScoped
fun provideSessionCache(): SessionCache { /*...*/ }
}
- @Qualifier (如 @Named): 當你需要提供多個相同型別的物件時(例如 API URL 和 API Key 都是 String),使用限定符為它們貼上標籤,以消除歧義。
@Provides
@Named("BaseUrl")
fun provideBaseUrl(): String = "https://api.example.com/"
@Provides
@Named("ApiKey")
fun provideApiKey(): String = "your_secret_key"
// 注入時也使用同樣的標籤
class ApiClient @Inject constructor(
@Named("BaseUrl") private val baseUrl: String,
@Named("ApiKey") private val apiKey: String
)
Jetpack 的高度整合
Hilt 的真正威力在於它與 Jetpack 的深度整合,專治各種「Android 特有種」的疑難雜症。
- @HiltViewModel: 讓你徹底告別手寫 ViewModelProvider.Factory 的痛苦。只需一個註解,Hilt 就會自動處理 ViewModel 的建立與依賴注入,甚至免費贈送 SavedStateHandle。
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel()
// 在 Fragment/Activity 中正常獲取即可
private val viewModel: MyViewModel by viewModels()
- @HiltWorker: 透過 @Assisted。 Inject,讓由系統管理的 WorkManager 背景任務也能輕鬆獲取 Hilt 提供的依賴。
Hilt 多模組依賴注入
在大型 Android 專案中,多模組架構是提升建構速度和程式碼分離度的關鍵。然而,如何有效管理跨模組的依賴注入,成為了核心挑戰。本文將以程式碼為核心,講解 Hilt 在多模組環境下的標準實踐。
核心原則:共享頂層元件
Hilt 的多模組策略,是利用一個所有模組共享的頂層元件 SingletonComponent 作為依賴的橋樑。其運作模式如下:
- 底層模組 (library, core):負責「提供」共享的依賴物件 (如 OkHttpClient, Room Database),並將其註冊到 SingletonComponent 中。
- 上層模組 (feature, app):從 SingletonComponent 中「獲取」並注入這些共享的依賴,無需關心其具體實作來源。
實作步驟
假設專案結構為 :app -> :feature_login -> :core_network。
1. 在 :core_network 模組中提供依賴
首先,在最底層的 :core_network 模組中建立 Hilt Module,來提供一個全域共享的 OkHttpClient 實例。
core_network/src/main/java/com/example/core/network/NetworkModule.kt
Kotlin
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import javax.inject.Singleton
@Module
// 關鍵:將此模組安裝到 SingletonComponent,使其成為全域可用的元件。
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
// 關鍵:確保在 App 生命週期中,此物件只有一個實例。
@Singleton
fun provideOkHttpClient(): OkHttpClient {
// ... 進行 OkHttpClient 的相關設定
return OkHttpClient.Builder().build()
}
}
2. 在 :feature_login 模組中使用依賴
接著,在 :feature_login 模組中,我們需要設定 Gradle 依賴,並直接注入 OkHttpClient。
feature_login/build.gradle.kts
Kotlin
dependencies {
// 依賴底層的 :core_network 模組
implementation(project(":core_network"))
// Hilt 相關依賴
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-compiler:2.51.1")
}
feature_login/src/main/java/com/example/feature/login/LoginActivity.kt
Kotlin
import androidx.appcompat.app.AppCompatActivity
import com.example.core.network.OkHttpClient // 注意:這裡是示意,通常會注入 Repository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
// 關鍵:直接使用 @Inject 請求依賴。
// Hilt 會自動查找 SingletonComponent 中已註冊的提供者。
@Inject
lateinit var okHttpClient: OkHttpClient
// ...
}
api vs implementation
在 Gradle 中,依賴的傳遞性至關重要。
- implementation(project(“:…”)): 依賴不會向上傳遞。如果 :app 依賴 :feature,而 :feature implementation :core,則 :app 看不見 :core 的內容。
- api(project(“:…”)): 依賴會向上傳遞。在上述情況下,若 :feature api :core,則 :app 也能存取 :core 的內容。
Hilt 規則:如果一個模組 A 提供了希望被模組 C (C -> B -> A) 注入的 Hilt 綁定,那麼在 B 的 build.gradle 中,對 A 的依賴必須使用 api。在多數情況下,底層 core 模組建議使用 api 來暴露其提供的共享依賴。