在 Android 和 Hilt 中限定作用域

image

將對(duì)象 A 的作用域限定到對(duì)象 B,指的是對(duì)象 B 的整個(gè)生命周期內(nèi)始終持有相同的 A 實(shí)例。當(dāng)涉及到 DI (依賴項(xiàng)注入) 時(shí),限定對(duì)象 A 的作用域?yàn)橐粋€(gè)容器,則意味著該容器在銷毀之前始終提供相同的 A 實(shí)例。

在 Hilt 中,您可以通過(guò)注解將類型的作用域限定在某些容器或組件內(nèi)。例如,您的應(yīng)用中有一個(gè)處理登錄和注銷的 UserManager 類型。您可以使用 @Singleton 注解將該類型的作用域限定為 ApplicationComponent (ApplicationComponent 是一個(gè)被整個(gè)應(yīng)用的生命周期管理的容器)。被限定作用域的類型在應(yīng)用組件中沿 組件層次結(jié)構(gòu) 向下傳遞: 在本案例中,相同的 UserManager 實(shí)例將被提供給層次結(jié)構(gòu)內(nèi)其余的 Hilt 組件。應(yīng)用中任何依賴于 UserManager 的類型都將獲得相同的實(shí)例。

注意 : 默認(rèn)情況下,Hilt 中的綁定都 未限定作用域 。這些綁定不屬于任何組件,并且可以在整個(gè)項(xiàng)目中被訪問(wèn)。每次被請(qǐng)求都會(huì)提供該類型的不同實(shí)例。當(dāng)您將綁定的作用域限定為某個(gè)組件時(shí),它會(huì)限制您使用該綁定的范圍以及該類型可以具有的依賴項(xiàng)。

在 Android 中,您不使用 DI 庫(kù)也可以通過(guò) Android Framework 來(lái)手動(dòng)限定作用域。讓我們看看如何手動(dòng)限定作用域,以及如何改用 Hilt 來(lái)限定作用域。最后,我們將比較使用 Android Framework 手動(dòng)限定作用域和使用 Hilt 限定作用域的區(qū)別。

在 Android 中限定作用域

看了上文的定義,您可能會(huì)有這樣的異議: 在某個(gè)特定類中使用一個(gè)類型的實(shí)例變量也可以做到限定該變量類型的作用域。沒(méi)錯(cuò)!不使用 DI 時(shí),您可以執(zhí)行如下操作:

class ExampleActivity : AppCompatActivity() {

  private val analyticsAdapter = AnalyticsAdapter()
  ...

}

analyticsAdapter 變量的作用域被限定為 MyActivity 的生命周期,這意味著只要 Activity 沒(méi)有被銷毀,該變量就是同一個(gè)實(shí)例。如果另一個(gè)類出于某種原因需要訪問(wèn)這個(gè)被限定了作用域的變量,每次訪問(wèn)也會(huì)獲得相同實(shí)例。當(dāng)新的 MyActivity 實(shí)例被創(chuàng)建時(shí) (如系統(tǒng)設(shè)置改變),一個(gè)新的 AnalyticsAdapter 實(shí)例將會(huì)被創(chuàng)建。

使用 Hilt,等效代碼如下:

@ActivityScoped
class AnalyticsAdapter @Inject constructor() { ... }

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

 @Inject lateinit var analyticsAdapter: AnalyticsAdapter

}

每次創(chuàng)建的 MyActivity 都會(huì)持有一個(gè) ActivityComponent DI 容器的新實(shí)例,在 Activity 被銷毀之前,該實(shí)例將向 組件層次結(jié)構(gòu) 下的依賴項(xiàng)提供相同的 AnalyticsAdapter 實(shí)例。

image

更改系統(tǒng)設(shè)置后,您將獲得一個(gè)新的 AnalyticsAdapter 和 MainActivity 實(shí)例

通過(guò) ViewModel 限定作用域

然而,我們可能希望 AnalyticsAdapter 可以在系統(tǒng)設(shè)置更改后留存!或者說(shuō),我們希望直到用戶離開(kāi) Activity 之前,都限定該實(shí)例的作用域?yàn)?Activity。

為此,您可以使用 組件架構(gòu)中的 ViewModel,因?yàn)樗梢栽谙到y(tǒng)設(shè)置更改后留存。

不使用依賴項(xiàng)注入時(shí),您可能有如下代碼:

class AnalyticsAdapter() { ... }

class ExampleViewModel() : ViewModel() {
  val analyticsAdapter = AnalyticsAdapter()
}

class ExampleActivity : AppCompatActivity() {

  private val viewModel: ExampleViewModel by viewModels()
  private val analyticsAdapter = viewModel.analyticsAdapter

}

通過(guò)這種方式,您將 AnalyticsAdapter 的作用域限定為 ViewModel。因?yàn)?Activity 具有 ViewModel 的訪問(wèn)權(quán)限,所以在該 Activity 中可以始終獲得相同的 AnalyticsAdapter 實(shí)例。

通過(guò)使用 Hilt,您可以通過(guò)限定 AnalyticsAdapter 的作用域?yàn)?ActivityRetainedComponent 來(lái)實(shí)現(xiàn)相同的行為,因?yàn)?ActivityRetainedComponent 也可以在系統(tǒng)設(shè)置更改后留存。

@ActivityRetainedScoped

class AnalyticsAdapter @Inject constructor() { ... }

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

@Inject lateinit var analyticsAdapter: AnalyticsAdapter

}
image

通過(guò)使用 ViewModel 或者 Hilt 中的 ActivityRetainedScope 注解,您可以在系統(tǒng)設(shè)置更改后獲得相同的實(shí)例

如果您希望在遵循良好的 DI 實(shí)踐的同時(shí),保留 ViewModel 用于處理視圖邏輯,您可以使用 @ViewModelInject 提供 ViewModel 的依賴項(xiàng),該注解的詳細(xì)描述請(qǐng)參見(jiàn): 文檔 | 使用 Hilt 注入 ViewModel 對(duì)象。這樣一來(lái),AnalyticsAdapter 的作用域就無(wú)需被限定為 ActivityRetainedComponent,因?yàn)榇藭r(shí)它的作用域被手動(dòng)限定為 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

}

我們剛才所看到的內(nèi)容,可以應(yīng)用到任何由 Android Framework 生命周期類管理的 Hilt 組件中。點(diǎn)擊查看 全部可用作用域。回到我們最初的示例,將作用域限定為 ApplicationComponent,等同于不使用 DI 框架時(shí)在 Application 類中持有該實(shí)例。

對(duì)比 Hilt 及 ViewModel 限定作用域

使用 Hilt 限定作用域,優(yōu)勢(shì)為您可在 Hilt 組件層次結(jié)構(gòu)中使用被限定的類型;而對(duì)于 ViewModel,則必須通過(guò) ViewModel 手動(dòng)訪問(wèn)被限定作用域的類型。

使用 ViewModel 限定作用域,優(yōu)勢(shì)為您可以在應(yīng)用中任何 LifecyclerOwner 對(duì)象中持有 ViewModel。例如,如果您使用了 Jetpack Navigation 庫(kù),則可以將 ViewModel 綁定到 NavGraph 上。

Hilt 提供的作用域數(shù)量有限??赡軟](méi)有符合您特定使用場(chǎng)景的作用域。例如嵌套 Fragment,對(duì)于這種情況,您可以退一步使用 ViewModel 限定作用域。

使用 Hilt 注入 ViewModel

如上文所述,您可以使用 @ViewModelInject 向 ViewModel 注入依賴項(xiàng)。其原理是這些綁定關(guān)系保存在 ActivityRetainedComponent 中,這也是為什么您只能注入未限定作用域的類型,或者是限定作用域?yàn)?ActivityRetainedComponent 以及 ApplicationComponent 的類型。

如果 Activity 或 Fragment 被 @AndroidEntryPoint 注解修飾,就可以通過(guò) getDefaultViewModelProviderFactory() 方法獲取 Hilt 生成的 ViewModel 工廠了。由于可以在 ViewModelProvider 中使用這些 ViewModel 工廠,使您獲取 ViewModel 的方式變得更加靈活。例如: 將作用域限定為 BackStackEntry 的 ViewModel。

限定作用域會(huì)有一些代價(jià),因?yàn)樘峁┑膶?duì)象在持有者被銷毀之前將一直保留在內(nèi)存中。請(qǐng)?jiān)趹?yīng)用中慎重地考慮使用限定作用域的對(duì)象。如果對(duì)象的內(nèi)部狀態(tài)要求使用同一實(shí)例,對(duì)象需要同步,或者對(duì)象的創(chuàng)建成本很高,那么限定作用域是恰當(dāng)?shù)淖龇ā?/p>

當(dāng)然,當(dāng)您需要限定作用域時(shí),您可以使用 Hilt 中的作用域注解,也可以直接使用 Android Framework。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容