中國移動家庭運營中心融合通信 閆同學
一、什麼是依賴注入?依賴注入是一個聽起來很「高大上」的概念,但是其實很簡單。本來我要接受各種參數自己構造一個對象,現在只接受一個已經實例化的對象直接作為參數。
class A(a:Int,b:Int){ val B = B(a,b)}class A(val b:B)沒錯,依賴注入的概念就是這麼簡單。
所謂依賴注入,其實就是之前在類的內部自己構造成員變量,現在放到了外部構造成員變量。
而Hilt,其實就是Android基於Dragger開發的一套Android上的依賴注入框架而已。
二、為什麼要用依賴注入當然是降低代碼的耦合關係了!
打個比方,我要買一把錘子,可以有以下的三種辦法
可以找生產錘子的工廠生產,向工廠購買。這對應了java中的工廠模式打電話給賣錘子的商店,讓人把錘子送貨上門,這就是依賴注入。採用依賴注入之後,「我」不需要關心錘子是哪裡生產的,也不需要關心是錘子是怎麼生產的,我只需要拿來直接使用就好。
這樣就解除了調用者(我)和被調用者(錘子)的耦合關係。這對於一個大型項目而言,意義重大。
三、Hilt怎麼用1. gradle配置接入在項目根目錄的build.gradle添加如下代碼
buildscript { ... dependencies { ... classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha' }}PS:項目根目錄加這個的意義在於「聲明」編譯過程中所需要的插件,對應後面的module中的apply plugin
在module的build.gradle中添加
...apply plugin: 'kotlin-kapt'apply plugin: 'dagger.hilt.android.plugin'
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }}
dependencies { implementation "com.google.dagger:hilt-android:2.28-alpha" kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"}然後這樣就基本配置完成,如果gradle sync有一些錯誤的話,如提示gradle版本過低之類的,按照提示升級就好。
2. application配置在Applicaiton上面必須使用@HiltAndroidApp ,表示開啟依賴注入
@HiltAndroidAppclass ExampleApplication : Application() { ... }3. 組件及組件的生命周期在介紹Hilt正式使用之前,需要先介紹組件的概念。
PS:這一部分有一點晦澀難懂,可以只有一個大概印象,後面用著用著就明白了。
Hilt中默認並不是所有的類都可以直接使用依賴注入的,對於每一個可以使用依賴注入的Android類,都有一個關聯的Hilt組件。每個組件負責實際的注入工作。
對應關係如下
Application對應ApplicationComponent組件ViewMode對應ActivityRetainedComponent組件Activity對應ActivityComponent組件Fragment對應FragmentComponent組件帶@WithFragmentBindings的View對應ViewWithFragmentComponent組件Service對應ServiceComponent組件注意:Hilt不會為廣播接收器生成組件,因為Hilt直接從ApplicationComponent注入廣播接收器
組件的生命周期
各個組件都有自己的生命周期,在創建時機中自動創建注入,在銷毀時機中銷毀生成的組件實例。
各個組件的生命周期對應如下
組件創建時機銷毀時機ApplicationComponentApplication#onCreateApplication#onDestoryActivityRetainedComponentActivity#onCreateActivity#onDestoryActivityComponentActivity#onCreateActivity#onDestoryFragmentComponentFragment#onAttachFragment#onDestoryViewComponentView#super視圖銷毀時ViewWithFragmentComponentView#super視圖銷毀時ServiceComponentServer#onCreateService#onDestory
注意順便說下,ActivityRetainedComponent與ActivityComponent有什麼不同?ActivityRetainedComponent在配置更改後依然存在,如橫豎屏切換。他是在第一次onCreate的時候創建,在最後一次onDestory中銷毀,這個和ViewModel的特性保持一致。
4. Hilt的各個註解的用法終於要講到怎麼用了
@AndroidEntryPoint表示這個類可以使用注入項。
@AndroidEntryPointclass ExampleActivity : AppCompatActivity() { ... }目前AndroidEntryPoint可以在以下類中使用
其中,如果Fragment使用,那麼包含該Fragment的Activity也必須使用該註解,如果View使用,那麼使用該View的Fragment和Activity也必須使用該註解
@Inject該註解有兩個作用
有點繞,直接看代碼
class AnalyticsAdapter @Inject constructor() { ... }
@AndroidEntryPointclass ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter ...}那analytics是在Activity生命周期的哪一步被注入呢?可以參考組件那一章節,是在onCreate中注入的。
@ViewModelInject與Inject類似,但是專用於ViewModel的構造函數上,如
@ActivityRetainedScopedclass ZdmViewModel @ViewModelInject constructor(private val adapter:AnalyticsAdapter,@Assisted private val state:SavedStateHandle) : BaseModel(), LifecycleObserver {}@ApplicationContext @ActivityContext直接拿到Application和Activity的Context實例,注意這裡的Context只能作為Context使用,而不是Activity實例,至於為什麼,有機會再介紹。用法如下
@Singletonclass DefaultConfigService @Inject constructor(@ApplicationContext context: Context) : BaseConfigService(context) {}@Singleton一般情況下來說,類成員變量使用@Inject 之後,每次都new一個實例出來(ViewModel例外,具體的先不講了,以後另開文章講)。如果想使用單例模式,可以在類上使用@Singleton, 標明這個類是單例模式
@Singletonclass DefaultConfigService @Inject constructor(@ApplicationContext context: Context) : BaseConfigService(context) {}好了,以上都是一些基礎的用法,會了這些已經會對平時的工作有幫助了,下面講一些略微高級的用法。這是一道分割線
@Moudle @InstallIn @Provider如果我想用@Inject 注入一個三方SDK的類實例,應該怎麼辦呢?
因為我們知道使用@Inject 的前提是該類的構造函數加上@Inject,所以沒有辦法直接用,那麼可以這樣。其中@Module生命模塊,@InstallIn是用於告知Hilt這個模塊將安裝在哪個Android類中(這裡我理解和生命周期息息相關)
@Module@InstallIn(ApplicationComponent::class)object AppModule { @Singleton @Provides fun gson(): GsonManager { return GsonManager.instance() }}@BindsBinds的作用是將接口與實現類綁定起來。這樣後面使用@Inject進行注入的時候,聲明該接口即可,而不用關心接口對應哪個類實現,進一步降低了耦合程度
@InstallIn(ApplicationComponent::class)@Moduleabstract class AppBindModule { @Binds abstract fun bindAccountService(defaultAccountService: DefaultAccountService): AccountService}@EntryPoint從註解名就可以看出,@AndroidEntryPoint類似, 只不過@AndroidEntryPoint只提供了系統默認的幾個類的支持, 如果想讓自己實現的類中也可以實現註解, 可以用@EntryPoint, 只不過要稍微麻煩點, 如下。
下面這段代碼中,AccountService是採用Inject依賴注入的,但是ServicesManager因為不能用@AndroidEntryPoint來註解,所以他如果想要獲取AccountService實例,可以用下面辦法
class ServicesManager private constructor(context: Context) { ... @EntryPoint @InstallIn(ApplicationComponent::class) interface AccountServiceEntryPoint { fun accountService(): AccountService } companion object { const val ACCOUNT_SERVICE = "account"
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { val manager = ServicesManager(LibApplication.instance()) val accountServiceEntryPoint = EntryPointAccessors.fromApplication(LibApplication.instance(), AccountServiceEntryPoint::class.java) manager.register(ACCOUNT_SERVICE, accountServiceEntryPoint.accountService()) manager } }Hilt的使用場景筆者能想到兩個使用場景。
工具類毫無疑問,工具類很適合幹這個事情了。但是感覺有點多餘,工具類一般邏輯不會太複雜,使用Hilt有點牛刀殺雞的感覺。
ViewModel相關場景這個重點介紹下。在Jetpack中,Google已經推薦使用ViewModel來管理所有的數據,加上LiveData,形成了ViewModel+LiveData+Framgent+Activity+Presenter(可選)一套非常成熟的實現思路,並且,Google在Kotlin中有兩個ViewModel的非常簡單的初始化方法
val viewModel:MyViewModel by viewModels()
val viewModel:MyViewModel by activityViewModels()如此,Activity可以與Fragment很方便的共享數據,如果再加上LiveData,可以達到Fragment與Activity通信的目的。
在這一點上,Hilt只不過換了一種寫法而已。
@Injectlateinit var viewModel:MyViewModel但是如果加上View呢?
有一些略微複雜的界面,有人喜歡抽成一個ViewGroup來做,這個ViewGroup只是一些View的組合而已,不需要重寫onMesure,onLayout,onDraw方法,但是因為有一些獨立的業務邏輯,所以抽成一個ViewGroup會比較方便,例如搜索控制項等。
這部分View就沒有辦法很好的與Activity或者Fragment進行通信,只能用set或者callback等命令式編程的思想來做通信,複雜且囉嗦。
這個時候就可以用到了Hilt。Hilt框架也可以在View中使用@Inject註解來獲取ViewModel。
算作一個還算不錯的實現思路。