市場上已經很多依賴注入套件(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 來暴露其提供的共享依賴。