在上一篇文章 Jetpack 新成員 Hilt 實踐(一)啓程過坑記 分別介紹了 Hilt 的經常使用註解、以及在實踐過程當中遇到的一些坑,Hilt 如何 Android 框架類進行綁定,以及他們的生命週期,這篇文章繼續講解 Hilt 的用法,代碼已經所有上傳到 GitHub:HiltWithAppStartupSimple 若是對你有幫助,請在倉庫右上角幫我點個贊。java
Hilt 涉及的知識點有點多並且比較難理解,在看本篇文章以前必定要先看一下以前的文章 Jetpack 新成員 Hilt 實踐(一),爲了節省篇幅,這篇文章將會忽略 Hilt 環境配置的過程等等以前文章已經介紹過的內容。android
另外若是想了解 Google 新推出的另外兩個 Jetpack 新成員 App Startup
和 Paging3
的實踐與原理,能夠點擊下方連接前去查看。git
經過這篇文章你將學習到如下內容:github
什麼是註解?面試
@assist
註解和 SavedStateHandle
如何使用?算法
如何使用 @Binds
註解實現接口注入?數據庫
@Binds
和 @Provides
的區別?編程
限定符 @Qualifier
的使用?api
@qualifers
@qualifers
組件做用域 @scopes
如何使用?數組
如何在 Hilt
不支持的類中執行依賴注入?
Hilt
如何和 ContentProvider
一塊兒使用?Hilt
如何和 App Startup
一塊兒使用?Hilt 是基於 Dagger 基礎上進行開發的,若是瞭解 Dagger 朋友們,應該會感受它們很像,可是與 Dagger 不一樣的是, Hilt 集成了 Jetpack 庫和 Android 框架類,並刪除了大部分模板代碼,讓開發者只須要關注如何進行綁定,而不須要管理全部 Dagger 配置的問題。
在上篇文章已經介紹過, Hilt 如何 Android 框架類進行綁定,以及他們的生命週期,這篇文章將介紹 Hilt 如何和 Jetpack 組件(ViewModel、App Startup)一塊兒綁定,在開始介紹以前咱們先來了解一下什麼是註解。
以前有小夥伴在 WX 上問過我,對註解不太瞭解,因此想在這裏想簡單的提一下。
註解是放在 Java 源碼的類、方法、字段、參數前的一種特殊「註釋」,註解則能夠被編譯器打包進入 class 文件,能夠在編譯,類加載,運行時被讀取。
常見的三個註解 @Override
、@Deprecated
、@SuppressWarnings
@Override
: 確保子類重寫了父類的方法,編譯器會檢查該方法是否正確地實現。@Deprecated
:表示某個類、方法已通過時,編譯器會檢查,若是使用了過期的方法,會給出提示。@SuppressWarnings
:編譯器會忽略產生的警告。在上一篇文章只是簡單的介紹了 Hilt 如何和 ViewModel 一塊兒使用,咱們繼續介紹 ViewModel 的另一個重要的參數 SavedStateHandle
,首先須要添加依賴。
在 App 模塊中的 build.gradle
文件中添加如下代碼。
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
複製代碼
koltin 使用 kapt, Java 使用 annotationProcessor。
注意: 這個是在 Google 文檔上沒有提到的,若是使用的是 kotlin 的話須要額外在 App 模塊中的 build.gradle 文件中添加如下代碼,不然調用 by viewModels()
會編譯不過。
// For Kotlin projects
kotlinOptions {
jvmTarget = "1.8"
}
複製代碼
在 ViewModel 的構造函數中使用 @ViewModelInject
註解提供一個 ViewModel,若是須要用到 SavedStateHandle
,須要使用 @assist
註解添加 SavedStateHandle
依賴項,代碼以下所示。
class HiltViewModel @ViewModelInject constructor(
private val tasksRepository: Repository,
//SavedStateHandle 用於進程被終止時,保存和恢復數據
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// getLiveData 方法會取得一個與 key 相關聯的 MutableLiveData
// 當與 key 相對應的 value 改變時 MutableLiveData 也會更新。
private val _userId: MutableLiveData<String> = savedStateHandle.getLiveData(USER_KEY)
// 對外暴露不可變的 LiveData
val userId: LiveData<String> = _userId
companion object {
private val USER_KEY = "userId"
}
}
複製代碼
將用戶的 userId
存儲在 SavedStateHandle
中,當進程被終止時保存和恢復對應的數據。
SavedStateHandle 是什麼?SavedStateHandle 爲了解決什麼問題?
Activity
和 Fragment
一般會在下面三種狀況下被銷燬(如下內容來自 Google):
finish()
方法)。對應 Activity 實例被永久關閉。ViewModel 會幫您處理第二種狀況,由於在這種狀況下 ViewModel 沒有被銷燬,而在第三種狀況下,ViewModel 被銷燬了, 當進程在後臺被殺死後,則須要使用 onSaveInstanceState()
做爲備用保存數據的方式。
SavedStateHandle
的出現就是爲了解決 App 進程終止保存和恢復數據問題,ViewModel 不須要向 Activity 發送和接收狀態。相反的,如今能夠在 ViewModel 中處理保存和恢復數據。
SavedStateHandle
相似於一個 Bundle,它是數據的鍵-值映射,這個 SavedStateHandle
包含在 ViewModel 中,它在後臺進程終止時仍然存在,之前保存在 onSaveInstanceState()
中的任何數據如今均可以保存在 SavedStateHandle
中。
注入接口實例有兩種方式分別使用註解 @Binds
和 @Provides
,@Provides
的方式在上一篇文章 Jetpack 新成員 Hilt 實踐(一)啓程過坑記 Hilt 如何和 Room 一塊兒使用 和 Hilt 如何和第三方組件一塊兒使用 都有介紹,這裏咱們來介紹如何使用註解 @Binds
。
interface WorkService {
fun init()
}
/**
* 注入構造函數,由於 Hilt 須要知道如何提供 WorkServiceImpl 的實例
*/
class WorkServiceImpl @Inject constructor() :
WorkService {
override fun init() {
Log.e(TAG, " I am an WorkServiceImpl")
}
}
@Module
@InstallIn(ApplicationComponent::class)
// 這裏使用了 ActivityComponent,所以 WorkServiceModule 綁定到 ActivityComponent 的生命週期。
abstract class WorkServiceModule {
/**
* @Binds 註解告訴 Hilt 須要提供接口實例時使用哪一個實現
*
* bindAnalyticsService 函數須要爲 Hilt 提供瞭如下信息
* 1. 函數返回類型告訴 Hilt 提供了哪一個接口的實例
* 2. 函數參數告訴 Hilt 提供哪一個實現
*/
@Binds
abstract fun bindAnalyticsService(
workServiceImpl: WorkServiceImpl
): WorkService
}
複製代碼
使用註解 @Binds
時,須要提供如下兩個信息:
註解 @Binds 和 註解 @Provides 的區別?
@Binds
:須要在方法參數裏面明確指明接口的實現類。@Provides
:不須要在方法參數裏面明確指明接口的實現類,由第三方框架實現,一般用於和第三方框架進行綁定(Retrofit
、Room
等等)// 有本身的接口實現
@Binds
abstract fun bindAnalyticsService(
workServiceImpl: WorkServiceImpl
): WorkService
// 沒有本身的接口實現
@Provides
fun providePersonDao(application: Application): PersonDao {
return Room
.databaseBuilder(application, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build().personDao()
}
@Provides
fun provideGitHubService(): GitHubService {
return Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(GitHubService::class.java)
}
複製代碼
來自 Google:@Qualifier
是一種註解,當類型定義了多個綁定時,使用它來標識該類型的特定綁定。
換句話說 @Qualifier
聲明同一個類型,能夠在多處進行綁定,我將限定符分爲兩種。
咱們先用註解 @Qualifier
聲明兩個不一樣的實現。
// 爲每一個聲明的限定符,提供對應的類型實例,和 @Binds 或者 @Provides 一塊兒使用
@Qualifier
// @Retention 定義了註解的生命週期,對應三個值(SOURCE、BINARY、RUNTIME)
@Retention(AnnotationRetention.BINARY)
annotation class RemoteTasksDataSource // 註解的名字,後面直接使用它
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class LocalTasksDataSource
複製代碼
@Qualifier
:爲每一個聲明的限定符,提供對應的類型實例,和 @Binds
或者 @Provides
一塊兒使用
@Retention
:定義了註解的生命週期,對應三個值(SOURCE、BINARY、RUNTIME)
AnnotationRetention.SOURCE
:僅編譯期,不存儲在二進制輸出中。AnnotationRetention.BINARY
:存儲在二進制輸出中,但對反射不可見。AnnotationRetention.RUNTIME
:存儲在二進制輸出中,對反射可見。一般咱們自定義的註解都是 RUNTIME,因此務必要加上@Retention(RetentionPolicy.RUNTIME)
這個註解
來看一下 @Qualifier
和 @Provides
一塊兒使用的例子,定義了兩個方法,具備相同的返回類型,可是實現不一樣,限定符將它們標記爲兩個不一樣的綁定。
@Singleton
@RemoteTasksDataSource
@Provides
fun provideTasksRemoteDataSource(): DataSource { // 返回值相同
return RemoteDataSource() // 不一樣的實現
}
@Singleton
@LocalTasksDataSource
@Provides
fun provideTaskLocalDataSource(appDatabase: AppDataBase): DataSource { // 返回值相同
return LocalDataSource(appDatabase.personDao()) // 不一樣的實現
}
複製代碼
當咱們聲明完 @Qualifier
註解以後,就可使用聲明的兩個 @Qualifier
,來看個例子,定義一個 Repository 構造方法裏面傳入用 @Qualifier
註解聲明的兩個不一樣實現。
@Singleton
@Provides
fun provideTasksRepository(
@LocalTasksDataSource localDataSource: DataSource,
@RemoteTasksDataSource remoteDataSource: DataSource
): Repository {
return TasksRepository(
localDataSource,
remoteDataSource
)
}
複製代碼
provideTasksRepository 方法內,傳入的參數都是 DataSource,可是前面用 @Qualifier
註解聲明瞭它們不一樣的實現。
Hilt 提供了一些預約義限定符,例如你可能在不一樣的狀況下須要不一樣的 Context
(Appliction
、Activity
)Hilt 提供了 @ApplicationContext
和 @ActivityContext
兩種限定符。
class HiltViewModel @ViewModelInject constructor(
@ApplicationContext appContext: Context,
@ActivityContext actContext: Context,
private val tasksRepository: Repository,
@Assisted private val savedStateHandle: SavedStateHandle
)
複製代碼
默認狀況下,Hilt 中的全部綁定都是無做用域的,這意味着每次應用程序請求綁定時,Hilt 都會建立一個所需類型的新實例。
@scopes
的做用在指定做用域範圍內(Application
、Activity
等等) 提供相同的實例。
Hilt 還容許將綁定的做用域限定到特定組件,Hilt 只爲綁定做用域到的組件的每一個實例建立一次範圍綁定,全部綁定請求共享同一個實例,咱們來看一例子。
@Singleton
class HiltSimple @Inject constructor() {
}
複製代碼
HiltSimple
用 @Singleton
聲明瞭其做用域,那麼在 Application 範圍內提供相同的實例,代碼以下所示,你們能夠運行 Demo 看一下輸出結果。
MainActivity: com.hi.dhl.hilt.appstartup.di.HiltSimple@8f75417
HitAppCompatActivity: com.hi.dhl.hilt.appstartup.di.HiltSimple@8f75417
複製代碼
注意:綁定組件範圍可能很是的昂貴,由於提供的對象會保留在內存中,直到該組件被銷燬,應該儘可能減小在應用程序中使用綁定組件範圍,對於要求在必定範圍內使用同一實例的綁定,或者對於建立成本高昂的綁定,使用組件範圍的綁定是合適的。
下表列出了每一個生成組件的 scope 註解對應的範圍。
Android class | Generated component | Scope |
---|---|---|
Application | ApplicationComponent | @Singleton |
View Model | ActivityRetainedComponent | @ActivityRetainedScope |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
View annotated with @WithFragmentBindings | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
Hilt 支持最多見的 Android 類 Application
、Activity
、Fragment
、View
、Service
、BroadcastReceiver
等等,可是您可能須要在 Hilt 不支持的類中執行依賴注入,在這種狀況下可使用 @EntryPoint
註解進行建立,Hilt 會提供相應的依賴。
@EntryPoint
:可使用 @EntryPoint
註解建立入口點,@EntryPoint
容許 Hilt 使用 Hilt 沒法在依賴中提供依賴的對象。
例如 Hilt 不支持 ContentProvider
,若是你在想在 ContentProvider
中獲取 Hilt 提供的依賴,你能夠定義一個接口,並添加 @EntryPoint
註解,而後添加 @InstallIn
註解指定 module 的範圍,代碼以下所示。
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface InitializerEntryPoint {
fun injectWorkService(): WorkService
companion object {
fun resolve(context: Context): InitializerEntryPoint {
val appContext = context.applicationContext ?: throw IllegalStateException()
return EntryPointAccessors.fromApplication(
appContext,
InitializerEntryPoint::class.java
)
}
}
}
複製代碼
使用 EntryPointAccessors
提供四個靜態方法進行訪問,分別是 fromActivity
、fromApplication
、fromFragment
、fromView
等等
EntryPointAccessors 提供四個靜態方法,第一個參數是 @EntryPoint
接口上 @InstallIn
註解指定 module 的範圍,咱們在接口 InitializerEntryPoint
用 @InstallIn
註解指定 module 的範圍是 ApplicationComponent
,因此咱們應該使用 EntryPointAccessors
提供的靜態方法 fromApplication
。
class WorkContentProvider : ContentProvider() {
override fun onCreate(): Boolean {
context?.run {
val service = InitializerEntryPoint.resolve(this).injectWorkService()
Log.e(TAG, "WorkContentProvider ${service.init()}")
}
return true
}
......
}
複製代碼
在 ContentProvider
中調用 EntryPointAccessors
類中的 fromApplication
方法就能夠獲取到 Hit 提供的依賴。
App Startup 會默認提供一個 InitializationProvider
,InitializationProvider
繼承 ContentProvider
,那麼 Hilt 在 App Startup 中使用的方式和 ContentProvider
同樣。
class AppInitializer : Initializer<Unit> {
override fun create(context: Context): Unit {
val service = InitializerEntryPoint.resolve(context).injectWorkService()
Log.e(TAG, "AppInitializer ${service.init()}")
return Unit
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> =
mutableListOf()
}
複製代碼
經過調用 EntryPointAccessors 的靜態方法,獲取到 Hit 提供的依賴,關於 App Startup 如何使用能夠查看這篇文章 Jetpack 最新成員 AndroidX App Startup 實踐以及原理分析
到這裏關於 Hilt 的註解使用都介紹完了,代碼已經所有上傳到了 GitHub:HiltWithAppStartupSimple。
HiltWithAppStartupSimple
包含了本篇文章和 Jetpack 新成員 Hilt 實踐(一)啓程過坑記 文章中使用的案例,若是以前沒有看過能夠先去了解一下,以後看代碼會更加的清楚。
Hilt 是基於 Dagger 基礎上進行開發的,入門要比 Dagger 簡單不少,不須要去管理全部的 Dagger 的配置問題,可是其入門的門檻仍是很高的,尤爲是 Hilt 的註解,須要瞭解其每一個註解的含義才能正確的使用,避免資源的浪費。
這篇文章和以前 Jetpack 新成員 Hilt 實踐(一)啓程過坑記 的文章其中不少案例都從新去設計了,由於 Google 的提供的案例,確實很難讓人理解,但願這兩篇文章能夠幫助小夥伴們快速入門 Hilt,後面還會有更多實戰案例。
計劃創建一個最全、最新的 AndroidX Jetpack 相關組件的實戰項目 以及 相關組件原理分析文章,正在逐漸增長 Jetpack 新成員,倉庫持續更新,能夠前去查看:AndroidX-Jetpack-Practice, 若是這個倉庫對你有幫助,請在倉庫右上角幫我點個贊,後面我會陸續完成更多 Jetpack 新成員的項目實踐。
致力於分享一系列 Android 系統源碼、逆向分析、算法、翻譯、Jetpack 源碼相關的文章,正在努力寫出更好的文章,若是這篇文章對你有幫助給個 star,一塊兒來學習,期待與你一塊兒成長。
因爲 LeetCode 的題庫龐大,每一個分類都能篩選出數百道題,因爲每一個人的精力有限,不可能刷完全部題目,所以我按照經典類型題目去分類、和題目的難易程度去排序。
每道題目都會用 Java 和 kotlin 去實現,而且每道題目都有解題思路、時間複雜度和空間複雜度,若是你同我同樣喜歡算法、LeetCode,能夠關注我 GitHub 上的 LeetCode 題解:Leetcode-Solutions-with-Java-And-Kotlin,一塊兒來學習,期待與你一塊兒成長。
正在寫一系列的 Android 10 源碼分析的文章,瞭解系統源碼,不只有助於分析問題,在面試過程當中,對咱們也是很是有幫助的,若是你同我同樣喜歡研究 Android 源碼,能夠關注我 GitHub 上的 Android10-Source-Analysis,文章都會同步到這個倉庫。
目前正在整理和翻譯一系列精選國外的技術文章,不只僅是翻譯,不少優秀的英文技術文章提供了很好思路和方法,每篇文章都會有譯者思考部分,對原文的更加深刻的解讀,能夠關注我 GitHub 上的 Technical-Article-Translation,文章都會同步到這個倉庫。