您现在的位置是:首页 >学无止境 >Hilt在Android中的使用网站首页学无止境
Hilt在Android中的使用
1、简介:Hilt 是 Android 颇具特色的依赖项注入库,可减少在项目中使用手动依赖项注入时产生的样板代码。
2、使用
1.我们需要为 Application
类添加 @HiltAndroidApp
注解
@HiltAndroidApp
会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类可使用依赖项注入。application 容器是应用的父级容器,这意味着其他容器可以访问它提供的依赖项。
@HiltAndroidApp class LogApplication : Application() { ... } |
2.为 Android 类添加 @AndroidEntryPoint
注解会创建一个沿袭 Android 类生命周期的依赖项容器
利用 @AndroidEntryPoint
,Hilt 可创建附着于 LogsFragment
生命周期的依赖项容器,并能够将实例注入 LogsFragment
@AndroidEntryPoint class LogsFragment : Fragment() { ... } |
3.对于要进行注入的字段,我们可以利用 @Inject
注解让 Hilt 注入不同类型的实例
注意:由 Hilt 注入的字段不能是私有字段
@AndroidEntryPoint class LogsFragment : Fragment() { @Inject lateinit var logger: LoggerLocalDataSource ... } |
4.告知 Hilt 如何通过 @Inject 提供依赖项
如要告知 Hilt 如何提供类型的实例,请向要注入的类的构造函数添加 @Inject 注解。
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) { ... } |
5.将实例的作用域限定为容器
Android 类 | 生成的组件 | 作用域 |
---|---|---|
Application | SingletonComponent | @Singleton |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
@Singleton class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) { ... } |
6.Hilt 模块
有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用 Hilt 模块向 Hilt 提供绑定信息
Hilt 模块是带有@Module
和 @InstallIn
注解的类。@Module
会告知 Hilt 这是一个模块,而 @InstallIn
会通过指定 Hilt 组件告知 Hilt 绑定在哪些容器中可用
6.1创建模块
@InstallIn(ActivityComponent::class) @Module abstract class NavigationModule { } |
6.2使用 @Provides 提供实例
我们可以在 Hilt 模块中为函数添加 @Provides
注解,告知 Hilt 如何提供无法通过构造函数注入的类型。
每当 Hilt 需要提供相应类型的实例时,都会执行带有 @Provides
注解的函数的函数主体。带有 @Provides
注解的函数的返回值类型会告知 Hilt 绑定的类型,即如何提供该类型的实例。函数参数是该类型的依赖项。
在 Kotlin 中,仅包含 @Provides
函数的模块可以是 object
类。通过这种方式,提供程序会得到优化。
@Module object DatabaseModule { @Provides fun provideLogDao(database: AppDatabase): LogDao { return database.logDao() } } |
6.3使用 @Binds 注入接口实例
Hilt 模块不能同时包含非静态和抽象绑定方法,因此您不能将 @Binds
和 @Provides
注解放在同一个类中(A @Module may not contain both non-static and abstract binding methods)
一个抽象函数会返回我们告知 Hilt 的接口(即 AppNavigator
),而函数参数是该接口的实现(即 AppNavigatorImpl
)
@InstallIn(ActivityComponent::class) @Module abstract class NavigationModule { @Binds abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator } |
6.4限定符
如要告知 Hilt 如何提供同一类型的不同实现(多个绑定),您可以使用限定符
6.4.1 定义注解
package com.example.android.hilt.di @Qualifier annotation class InMemoryLogger @Qualifier annotation class DatabaseLogger |
6.4.2对实现添加注解
package com.example.android.hilt.di @Qualifier annotation class InMemoryLogger @Qualifier annotation class DatabaseLogger @InstallIn(ApplicationComponent::class) @Module abstract class LoggingDatabaseModule { @DatabaseLogger @Singleton @Binds abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource } @InstallIn(ActivityComponent::class) @Module abstract class LoggingInMemoryModule { @InMemoryLogger @ActivityScoped @Binds abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource } |
6.4.3通过注解选中实现方式
@AndroidEntryPoint class LogsFragment : Fragment() { @InMemoryLogger @Inject lateinit var logger: LoggerDataSource ... } @AndroidEntryPoint class ButtonsFragment : Fragment() { @InMemoryLogger @Inject lateinit var logger: LoggerDataSource ... } |
3、原理分析
3.1通过注解处理器生成hilt相关文件
3.2通过插件将super类替换
plugin主要代码地址:
/Users/xxx/.gradle/caches/modules-2/files-2.1/com.google.dagger/hilt-android-gradle-plugin/2.28-alpha/eb33a043b2bbdc7cdee3c851d0f8532bfd3645a5/hilt-android-gradle-plugin-2.28-alpha-sources/dagger/hilt/android/plugin/
查看AndroidEntryPointClassTransformer.kt主要代码:
private fun transformClass(clazz: CtClass): Boolean { if (ANDROID_ENTRY_POINT_ANNOTATIONS.none { clazz.hasAnnotation(it) }) { // Not a Android entry point annotated class, don't do anything. return false } // TODO(danysantiago): Handle classes with '$' in their name if they do become an issue. val superclassName = clazz.classFile.superclass //将标有@AndroidEntryPoint和@HiltAndroidApp的父类替换为Hilt_XXX val entryPointSuperclassName = clazz.packageName + ".Hilt_" + clazz.simpleName.replace("$", "_") logger.info( "[$taskName] Transforming ${clazz.name} to extend $entryPointSuperclassName instead of " + "$superclassName." ) clazz.superclass = classPool.get(entryPointSuperclassName) transformSuperMethodCalls(clazz, superclassName, entryPointSuperclassName) return true } |