Dagger Hilt (這名字起的溜...........)html
官方描述其設計目的:java
簡單說就是Dagger Android的瘦身包,使依賴注入在Android開發中標準化、簡單化。android
首先在項目級別的build.gradle文件中添加如下內容,這將使咱們可以訪問hilt gradle插件:git
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
複製代碼
而後在應用程序級別的build.gradle文件並應用此插件:github
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
複製代碼
最後,在應用程序級別build.gradle文件中添加所需的hilt依賴項:segmentfault
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
複製代碼
這樣Hilt就可使用了。api
按照官方要求,首先須要在自定義的Application類中添加@HiltAndroidApp
註解:緩存
@HiltAndroidApp
class APP:Application()
複製代碼
這有什麼做用?如下來自官方描述:bash
All apps using Hilt must contain an Application
class annotated with @HiltAndroidApp
. @HiltAndroidApp
kicks off the code generation of the Hilt components and also generates a base class for your application that uses those generated components. Because the code generation needs access to all of your modules, the target that compiles your Application
class also needs to have all of your Dagger modules in its transitive dependencies.app
Just like other Hilt Android entry points, Applications are members injected as well. This means you can use injected fields in the Application after super.onCreate()
has been called.
在Daager2
中,須要Application繼承DaggerApplication
,而且還須要建立Application的Module
。 這裏只須要使用@HiltAndroidApp
的註解就能夠完成對Application的依賴注入,由Hilt gradle插件生成對應的文件
構建後咱們看到在 app/build/generated/source/kapt/debug/
目錄下生成了一個Hilt_APP
的抽象類:
/**
* A generated base class to be extended by the @dagger.hilt.android.HiltAndroidApp annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation. */
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator")
public abstract class Hilt_APP extends Application implements GeneratedComponentManager<Object> {
private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
@Override
public Object get() {
return DaggerAPP_HiltComponents_ApplicationC.builder()
.applicationContextModule(new ApplicationContextModule(Hilt_APP.this))
.build();
}
});
protected final ApplicationComponentManager componentManager() {
return componentManager;
}
@Override
public final Object generatedComponent() {
return componentManager().generatedComponent();
}
@CallSuper
@Override
public void onCreate() {
// This is a known unsafe cast, but is safe in the only correct use case:
// APP extends Hilt_APP
((APP_GeneratedInjector) generatedComponent()).injectAPP(UnsafeCasts.<APP>unsafeCast(this));
super.onCreate();
}
}
複製代碼
看下里面涉及到的~
public final class ApplicationComponentManager implements GeneratedComponentManager<Object> {
private volatile Object component;
private final Object componentLock = new Object();
private final ComponentSupplier componentCreator;
public ApplicationComponentManager(ComponentSupplier componentCreator) {
this.componentCreator = componentCreator;
}
@Override
public Object generatedComponent() {
if (component == null) {
synchronized (componentLock) {
if (component == null) {
component = componentCreator.get();
}
}
}
return component;
}
}
複製代碼
主要用於管理應用程序中的Hilt Component的建立
構造中建立ComponentSupplier實例。
generatedComponent():經過對象鎖獲取ComponentSupplier
類中的Object,並負責在onCreate函數中將依賴項注入到咱們的應用程序類中。
提供component
的接口
/**
* Interface for supplying a component. This is separate from the Supplier interface so that
* optimizers can strip this method (and therefore all the Dagger code) from the main dex even if a
* Supplier is referenced in code kept in the main dex.
*/
public interface ComponentSupplier {
Object get();
}
複製代碼
代碼比較長就不貼出來了,看下builder:
import dagger.hilt.android.internal.builders.ActivityComponentBuilder;
import dagger.hilt.android.internal.builders.ActivityRetainedComponentBuilder;
import dagger.hilt.android.internal.builders.FragmentComponentBuilder;
import dagger.hilt.android.internal.builders.ServiceComponentBuilder;
import dagger.hilt.android.internal.builders.ViewComponentBuilder;
import dagger.hilt.android.internal.builders.ViewWithFragmentComponentBuilder;
複製代碼
在ComponentSupplier實現的內部,咱們能夠看到對ApplicationC(Application組件)類的引用。DaggerAPP_HiltComponents_ApplicationC是生成的應用程序組件,它充當Hilt在咱們的應用程序中使用的(Activity,Fragment,Service,View等組件)的全局容器。
@OriginatingElement(
topLevelClass = APP.class
)
@GeneratedEntryPoint
@InstallIn(ApplicationComponent.class)
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.InjectorEntryPointGenerator")
public interface APP_GeneratedInjector {
void injectAPP(APP aPP);
}
複製代碼
接口類提供injectAPP
方法爲外部類提供了一個訪問點,以觸發應用程序Component的注入。
@Module
@InstallIn(ApplicationComponent.class)
public final class ApplicationContextModule {
private final Context applicationContext;
public ApplicationContextModule(Context applicationContext) {
this.applicationContext = applicationContext;
}
@Provides
@ApplicationContext
Context provideContext() {
return applicationContext;
}
@Provides
Application provideApplication() {
return (Application) applicationContext.getApplicationContext();
}
}
複製代碼
主要是提供ApplicationContext
,經過**@InstalIIn**注入到 ApplicationComponent
便於後續使用
這裏有個@ApplicationContext
這是個qualifers 限定符,Hilt還提供了一個@ActivityContext
,
例如:
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
複製代碼
@Singleton
class NetWorkUtils @Inject constructor(@ApplicationContext private val context: Context) {
fun isNetworkConnected(): Boolean {
.....
}
}
複製代碼
能夠直接做爲@Provides
方法或@Inject
構造的參數使用。
在以前Dagger-Android中,咱們必須建立諸如ActivityScope,FragmentScope之類的範圍註釋,以管理對象的生命週期,
而這裏只要使用@InstallIn
的註解,就能夠委託Hilt
幫咱們管理
組件的生存期:
組件 | 範圍 | 建立 | 銷燬 |
---|---|---|---|
ApplicationComponent |
@Singleton |
Application#onCreate() |
Application#onDestroy() |
ActivityRetainedComponent |
@ActivityRetainedScope |
Activity#onCreate() 連接 |
Activity#onDestroy() 連接 |
ActivityComponent |
@ActivityScoped |
Activity#onCreate() |
Activity#onDestroy() |
FragmentComponent |
@FragmentScoped |
Fragment#onAttach() |
Fragment#onDestroy() |
ViewComponent |
@ViewScoped |
View#super() |
View 銷燬 |
ViewWithFragmentComponent |
@ViewScoped |
View#super() |
View 銷燬 |
ServiceComponent |
@ServiceScoped |
Service#onCreate() |
Service#onDestroy() |
在@InstallIn
模塊中肯定綁定範圍時,綁定上的範圍必須與component的範圍匹配。例如,@InstallIn(ActivityComponent.class)
模塊內的綁定只能用限制範圍@ActivityScoped
。
例如咱們須要在App中共享OkHttp的配置:
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {
@Provides
fun provideBaseUrl() = "...."
@Provides
@Singleton
fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
} else OkHttpClient
.Builder()
.build()
@Provides
@Singleton
fun provideRetrofit(
okHttpClient: OkHttpClient,
BASE_URL: String
): Retrofit =
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java)
}
複製代碼
相似Dagger中:
@Module
class NetworkModule {
// Hypothetical dependency on LoginRetrofitService
@Provides
fun provideLoginRetrofitService(
okHttpClient: OkHttpClient
): LoginRetrofitService { ... }
}
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
...
}
複製代碼
這裏經過@InstallIn(ApplicationComponent::class)
讓Hilt幫咱們管理ApplicationModule的生命週期
module的使用基本和dagger中同樣, 用來提供一些沒法用構造@Inject
的依賴, 好比接口, 第三方庫類型, Builder模式構造的對象等.
@Module
: 標記一個module, 能夠是一個object
.@Provides
: 標記方法, 提供返回值類型的依賴.這裏就不須要手動添加到@Component(modules = ...)
@Binds
: 標記抽象方法, 返回接口類型, 接口實現是方法的惟一參數.interface AnalyticsService {
fun analyticsMethods()
}
// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
複製代碼
@Provides
與@Binds
的區別:
按照官方說@Binds
須要module是一個abstract class,@Provides
須要module是一個object.並且@Binds
須要在方法參數裏面明確指明接口的實現類
可是@Provides
這麼用也是能夠的。
若是要提供同一個接口的不一樣實現, 能夠用不一樣的註解來標記. (相似於dagger
中是@Named
).
什麼意思? 好比咱們緩存接口有內存和磁盤兩種實現:
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheInMemory
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheInDisk
複製代碼
module中提供的時候用來標記相應的依賴:
@InstallIn(ApplicationComponent::class)
@Module
object CacheModule {
@CacheInMemory
@Singleton
@Provides
fun getCacheInMemory(memoryImpl: CacheSourceMemoryImpl): CacheSource = memoryImpl
@CacheInDisk
@Singleton
@Provides
fun getCacheInDisk(diskImpl: CacheSourceDiskImpl): CacheSource = diskImpl
}
複製代碼
在Dagger2
中,對Activity和Fragment的注入依賴的使用比較麻煩。
@Module
abstract class ActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = [MainActivityFragmentModule::class])
internal abstract fun contributeMainActivity(): MainActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributeMovieDetailActivity(): MovieDetailActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributeTvDetailActivity(): TvDetailActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributePersonDetailActivity(): PersonDetailActivity
}
複製代碼
在Hilt
中就比較簡單了,只須要@AndroidEntryPoint
的註解。至關於上面的@ContributesAndroidInjector
。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//
private val mJokesViewModel: JokesViewModel by viewModels()
private val mBinding: ActivityMainBinding by viewBinding {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mJokesViewModel.jokes.observe(this, Observer {
........
}
}
}
複製代碼
咱們不須要編寫AndroidInjection.inject(this)
或擴展DaggerAppCompatActivity
類。
這裏官方給了限定範圍:
Hilt currently only supports activities that extend ComponentActivity
and fragments that extend androidx library Fragment
, not the (now deprecated) Fragment
in the Android platform.
Hilt
目前不直接支持 content providers.
Hilt支持最經常使用的Android組件, 對於默認不支持的類型, 若是要作字段注入, 須要用@EntryPoint
.
這裏只是限制了字段注入的狀況, 對於自定義類型咱們通常習慣於用構造注入。
必須與@InstallIn
搭配使用,將interface
標記爲入口點,這樣就可使用Hilt
容器提供的依賴對象們.
若是要content provider
使用Hilt
:
class ExampleContentProvider : ContentProvider() {
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface ExampleContentProviderEntryPoint {
fun analyticsService(): AnalyticsService
}
...
}
複製代碼
要訪問@EntryPoint
,使用靜態方法 EntryPointAccessors
:
class ExampleContentProvider: ContentProvider() {
...
override fun query(...): Cursor {
val appContext = context?.applicationContext ?: throw IllegalStateException()
val hiltEntryPoint =
EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)
val analyticsService = hiltEntryPoint.analyticsService()
...
}
}
複製代碼
appContext
參數要與@InstallIn(ApplicationComponent::class)
保持一致。
@EntryPoint
除了解決上述字段注入的 問題,還有什麼場景能夠發揮用處?
生命週期匹配
咱們利用FragmentFactory在Activity
與Fragment
之間用構造函數傳遞數據 :
class ContainerActivity : AppCompatActivity() {
private var fragmentDataTest = FragmentDataTest()
private val mBinding: ActivityContainerBinding by viewBinding {
ActivityContainerBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = EntryPointFragmentFactory(fragmentDataTest)
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
setSupportActionBar(mBinding.toolbar)
}
}
複製代碼
看上述代碼,在沒有用Hilt以前fragmentFactory的設置應該是在 super.onCreate()
以前,可是若是用Hilt就不能這麼寫了,由於在
以前Hilt Application中已經說過,Hilt是在super.onCreate()
中進行依賴注入的,在Hilt_ContainerActivity
類中:
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
inject();
super.onCreate(savedInstanceState);
}
複製代碼
Injection happens in super.onCreate().
因此,若是咱們要在ContainerActivity中使用FragmentFactory就該在super.onCreate()以後,那麼問題來了...咱們知道FragmentFactory
負責在 Activity 和 parent Fragment 初始化 Fragment,應該在 super.onCreate()
以前關聯 FragmentFactory 和 FragmentManager設置。若是在使用Hilt注入以後仍是放在編譯會報錯:
UninitializedPropertyAccessException: lateinit property mFragmentFactory has not been initialized
複製代碼
因此咱們將FragmentManager
綁定FragmentFactory
的動做放在super.onCreate()
以後:
@AndroidEntryPoint
class ContainerActivity : AppCompatActivity() {
@Inject lateinit var mFragmentFactory: EntryPointFragmentFactory
private val mBinding: ActivityContainerBinding by viewBinding {
ActivityContainerBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager.fragmentFactory = mFragmentFactory
setContentView(mBinding.root)
mFragmentFactory.fragmentDataTest.setData("xxx")
}
}
複製代碼
這樣就能夠了,可是要注意了:
若是上面的ContainerActivity
被意外終止而觸發重建的話是會報錯的 :
java.lang.RuntimeException: Unable to start activity ComponentInfo{tt.reducto.daggerhiltsample/tt.reducto.daggerhiltsample.ui.entry.ContainerActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment tt.reducto.daggerhiltsample.ui.entry.EntryPointFragment: could not find Fragment constructor
...
Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment tt.reducto.daggerhiltsample.ui.entry.EntryPointFragment: could not find Fragment constructor
複製代碼
能夠定位 Hilt_ContainerActivity
中的super.onCreate(savedInstanceState);
這裏就涉及到FragmentManager的狀態保存與恢復
Fragment依附於Activity,而Fragment的狀態保存與恢復機制也是由Activity的相關方法觸發。Activity的方法onSaveInstanceState(Bundle outState)
的參數outState是系統在狀態須要保存時用來提供存放持久化狀態的容器,當系統觸發狀態保存時,Activity下的Fragment的全部狀態便經過mFragments的saveAllState
方法保存在了 FRAGMENTS_TAG
鍵中,在Activity重建 的 時候經過mFragments.restoreAllState入口將狀態恢復
在此期間Fragment的都會交由FragmentManager管理,包括咱們須要注意的如何新建一個Fragment對象:
Fragment.instantiate(…)方法會根據所給的class name加載對應的Class類,調用clazz.newInstance()
新建一個全新的Fragment對象:
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
...
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
...
}
複製代碼
綜上,Activity重建後沒法利用FragmentFactory從新建立Fragment,因此官方FragmentFactory
文檔纔有這麼一句話:
瞭解了FragmentManager
綁定FragmentFactory
的動做在super.onCreate()
以前執行的必要性後,咱們再來利用Hilt提供的特性解決聲明週期不匹配的問題
@EntryPoint
@InstallIn(ActivityComponent::class)
interface ContainerActivityEntryPoint {
fun getFragmentManager(): FragmentManager
fun getFragmentFactory(): EntryPointFragmentFactory
}
複製代碼
咱們利用@EntryPoint
從ContainerActivityEntryPoint
對象中獲取FragmentManager和EntryPointFragmentFactory的引用:
@AndroidEntryPoint
class ContainerActivity : AppCompatActivity() {
private val mBinding: ActivityContainerBinding by viewBinding {
ActivityContainerBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
val entryPoint = EntryPointAccessors.fromActivity(this,ContainerActivityEntryPoint::class.java)
val mFragmentFactory = entryPoint.getFragmentFactory()
entryPoint.getFragmentManager().fragmentFactory = mFragmentFactory
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mFragmentFactory.fragmentDataTest.setData("xxxxxxxxxxx")
}
......
}
複製代碼
從靜態類EntryPointAccessors
中獲取定義的實例,有點SOLID中接口隔離原則的意思。
再次模擬Activity重建狀態,一切正常。
以前Dagger注入ViewModel時比較麻煩,在構造函數中建立帶有參數的ViewModel實例,每一個ViewModel
必須實現一個全局或每一個ViewModelFactory
,並實現ViewModelModule
來綁定ViewModel
。
@Module
internal abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModels(mainActivityViewModel: MainActivityViewModel): ViewModel
......
}
複製代碼
而Hilt
更簡單:
class JokesViewModel @ViewModelInject constructor(
private val jokesRepository: JokesRepository,
private val netWorkUtils: NetWorkUtils,
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
........
}
複製代碼
使用@ViewModelInject
便可,JokesRepository
、NetWorkUtils
都是由Hilt注入的。
Hilt
將在後臺生成相應的工廠類和東西。
這裏有個@Assisted
須要注意下:
由於在這以前ViewModel
中注入SavedStateHandle
是比較麻煩的,因爲@AssistedInject.Factory
修飾接口再經過@AssistedInject
注入ViewModel
,最後還要經過 @AssistedModule
中添加.....太太太麻煩了
看下Hilt
的@Assisted
描述:
/**
* Marks a parameter in a {@link androidx.hilt.lifecycle.ViewModelInject}-annotated constructor
* or a {@link androidx.hilt.work.WorkerInject}-annotated constructor to be assisted
* injected at runtime via a factory.
*/
// TODO(danysantiago): Remove and replace with dagger.assisted.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Assisted {
}
複製代碼
意思是Worker
經過@WorkerInject
構造函數注入時要經過@Assisted
修飾Context
和WorkerParameters
相似:
class ExampleWorker @WorkerInject constructor(
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
workerDependency: WorkerDependency
) : Worker(appContext, workerParams) { ... }
複製代碼
利用Hilt寫個超簡單的請求列表的小例子:Github 傳送
以上就是Dagger Hilt簡單上手,
目前Hilt還處於alpha狀態,依賴kapt,等KSP成熟以後預計效率會有進一步提高。
固然koin玩起來更舒心。
developer.android.com/training/de…