持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
简介
Hilt 是什么?
官方的定义是:
Hilt是Android的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。执行手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。
Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法。Hilt在热门DI库 Dagger 的基础上构建而成,因而能够受益于 Dagger 的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。如需了解详情,请参阅 Hilt 和 Dagger。
所以,Hilt是一个对Dagger
库的扩展。那Hilt对于Dagger
的优势主要在于Hilt与Jetpack
库和Android Framework
的类是集成在一起的,并删除了大部分样板代码,我们只要专注于定义和注入绑定的部分。
为什么需要使用依赖注入?
使用依赖注入有以下的优点:
- 简化 Android 应用的 Dagger 相关基础架构。
- 创建一组标准的组件和作用域,以简化设置、提高可读性以及在应用之间共享代码。
- 提供一种简单的方法来为各种构建类型(如测试、调试或发布)配置不同的绑定。
Hilt依赖
hilt-android-gradle-plugin
插件依赖,在项目级的build.gradle
文件中添加:
buildscript {
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
}
}
应用层级的app/build.gradle
文件中添加以下依赖项:
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
dependencies {
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
因为Hilt使用Java 8功能,需在项目中启用 Java 8,在 app/build.gradle
文件中添加:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
Hilt常用注解介绍
Hilt常用注解主要是:@HiltAndroidApp
、@AndroidEntryPoint
、@Inject
、@Module
、@InstallIn
、@Binds
、@Provides
、@EntryPoint
等等。
Hilt 应用类@HiltAndroidApp
所有使用Hilt的应用都必须包含一个带有@HiltAndroidApp
注释的Application
类。
@HiltAndroidApp
class HiltApp : Application() {
override fun onCreate() {
super.onCreate()
}
}
主要添加了@HiltAndroidApp
会触发Hilt的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。生成的这一Hilt组件会附加到Application
对象的生命周期,并为其提供依赖项。此外,它也是应用的父组件,这意味着,其他组件可以访问它提供的依赖项。 在HiltApp
中设置好@HiltAndroidApp
之后,就可以使用Hilt提供的组件了,组件包含Application、Activity、Fragment、View、Service、BroadcastReceiver等。
将依赖项注入Android类 @AndroidEntryPoint
在Application
类中设置了Hilt且有了应用级组件后,Hilt可以为带有@AndroidEntryPoint
注释的其他Android
类提供依赖项:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Hilt目前支持以下Android
类:
Application
(通过使用@HiltAndroidApp
)Activity
Fragment
View
Service
BroadcastReceiver
如果我们要使用@AndroidEntryPoint
为某个Android
类添加注释,则还必须为依赖于该类的Android
类添加注释。例如,如果给一个Fragment
添加注释,则还必须为使用该 Fragment
的所有Activity
添加注释。
注意:在 Hilt 对 Android 类的支持方面还要注意以下几点:
- Hilt仅支持扩展
ComponentActivity
onentActivity)的Activity,如AppCompatActivity
。- Hilt仅支持扩展
androidx.Fragment
的Fragment。- Hilt不支持保留的Fragment。
@Inject
@AndroidEntryPoint
会为项目中的每个Android
类生成一个单独的Hilt
组件。这些组件可以从它们各自的父类接收依赖项,如组件层次结构中所述。
如需从组件获取依赖项,可以使用@Inject
注释执行字段注入:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var analytics: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
由Hilt注入的字段不能为私有字段。尝试使用Hilt注入私有字段会导致编译错误。
Hilt 注入的类可以有同样使用注入的其他基类。如果这些类是抽象类,则它们不需要
@AndroidEntryPoint
注释。
为了执行字段注入,Hilt需要知道如何从相应组件提供必要依赖项的实例。绑定包含将某个类型的实例作为依赖项提供所需的信息。向Hilt提供绑定信息的一种方法是构造函数注入。在某个类的构造函数中使用 @Inject
注释,以告知Hilt如何提供该类的实例:
class AnalyticsAdapter @Inject constructor(
private val service: AnalyticsService
) { }
在一个类的代码中,带有注释的构造函数的参数即是该类的依赖项。在本例中AnalyticsService
是 AnalyticsAdapter
的一个依赖项。因此,Hilt还必须知道如何提供AnalyticsService
的实例。
注意:在构建时,Hilt会为
Android
类生成Dagger
组件。然后,Dagger
会走查您的代码,并执行以下步骤:
- 构建并验证依赖关系图,确保没有未满足的依赖关系且没有依赖循环。
- 生成它在运行时用来创建实际对象及其依赖项的类。
Hilt模块 @Module
有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用Hilt @Module
向Hilt提供绑定信息。比如我们常用于创建依赖类的对象(例如第三方库OkHttp
、Retrofit
等等),使用@Module
注解的类,需要使用@InstallIn
注解指定module
的范围。
@Module
@InstallIn(SingletonComponent::class)
// 把NetworkModule绑定到HiltApp 的生命周期。
object NetworkModule {
}
@InstallIn
使用@Module
注入的类,需要使用@InstallIn
注解指定module
的范围,例如使用 @InstallIn(HiltApp::class)
注解的module
会绑定到activity
的生命周期上。 Hilt提供了以下组件来绑定依赖与对应的Android
类的活动范围:
Hilt 提供的组件 | 对应的 Android 类的活动范围 |
---|---|
Singletonomponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
注意:Hilt 没有为 broadcast receivers 提供组件,因为 Hilt 直接从 ApplicationComponent 注入 broadcast receivers。
@Binds 注入接口实例
接口是无法通过构造方法进行注入的,需要通过在Hilt 模块内创建一个带有@Binds
注释的抽象函数进行注入。@Binds
注释会告知Hilt在需要提供接口的实例时要使用哪种实现。
带有注释的函数会向Hilt提供以下信息:
- 函数返回类型会告知Hilt函数提供哪个接口的实例。
- 函数参数会告知Hilt要提供哪种实现。
看下面代码:
interface AnalyticsService {
fun analyticsMethods()
}
// 构造函数也需要注入,因为Hilt需要知道如何提供AnalyticsServiceImpl的实例。
class AnalyticsServiceImpl @Inject constructor(
...
) : AnalyticsService { ... }
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
其中的Hilt模块AnalyticsModule
带有@InstallIn(ActivityComponent::class)
注释,这是表示要Hilt 将该依赖项注入ExampleActivity
。
@Provides 注入实例
除了接口无法通过构造函数注入实例外,还有第三方库如:Retrofit
、OkHttpClient
或 Room
数据库等都是无法通过构造函数注入,这时就需要使用@Provides
注入实例。看如下代码:
@Module
@InstallIn(SingletonComponent::class)
// 把NetworkModule绑定到HiltApp 的生命周期。
object NetworkModule {
@Provides
fun getApiService() : ApiService{
return Retrofit.Builder()
.baseUrl("https://www.wanandroid.com")
.build()
.create(ApiService::class.java)
}
}
带有@Provides
注释的函数会向 Hilt 提供以下信息:
- 函数返回类型会告知Hilt函数提供哪个类型的实例;
- 函数参数会告知Hilt相应类型的依赖项;
- 函数主体会告知Hilt如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt 都会执行函数主体。
Hilt 中的预定义限定符
Hilt提供了一些预定义的限定符。例如,由于我们可能需要来自应用或Activity
的Context
类,因此Hilt提供了@ApplicationContext
和 @ActivityContext
限定符。
假设下面例子中的AnalyticsAdapter
类需要Activity
的上下文。以下代码演示了如何向AnalyticsAdapter
提供Activity
上下文:
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { }
@EntryPoint注解
Hilt支持最常见的Android
类Application
、Activity
、Fragment
、View
、Service
、BroadcastReceiver
等等,但是我们可能需要在Hilt 不支持的类中执行依赖注入,在这种情况下可以使用@EntryPoint
注解进行创建,Hilt会提供相应的依赖。
Hilt的基本使用
基本的Android组件中使用
先创建一个HiltSample
类,并使用@Inject
注解修饰构造方法,这时告诉Hilt如何提供该类的实例,@Inject
常用于构造函数、非私有字段、方法中。
class HiltSample @Inject constructor() {
companion object{
val TAG = HiltSample::class.simpleName
}
fun hiltTodo(str: String){
Log.e(TAG,str)
}
}
创建Fragment并添加注解和过@Inject
获取HiltSample
实例:
/**
* 注解 fragment后,它依赖的activity也需要注解
*/
@AndroidEntryPoint
class MainFragment : Fragment() {
@Inject
lateinit var hiltSample: HiltSample
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
hiltSample.hiltTodo("MainFragment")
}
}
在Activity
中使用MainFragment
:
/**
* 为项目中的每个Android类生成一个Hilt组件,这些组件可以从它们各自的父类接收依赖项
*
* 抽象类不可使用@AndroidEntryPoint进行注解
*/
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.add(R.id.container, MainFragment::class.java, null)
.commit()
}
}
Hilt在第三方库中的使用
在项目中注入第三方库类组件的依赖,我们需要使用@Module
注解,并在使用@Module
注解的类中创建第三方依赖的对象。
@Module
@InstallIn(SingletonComponent::class) // 把NetworkModule绑定到HiltApp 的生命周期
object NetworkModule {
@Provides
@Singleton
fun getOkHttpClient() : OkHttpClient{
return OkHttpClient.Builder().build()
}
@Provides
@Singleton
fun createRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://www.wanandroid.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun getApiService() : ApiService{
return createRetrofit(getOkHttpClient()).create(ApiService::class.java)
}
}
这里要注意两点:
- 使用 @Module 注入的类,需要使用 @InstallIn 注解指定 module 的范围,会绑定到 Android 类对应的生命周期上。
- @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
Hilt在ViewModel的使用
先添加ViewModel的依赖:
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
透过使用@HiltViewModel
添加注释, 并在ViewModel
对象的构造函数中使用@Inject
注释来提供一个ViewModel
。
@HiltViewModel
class HiltViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: HiltRepository,
) : ViewModel() {
}
然后,带有 @AndroidEntryPoint
注释的Activity
或Fragment
可以使用ViewModelProvider
或 by viewModels()
KTX扩展照常获取 ViewModel
实例:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val hiltViewModel: HiltViewModel by viewModels()
...
}
Hilt与Room的结合使用
在Hilt中我们只要使用注解@Module
创建 RoomModule
文件,使用@InstallIn
注解指定module
的生命周期,例如使用@InstallIn(SingletonComponent::class)
注解 module
会绑定到Application
的生命周期上:
@Module
@InstallIn(SingletonComponent::class)
// SingletonComponent把RoomModule 绑定到 Application的生命周期。
object RoomModule {
@Provides
@Singleton
fun provideAppDataBase(application: Application): AppDataBase {
return Room
.databaseBuilder(application, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
}
总结
由于之前没有使用过Dagger,无法评判Hilt和Dagger的优劣势,社区中好多同学说Dagger学习的成本非常高,而且难用,所以这也是为什么Android
开发团队基于Dagger开发了Hilt这个框架。有一点比较显而易见的是,Hilt 集成了Jetpack
库和Android
框架类,并自动管理它们的生命周期,这让开发者只用关注如何绑定就好,的确方便了我们的学习和使用。这篇文章只是介绍学习了Hilt的概念和一些基本使用,后续会考虑在之前开源的WanAndroid项目用上。项目中的大多数知识点都来自官网,如有错误,烦请指正,谢谢。