10 Jetpack-Hilt-1
通过这篇文章你将学习到以下内容:
- 为什么需要使用依赖注入库?
- Hilt 是什么?
- Hilt 常用注解含义?
- 使用 Hilt 都有那些坑需要注意?
- Hilt 如何和 Android 组件一起使用?
- Hilt 如何和第三方组件一起使用?
- Hilt 如何和 Jetpack 组件 ViewModule 一起使用?
- Hilt 如何和 Jetpack 组件 Room 一起使用?
研究 Hilt 时遇到一些坑,有些坑在 Goggle 文档上也没有提到,我会在文中特别强调,并在 文末总结部分 进行汇总。
为什么需要使用依赖注入库
Hilt、Dagger、Koin 等等都是依赖注入库,Google 也在努力不断的完善依赖注入库从 Dagger 到 Dagger2 在到现在的 Hilt,因为依赖注入是面向对象设计中最好的架构模式之一,使用依赖注入库有以下优点:
- 依赖注入库会自动释放不再使用的对象,减少资源的过度使用。
- 在配置 scopes 范围内,可重用依赖项和创建的实例,提高代码的可重用性,减少了很多模板代码。
- 代码变得更具可读性。
- 易于构建对象。
- 编写低耦合代码,更容易测试。
Hilt 是什么?
Hilt 是 Android 的依赖注入库,它减少了在项目中进行手动依赖,进行手动依赖注入需要您手动构造每个类及其依赖,依赖注入库的出现节省了 Android 开发者大量的时间。
Hilt 通过为项目中的每个 Android 类提供容器并自动管理它们的生命周期,提供了在应用程序中使用 DI 的标准方法。Hilt 是在 Dagger 的基础上进行构建,因为 Dagger 提供的编译时正确性、运行时性能、可伸缩性并且从 Android Studio 支持 Dagger 中获益。
Hilt 的实现要比 Dagger 简单得多,使用 Dagger 实现依赖注入,需要去编写 modules、components 等等。每次创建一个新的 android 组件,比如 Activity、Fragment 或Service,我们都需要手动将它们添加到各自的 modules 中,以便在需要的时候注入它们。
接下来我们开始从分析 Hilt 注解的含义出发,来了解如何在应用程序中使用 Hilt。
Hilt 常用注解的含义
Hilt 常用注解包含 @HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module、@InstallIn、@Provides、@EntryPoint 等等。
@HiltAndroidApp
- 所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application。
- @HiltAndroidApp 注解将会触发 Hilt 代码的生成,作为应用程序依赖项容器的基类。
- 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其他组件访问的依赖。
- 在 Application 中设置好 @HiltAndroidApp 之后,就可以使用 Hilt 提供的组件了,组件包含Application、Activity、Fragment、View、Service、BroadcastReceiver 等等。
@AndroidEntryPoint
Hilt 提供的 @AndroidEntryPoint 注解用于提供 Android 类的依赖(Activity、Fragment、View、Service、BroadcastReceiver)特殊的 Application 使用 @HiltAndroidApp 注解。
- Activity:仅仅支持 ComponentActivity 的子类例如 FragmentActivity、AppCompatActivity 等等。
- Fragment:仅仅支持继承 androidx.Fragment 的 Fragment
- View
- Service
- BroadcastReceiver
坑:
-
如果使用 @AndroidEntryPoint 在非 ComponentActivity 子类上注解,例如 Activity 则会抛出以下异常。
Activities annotated with @AndroidEntryPoint must be a subclass of androidx.activity.ComponentActivity. (e.g. FragmentActivity, AppCompatActivity, etc.) 复制代码
-
如果使用 @AndroidEntryPoint 注解 Android 类,必须在它依赖的 Android 类添加同样的注解,例如在 Fragment 中添加 @AndroidEntryPoint 注解,必须在 Fragment 依赖的 Activity 上也添加 @AndroidEntryPoint 注解 , 否则会抛出以下异常。
java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity 复制代码
@Inject
Hilt 需要知道如何从相应的组件中提供必要依赖的实例。使用 @Inject 注解来告诉 Hilt 如何提供该类的实例,它常用于构造函数、非私有字段、方法中。
注意:在构建时,Hilt 为 Android 类生成 Dagger 组件。然后 Dagger 遍历您的代码并执行以下步骤:
- 构建并验证依赖关系,确保没有未满足的依赖关系。
- 生成它在运行时用于创建实际对象及其依赖项的类。
@Module
常用于创建依赖类的对象(例如第三方库 OkHttp、Retrofit等等),使用 @Module 注解的类,需要使用 @InstallIn 注解指定 module 的范围。
@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,因此 NetworkModule 绑定到 Application 的生命周期。
object NetworkModule {
}
复制代码
@InstallIn
使用 @Module 注入的类,需要使用 @InstallIn 注解指定 module 的范围,例如使用 @InstallIn(ActivityComponent::class) 注解的 module 会绑定到 activity 的生命周期上。
Hilt 提供了以下组件来绑定依赖与 对应的 Android 类的活动范围。
Hilt 提供的组件 | 对应的 Android 类的活动范围 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
注意:Hilt 没有为 broadcast receivers 提供组件,因为 Hilt 直接从 ApplicationComponent 注入 broadcast receivers。
Hilt 会根据相应的 Android 类生命周期自动创建和销毁生成的组件类的实例,它们的对应关系如下表格所示。
Hilt 提供的组件 | 创建对应的生命周期 | 销毁对应的生命周期 |
---|---|---|
ApplicationComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | View destroyed |
ViewWithFragmentComponent | View#super() | View destroyed |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
@Provides
它常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,因此 NetworkModule 绑定到 Application 的生命周期。
object NetworkModule {
/**
* @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
* @Singleton 提供单例
*/
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.build()
}
}
复制代码
@EntryPoint
Hilt 支持最常见的 Android 类 Application、Activity、Fragment、View、Service、BroadcastReceiver 等等,但是您可能需要在Hilt 不支持的类中执行依赖注入,在这种情况下可以使用 @EntryPoint 注解进行创建,Hilt 会提供相应的依赖。
基本概念介绍完了之后,我们正式在项目中使用 Hilt。
如何使用 Hilt
首先需要添加 Hilt 依赖,Hilt 依赖添加方式相比于 Koin 太麻烦了,首先在 project 的 build.gradle 添加以下依赖。
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
复制代码
然后在 App 模块中的 build.gradle 文件中添加以下代码。
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
复制代码
坑:需要注意的是如果同时使用 hilt 和 data binding,Android Studio 的版本必须 >= 4.0
所以还没有升级的朋友们,尽快升级吧,升级到 Android Studio 4.0 也会遇到一些坑,不过好在这些坑现在都有相应的解决方案了。
Hilt 使用 Java 8 的功能,所以要在项目中启用 Java 8,需要在 App 模块的 build.gradle 文件中,添加以下代码
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// For Kotlin projects
kotlinOptions {
jvmTarget = "1.8"
}
}
复制代码
注意: 这里有一个坑,对于 Kotlin 项目,需要添加 kotlinOptions,这是 Google 文档 Dependency injection with Hilt 中没有提到的,否则使用 ViewModel 会编译不过,下文会有详细的讲解。
Hilt 依赖添加方式相比于 Koin 太麻烦了,使用 koin 只需要添加相应的依赖就可以使用了。
Application 是 App 的入口,所以所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application
@HiltAndroidApp
class HiltApplication : Application() {
/**
* 1\. 所有使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application
* 2\. @HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖项容器的基类
* 3\. 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其他组件访问的依赖
* 4\. 在 Application 中设置好 @HiltAndroidApp 之后,就可以使用 Hilt 提供的组件了,
* Hilt 提供的 @AndroidEntryPoint 注解用于提供 Android 类的依赖(Activity、Fragment、View、Service、BroadcastReceiver)等等
* Application 使用 @HiltAndroidApp 注解
*/
}
复制代码
- @HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖容器的基类
- 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其他组件访问的依赖
准备工作都做完了,接下来我们来看几个例子,如何使用 Hilt 进行依赖注入。
如何使用 Hilt 进行依赖注入
我们先来看一个简单的例子,注入 HiltSimple 并在 Application 中调用它的 doSomething 方法。
class HiltSimple @Inject constructor() {
fun doSomething() {
Log.e(TAG, "----doSomething----")
}
}
@HiltAndroidApp
class HiltApplication : Application() {
@Inject
lateinit var mHiltSimple: HiltSimple
override fun onCreate() {
super.onCreate()
mHiltSimple.doSomething()
}
}
复制代码
Hilt 需要知道如何从相应的组件中提供必要依赖的实例。使用 @Inject 注解来告诉 Hilt 如何提供该类的实例,@Inject 常用于构造函数、非私有字段、方法中。
Hilt 如何和 Android 组件一起使用
如果是 Hilt 支持的 Android 组件,直接使用 @AndroidEntryPoint 注解即可。
/**
*
* 为项目中的每个 Android 类生成一个 Hilt 组件,这些组件可以从它们各自的父类接收依赖项,
* 如果是抽象类则不能使用 @AndroidEntryPoint 注解
*
* 如果使用 @AndroidEntryPoint 注解 Android 类,还必须注解依赖于它的 Android 类,
* 例如 如果 注解 fragment 然后还必须注解 fragment 依赖的 Activity, 否则会抛出以下异常
* java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity
*/
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 用到了 Fragment 1.2.0 中重要的更新
// 可以查看之前写的这篇文章 @see https://juejin.im/post/6844904167685750798
supportFragmentManager.beginTransaction()
.add(R.id.container, HiltFragment::class.java, null)
.commit()
}
}
/**
* 如果 注解 fragment 然后还必须注解 fragment 依赖的 Activity, 否则会抛出以下异常
* java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity
*/
@AndroidEntryPoint
class HiltFragment : Fragment() {
// 使用 @Inject 注解从组件中获取依赖
@Inject
lateinit var mHiltSimple: HiltSimple
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_hilt, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mHiltSimple.doSomething()
}
}
复制代码
- 如果是抽象类则不需要使用 @AndroidEntryPoint 注解。
- @AndroidEntryPoint 注解 仅仅支持 ComponentActivity 的子类例如 FragmentActivity、AppCompatActivity 等等。
- 如果使用 @AndroidEntryPoint 注解 Android 类,必须在它依赖的 Android 类添加同样的注解,例如在 Fragment 中添加 @AndroidEntryPoint 注解,必须在 Fragment 依赖的 Activity 上也添加 @AndroidEntryPoint 注解。
注意: 在 Activity 中添加 Fragment,用到了 Fragment 1.2.0 中重要的更新,可以查看之前写的这篇文章 [译][Google工程师] 详解 FragmentFactory 如何优雅使用 Koin 以及部分源码分析。
Hilt 如何和第三方组件一起使用
如果要在项目中注入第三方依赖,我们需要使用 @Module 注解,使用 @Module注解的普通类,在其中创建第三方依赖的对象。
@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,因此 NetworkModule 绑定到 Application 的生命周期。
object NetworkModule {
/**
* @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
* @Singleton 提供单例
*/
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideGitHubService(retrofit: Retrofit): GitHubService {
return retrofit.create(GitHubService::class.java)
}
}
复制代码
- @Module 常用于创建依赖类的对象(例如第三方库 OkHttp、Retrofit等等)。
- 使用 @Module 注入的类,需要使用 @InstallIn 注解指定 module 的范围,会绑定到 Android 类对应的生命周期上。
- @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
Hilt 如何和 ViewModel 一起使用
在 App 模块中的 build.gradle 文件中添加以下代码。
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
复制代码
注意: 这个是在 Google 文档上没有提到的,如果使用的是 kotlin 的话需要额外在 App 模块中的 build.gradle 文件中添加以下代码,否则调用 by viewModels()
会编译不过。
// For Kotlin projects
kotlinOptions {
jvmTarget = "1.8"
}
复制代码
在 ViewModel 对象的构造函数中使用 @ViewModelInject 注解提供一个 ViewModel。
class HiltViewModel @ViewModelInject constructor(
) : ViewModel() {
/**
* 在 LifeCycle 2.2.0 之后,可以用更精简的方法来完成,使用 LiveData 协程构造方法 (coroutine builder)。
* liveData 协程构造方法提供了一个协程代码块,产生的是一个不可变的 LiveData,emit() 方法则用来更新 LiveData 的数据。
*/
val mHitLiveData = liveData {
emit(" i am a ViewModelInject")
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val mHitViewModule: HiltViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mHitViewModule.mHitLiveData.observe(this, Observer {
tvResult.setText(it)
})
}
}
复制代码
在 HiltViewModel 里面使用了 LifeCycle 2.2.0 之后新增的方法,LiveData 协程构造方法提供了一个协程代码块,产生的是一个不可变的 LiveData,emit() 方法则用来更新 LiveData 的数据。
Hilt 如何和 Room 一起使用
这里需要用到 @Module 注解,使用 @Module 注解的普通类,在其中提供 Room 的实例。
@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,因此 NetworkModule 绑定到 Application 的生命周期。
object RoomModule {
/**
* @Provides 常用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
* @Singleton 提供单例
*/
@Provides
@Singleton
fun provideAppDataBase(application: Application): AppDataBase {
return Room
.databaseBuilder(application, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
@Provides
@Singleton
fun providePersonDao(appDatabase: AppDataBase): PersonDao {
return appDatabase.personDao()
}
}
复制代码
总结
全文到这里就结束了,本篇文章的案例 [HiltSimple]
这篇文章里面分别介绍了 @HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module、@InstallIn、@Provides 的含义以及实战案例,下篇文章我们一起来分析一下 @EntryPoint,以及和其他 Jetpack 组件如何一起使用。
需要注意的是使用 Hilt 有三个需要注意的地方
-
如果注解非 ComponentActivity 子类,例如 Activity 则会抛出以下异常。
Activities annotated with @AndroidEntryPoint must be a subclass of androidx.activity.ComponentActivity. (e.g. FragmentActivity, AppCompatActivity, etc.) 复制代码
-
如果使用 @AndroidEntryPoint 注解 Android 类,必须在它依赖的 Android 类添加同样的注解,例如在 Fragment 中添加 @AndroidEntryPoint 注解,必须在 Fragment 依赖的 Activity 上也添加 @AndroidEntryPoint 注解 , 否则会抛出以下异常。
java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.hi.dhl.hilt.MainActivity 复制代码
-
需要注意的是如果同时使用 hilt 和 data binding,Android Studio 的版本必须 >= 4.0
所以还没有升级的朋友们,尽快升级吧,升级到 Android Studio 4.0 也会遇到一些坑,不过好在这些坑现在都有相应的解决方案了。
Hilt 如果和 ViewModel 一起使用有点需要注意
这个是在 Google 文档上没有提到的,如果使用的是 kotlin 语言的话,需要额外在 App 模块中的 build.gradle 文件中添加以下代码,否则调用 by viewModels()
会编译不过。
kotlinOptions {
jvmTarget = "1.8"
}
复制代码