Hilt 是基於 Dagger 開發的全新的依賴項注入代碼庫,它簡化了 Android 應用中 Dagger 的調用方式。本文經過簡短的代碼片斷爲您展現其核心功能以幫助開發者們快速入門 Hilt。html
如需在應用中配置 Hilt,請先參考 Gradle Build Setup。android
完成安裝所有的依賴和插件之後,僅需在您的 Application 類以前添加 @HiltAndroidApp 註解便可開始使用 Hilt,而無需其它操做。git
@HiltAndroidApp class App : Application()
當您寫代碼用到依賴項注入的時候,有兩個要點須要考慮:github
而上述這兩點並不互斥,並且在不少狀況下,您的類既能夠注入依賴項同時也包含依賴。api
使依賴項可注入緩存
若是須要在 Hilt 中使某個類變得可注入,您須要告訴 Hilt 如何建立該類的實例。該過程叫作綁定 (bindings)。框架
在 Hilt 中定義綁定有三種方式:ide
@Inject
註解;@Binds
註解;@Provides
註解。⮕ 在構造函數上使用 @Inject
註解函數
任何類的構造函數均可以添加 @Inject
註解,這樣該類在整個工程中均可以做爲依賴進行注入。測試
class OatMilk @Inject constructor() { ... }
⮕ 使用模塊
在 Hilt 中另外兩種將類轉爲可注入的方法是使用模塊。
Hilt 模塊 就好像 "菜譜",它能夠告訴 Hilt 如何建立那些不具有構造函數的類的實例,好比接口或者系統服務。
此外,在您的測試中,任何模塊均可以被其它模塊所替代。這有利於使用 mock 替換接口實現。
模塊經過 @InstallIn 註解被安裝在特定的 Hilt 組件 中。這一部分我會在後面詳細介紹。
選項 1: 使用 @Binds
爲接口建立綁定
若是您但願在須要 Milk
時候,使用 OatMilk
在代碼中取而代之,那麼能夠在模塊中建立一個抽象方法,而後爲該方法添加 @Binds
註解。注意 OatMilk 自己必須是可注入的,僅需在 OatMilk 的構造函數上添加 @Inject
註解便可。
interface Milk { ... } class OatMilk @Inject constructor(): Milk { ... } @Module @InstallIn(ActivityComponent::class) abstract class MilkModule { @Binds abstract fun bindMilk(oatMilk: OatMilk): Milk }
選項 2: 使用 @Provides 來建立工廠函數
當實例沒法被直接建立,您能夠建立一個 provider。provider 就是能夠返回對象實例的工廠函數。
一個典型的例子就是系統服務,好比 ConnectivityManager
,它們的實例須要經過 Context 對象來返回。
@Module @InstallIn(ApplicationComponent::class) object ConnectivityManagerModule { @Provides fun provideConnectivityManager( @ApplicationContext context: Context ) = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
只要使用註解 @ApplicationContext
或者 @ActivityContext
,Context
對象就是默承認注入的。
注入依賴
當依賴可注入後,您可使用 Hilt 經過兩種方式:
⮕ 做爲構造函數參數注入
interface Milk { ... } interface Coffee { ... } class Latte @Inject constructor( private val Milk milk, private val Coffee coffee ) { ... }
若是構造函數使用了註解 @Inject
,Hilt 會根據您爲類型所定義的綁定來注入全部的參數。
⮕ 做爲字段注入
interface Milk { ... } interface Coffee { ... } @AndroidEntryPoint class LatteActivity : AppCompatActivity() { @Inject lateinit var milk: Milk @Inject lateinit var coffee: Coffee ... }
若是類是入口點,這裏特指使用了 @AndroidEntryPoint
註解的類 (後面章節會詳細介紹),那麼該類中全部包含 @Inject
註解的字段均會被注入。
使用 @Inject
註解的字段必須是 public 類型的。也能夠添加 lateinit
來避免字段空值,由於它們在注入以前的初始值就是 null
。
請注意做爲字段注入依賴項的場景僅僅適合類必須包含無參構造函數的狀況,好比 Activity
。在大多數場景下,您更應經過構造函數的參數來注入依賴項。
入口點
還記得我在上文裏提到,在不少狀況下,您的類會在經過依賴注入建立的同時包含被注入的依賴項。有些狀況下,您的類可能不是經過依賴項注入來建立,可是仍然會被注入依賴項。一個典型的例子就是 activity,它是由 Android 框架內部建立的,而不是由 Hilt 建立。
這些類屬於 Hilt 依賴圖譜的 入口點,並且 Hilt 須要知道這些類包含要注入的依賴。
⮕ Android 入口點
大部分入口點是所謂的 Android 入口點:
若是是 Android 入口點,請添加 @AndroidEntryPoint 註解。
@AndroidEntryPoint class LatteActivity : AppCompatActivity() { ... }
⮕ 其它入口點
Android 入口點對於大多數應用已經足夠,可是若是您使用了不含有 Dagger 的庫或者還沒有在 Hilt 中支持的 Android 組件,那麼您可能須要建立您本身的入口點來手動訪問 Hilt 依賴圖譜。詳情請查看 將任意類轉換爲入口點。
ViewModel
ViewModel 是一個特例: 由於框架會建立它們,它既不是被直接實例化的,也不是 Android 入口點。ViewModel 須要使用特殊的 @HiltViewModel
註解,當 ViewModel
經過 byViewModels()
建立的時候,該註解使 Hilt 可以向 ViewModel 注入依賴,和其它類的 @Inject
註解的原理類似。
interface Milk { ... } interface Coffee { ... } @HiltViewModel class LatteViewModel @Inject constructor( private val milk: Milk, private val coffee: Coffee ) : ViewModel() { ... } @AndroidEntryPoint class LatteActivity : AppCompatActivity() { private val viewModel: LatteViewModel by viewModels() ... }
若是您須要訪問 ViewModel
已緩存的狀態,能夠添加 @Assisted
註解,將 SavedStateHandle 做爲構造函數參數進行注入。
@HiltViewModel class LatteViewModel @Inject constructor( @Assisted private val savedState: SavedStateHandle, private val milk: Milk, private val coffee: Coffee ) : ViewModel() { ... }
要使用 @ViewModelInject
,您可能須要添加更多依賴。更多詳細內容請詳見 Hilt 和 Jetpack 集成指南。
組件
各個模塊都是安裝在 Hilt 組件 中的,經過 @InstallIn(<組件名>)
指定。模塊的組件主要用於防止意外將依賴注入到錯誤的位置。好比,@InstallIn(ServiceComponent.class)
能夠防止註解所修飾的模塊中的 binding 和 provider 被 activity 調用。
此外,binding 的做用域會被限制在組件所屬的整個模塊。也就是接下來咱們要講的...
做用域
默認狀況下,綁定都未被限定做用域。正如上面的示例,意味着每次注入 Milk
的時候,您均可以得到一個新的 OatMilk
實例。若是添加了 @ActivityScoped
註解,那麼您會將綁定的做用域限制到 ActivityComponent
。
@Module @InstallIn(ActivityComponent::class) abstract class MilkModule { @ActivityScoped @Binds abstract fun bindMilk(oatMilk: OatMilk): Milk }
如今您的模塊被限制做用域了,Hilt 在每一個 activity 實例中僅建立一個 OatMilk 實例。此外,OatMilk 實例會綁定到 activity 的生命週期中——當 activity 的 onCreate() 被調用的時候,它會被建立,而當 activity 的 onDestroy() 被調用的時候,它會被銷燬。
@AndroidEntryPoint class LatteActivity : AppCompatActivity() { @Inject lateinit var milk: Milk @Inject lateinit var moreMilk: Milk //這裏的實例和上面的相同 ... }
在本例中,milk
和 moreMilk
指向同一個 OatMilk
實例。然而,若是您有多個 LatteActivity
實例,它們會包含各自的 OatMilk
實例。
相應的,其它被注入到該 activity 的依賴,它們的做用域是一致的。所以它們也會引用到相同的 OatMilk
實例:
// Milk 實例的建立會在 Fridge 存在以前,由於它被綁定到了 activity 的生命週期中 class Fridge @Inject constructor(private val Milk milk) { ... } @AndroidEntryPoint class LatteActivity : AppCompatActivity() { // 下面四項共享了同一個 Milk 實例 @Inject lateinit var milk: Milk @Inject lateinit var moreMilk: Milk @Inject lateinit var fridge: Fridge @Inject lateinit var backupFridge: Fridge ... }
做用域依賴於您的模塊所安裝的組件,好比 @ActivityScoped
僅僅用於在 ActivityComponent
安裝的模塊內的綁定。
做用域一樣決定了注入實例的生命週期: 在本例中,被 Fridge
和 LatteActivity
使用的 Milk 的單獨實例會在 LatteActivity
的 onCreate()
被調用的時候被建立——而當 onDestroy() 被調用的時候被銷燬。這也意味着當配置發生改變的時候,Milk 不會 "倖免",由於配置發生改變的時候會調用 activity 的 onDestroy()。您能夠經過使用生命週期更長的做用域來避免該問題,好比使用 @ActivityRetainedScope
。
若是想要了解可用的做用域列表、相關的組件以及所遵循的生命週期,請參見 Hilt 組件。
Provider 注入
有些時候您但願可以更加直接地控制注入實例的建立。好比,您可能但願基於業務邏輯,注入某個類型的一個實例或者幾個實例。針對這樣的場景,您可使用 dagger.Provider:
class Spices @Inject constructor() { ... } class Latte @Inject constructor( private val spiceProvider: Provider<Spices> ) { fun addSpices() { val spices = spiceProvider.get()// 建立 Spices 的新實例 ... } }
provider 注入能夠忽略具體的依賴類型以及注入的方式。任何可被注入的內容都可以封裝在 Provider<...>
中來使用 provider 注入的方式。
依賴注入框架 (像 Dagger 和 Guice) 一般被用於大型且複雜的項目。而 Hilt 既容易上手,配置起來又很是簡單,同時做爲獨立的代碼包,還兼顧了 Dagger 中可被各類類型應用,不管代碼規模大小,都可兼容的強大特性。
若是您但願瞭解更多關於 Hilt 的內容、它的工做原理,以及其它對您來講有用的特性,請移步官方網站,瞭解更多詳細的介紹和參考文檔。