將對象 A 的做用域限定到對象 B,指的是對象 B 的整個生命週期內始終持有相同的 A 實例。當涉及到 DI (依賴項注入) 時,限定對象 A 的做用域爲一個容器,則意味着該容器在銷燬以前始終提供相同的 A 實例。android
在 Hilt 中,您能夠經過註解將類型的做用域限定在某些容器或組件內。例如,您的應用中有一個處理登陸和註銷的 UserManager
類型。您可使用 @Singleton
註解將該類型的做用域限定爲 ApplicationComponent
(ApplicationComponent
是一個被整個應用的生命週期管理的容器)。被限定做用域的類型在應用組件中沿 組件層次結構 向下傳遞: 在本案例中,相同的 UserManager
實例將被提供給層次結構內其他的 Hilt 組件。應用中任何依賴於 UserManager
的類型都將得到相同的實例。架構
注意 : 默認狀況下,Hilt 中的綁定都 未限定做用域 。這些綁定不屬於任何組件,而且能夠在整個項目中被訪問。每次被請求都會提供該類型的不一樣實例。當您將綁定的做用域限定爲某個組件時,它會限制您使用該綁定的範圍以及該類型能夠具備的依賴項。
在 Android 中,您不使用 DI 庫也能夠經過 Android Framework 來手動限定做用域。讓咱們看看如何手動限定做用域,以及如何改用 Hilt 來限定做用域。最後,咱們將比較使用 Android Framework 手動限定做用域和使用 Hilt 限定做用域的區別。框架
看了上文的定義,您可能會有這樣的異議: 在某個特定類中使用一個類型的實例變量也能夠作到限定該變量類型的做用域。沒錯!不使用 DI 時,您能夠執行以下操做:ide
class ExampleActivity : AppCompatActivity() { private val analyticsAdapter = AnalyticsAdapter() ... }
analyticsAdapter
變量的做用域被限定爲 MyActivity
的生命週期,這意味着只要 Activity 沒有被銷燬,該變量就是同一個實例。若是另外一個類出於某種緣由須要訪問這個被限定了做用域的變量,每次訪問也會得到相同實例。當新的 MyActivity
實例被建立時 (如系統設置改變),一個新的 AnalyticsAdapter
實例將會被建立。ui
使用 Hilt,等效代碼以下:google
@ActivityScoped class AnalyticsAdapter @Inject constructor() { ... } @AndroidEntryPoint class ExampleActivity : AppCompatActivity() { @Inject lateinit var analyticsAdapter: AnalyticsAdapter }
每次建立的 MyActivity
都會持有一個 ActivityComponent
DI 容器的新實例,在 Activity 被銷燬以前,該實例將向 組件層次結構 下的依賴項提供相同的 AnalyticsAdapter
實例。spa
更改系統設置後,您將得到一個新的 AnalyticsAdapter 和 MainActivity 實例code
然而,咱們可能但願 AnalyticsAdapter
能夠在系統設置更改後留存!或者說,咱們但願直到用戶離開 Activity 以前,都限定該實例的做用域爲 Activity。component
爲此,您可使用 組件架構中的 ViewModel,由於它能夠在系統設置更改後留存。對象
不使用依賴項注入時,您可能有以下代碼:
class AnalyticsAdapter() { ... } class ExampleViewModel() : ViewModel() { val analyticsAdapter = AnalyticsAdapter() } class ExampleActivity : AppCompatActivity() { private val viewModel: ExampleViewModel by viewModels() private val analyticsAdapter = viewModel.analyticsAdapter }
經過這種方式,您將 AnalyticsAdapter
的做用域限定爲 ViewModel。由於 Activity 具備 ViewModel 的訪問權限,因此在該 Activity 中能夠始終得到相同的 AnalyticsAdapter
實例。
經過使用 Hilt,您能夠經過限定 AnalyticsAdapter
的做用域爲 ActivityRetainedComponent
來實現相同的行爲,由於 ActivityRetainedComponent
也能夠在系統設置更改後留存。
@ActivityRetainedScoped class AnalyticsAdapter @Inject constructor() { ... } @AndroidEntryPoint class ExampleActivity : AppCompatActivity() { @Inject lateinit var analyticsAdapter: AnalyticsAdapter }
經過使用 ViewModel 或者 Hilt 中的 ActivityRetainedScope 註解,您能夠在系統設置更改後得到相同的實例
若是您但願在遵循良好的 DI 實踐的同時,保留 ViewModel 用於處理視圖邏輯,您可使用 @ViewModelInject 提供 ViewModel 的依賴項,該註解的詳細描述請參見: 文檔 | 使用 Hilt 注入 ViewModel 對象。這樣一來,AnalyticsAdapter
的做用域就無需被限定爲 ActivityRetainedComponent
,由於此時它的做用域被手動限定爲 ViewModel:
class AnalyticsAdapter @Inject constructor() { ... } class ExampleViewModel @ViewModelInject constructor( private val analyticsAdapter: AnalyticsAdapter ) : ViewModel() { ... } @AndroidEntryPoint class ExampleActivity : AppCompatActivity() { private val viewModel: ExampleViewModel by viewModels() private val analyticsAdapter = viewModel.analyticsAdapter }
咱們剛纔所看到的內容,能夠應用到任何由 Android Framework 生命週期類管理的 Hilt 組件中。點擊查看 所有可用做用域。回到咱們最初的示例,將做用域限定爲 ApplicationComponent
,等同於不使用 DI 框架時在 Application 類中持有該實例。
使用 Hilt 限定做用域,優點爲您可在 Hilt 組件層次結構中使用被限定的類型;而對於 ViewModel,則必須經過 ViewModel 手動訪問被限定做用域的類型。
使用 ViewModel 限定做用域,優點爲您能夠在應用中任何 LifecyclerOwner 對象中持有 ViewModel。例如,若是您使用了 Jetpack Navigation 庫,則能夠將 ViewModel 綁定到 NavGraph 上。
Hilt 提供的做用域數量有限。可能沒有符合您特定使用場景的做用域。例如嵌套 Fragment,對於這種狀況,您能夠退一步使用 ViewModel 限定做用域。
如上文所述,您可使用 @ViewModelInject
向 ViewModel 注入依賴項。其原理是這些綁定關係保存在 ActivityRetainedComponent
中,這也是爲何您只能注入未限定做用域的類型,或者是限定做用域爲 ActivityRetainedComponent
以及 ApplicationComponent
的類型。
若是 Activity 或 Fragment 被 @AndroidEntryPoint
註解修飾,就能夠經過 getDefaultViewModelProviderFactory()
方法獲取 Hilt 生成的 ViewModel 工廠了。因爲能夠在 ViewModelProvider
中使用這些 ViewModel 工廠,使您獲取 ViewModel 的方式變得更加靈活。例如: 將做用域限定爲 BackStackEntry
的 ViewModel。
限定做用域會有一些代價,由於提供的對象在持有者被銷燬以前將一直保留在內存中。請在應用中慎重地考慮使用限定做用域的對象。若是對象的內部狀態要求使用同一實例,對象須要同步,或者對象的建立成本很高,那麼限定做用域是恰當的作法。
固然,當您須要限定做用域時,您可使用 Hilt 中的做用域註解,也能夠直接使用 Android Framework。