网站建设兼职合同,软件著作权申请费用,网页优化方法,大专自考报名入口官网一起来学Kotlin#xff1a;概念#xff1a;27. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用#xff0c;系列3#xff1a;Hilt 注释介绍及使用案例
此系列博客中#xff0c;我们将主要介绍#xff1a;
Dependency Injection#xff08;依赖注入#xff09…一起来学Kotlin概念27. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用系列3Hilt 注释介绍及使用案例
此系列博客中我们将主要介绍
Dependency Injection依赖注入 概念介绍。网上看了许多关于 DI 的介绍云里雾里。这里我们通过通俗易懂地方式对其进行介绍。手动依赖注入介绍。为了让大家更容易理解 Hilt我们先介绍如何通过手动的方式实现依赖注入效果。Hilt 注释annotations介绍及使用案例MVVM 案例中如何使用 Hilt
此博客基于一个非常简单的 Kotlin 项目来解释 Hilt 的使用方式。 文章目录一起来学Kotlin概念27. Dependency Injection 依赖注入以及Hilt在Kotlin中的使用系列3Hilt 注释介绍及使用案例前言1 回顾2 Hilt 的相关注释annotations2.1 HiltAndroidApp 注释2.2 AndroidEntryPoint 注释2.3 Inject 注释2.4 ViewModelInject 注释2.5 Module 注释2.6 Binds 注释2.7 InstallIn 注释2.8 Provides 注释3 Hilt 的简单案例3.1 AndroidManifest.xml3.2 build.gradle (Project)3.3 build.gradle (Module)3.4 Application Class MyApp.kt3.5 MainActivity.kt3.6 DatabaseAdapter.kt3.7 DatabaseService.kt前言
1 回顾
在系列的第一篇博客中我们介绍了依赖注入的概念以及为什么需要依赖注入。
在系列的第二篇博客中我们介绍了手动依赖注入。我们并不建议在项目中使用手动依赖注入但我们可以通过手动依赖注入的介绍来解释Hilt主要完成的两件事
提供了“containers”容器用来装各种依赖自动管理这些“containers”容器的“lifecycles”生命周期。
在下面的章节中我们主要解决一个问题Hilt 具体应该在项目中怎么使用。
2 Hilt 的相关注释annotations
我们先对 Hilt 涉及到的注释进行一一介绍这些注释包括
HiltAndroidAppAndroidEntryPointInjectViewModelInjectModuleBindsInstallInProvides
2.1 HiltAndroidApp 注释
在我们的安卓工程项目中如果要使用Hilt必须要有一个自定义的Application才行。这里我们的 MyApp.kt 文件中需在该类上方标注 HiltAndroidApp
HiltAndroidApp
class MyApp : Application() {
...
}2.2 AndroidEntryPoint 注释
此注释可以将成员注入到各安卓组件中例如活动activities、片段fragments、视图views、服务services和广播接收器broadcast receivers。
我们必须要使用 AndroidEntryPoint 注释来注释安卓组件才能接下来其中继续注入字段或方法比如 Inject 注释。Hilt 目前支持以下安卓类
Activity使用 HiltAndroidApp 注释标注Fragment使用 AndroidEntryPoint 注释标注View使用 AndroidEntryPoint 注释标注Service使用 AndroidEntryPoint 注释标注Broadcast Receiver使用 AndroidEntryPoint 注释标注
使用 AndroidEntryPoint 注释一个安卓类需要注释所有依赖于它的类。每当我们注释一个 fragment 时我们还必须注释使用该 fragment 的任何 activity。
比如Activity
AndroidEntryPoint
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)}
}对于 Fragment
AndroidEntryPoint
class CountriesListFragment : Fragment() {
...
}2.3 Inject 注释
当项目中的构造函数被 Inject 注释时它就可以作为依赖项在任何地方使用。比如上面 MainActivity.kt 中的 databaseAdapter。也比如下面的例子
AndroidEntryPoint
class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingInjectlateinit var repository: DbRepositoryInjectlateinit var taskAdapter: TaskAdapterInjectlateinit var task: TaskEntityoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)... }
}我们之前提到依赖注入的三种形式
constructor injection类的构造函数注入依赖项是通过类构造函数提供的。setter/field injection类字段注入客户端公开一个 setter 方法注入器使用它来注入依赖项。interface injection依赖项提供了一个注入器方法可以将依赖项注入传递给它的任何客户端。 客户端必须实现一个接口该接口公开一个接受依赖项的 setter 方法。
上面这个例子就是属于第二种。这里要明确说明一点在使用 Hilt 做类字段注入的时候变量名前面是不能加 private 关键字的。因为加了 private 之后这个变量就变成了类私有的变量Hilt 就没有权限访问了也无从谈起依赖注入了。
这也就是我们在使用了 Hilt 之后不需要像手动依赖注入时那样在 Activity 类里面的 onCreate() 函数对这个变量进行赋值。而是使用 inject 注释这个变量以后直接在代码里使用。赋值的事情是由 Hilt 帮我们代劳了。
2.4 ViewModelInject 注释
和 Inject 注释类似的viewModel 里面的构造函数被 ViewModelInject 注释时它就可以作为依赖项在任何地方使用。ViewModelInject 不是来自 Hilt 框架而是来自 Hilt 支持的 Jetpack 库它向 Jetpack 发出 ViewModel 已准备好注入的信号。这个注释是专门为 ViewModel 组件设计的。比如下面的例子
HiltViewModel
class CountriesListViewModel
Inject constructor(private val repository: ApiRepository) : ViewModel() {
...
}2.5 Module 注释
对于接口来说接口又没有构造函数怎么 Inject 呢这个时候我们就需要 Module 注释。
Hilt module 也是一个类但是它需要在类名前面加上注释annotationModule同时还需要加上 InstallIn顾名思义我们拆开来看这个注释Install In后面肯定跟的是一个作用域范围scope意思就是告诉Hilt我们新建的这个 “Module” 类作用范围是什么。这个范围scope的分类我们后续将会提到在这暂且先不用深究。
Module
object ApiModule {
...
}2.6 Binds 注释
前面我们说过接口是没有构造函数的所以也无法使用 constructor injection 的方式进行依赖注入那么我们怎么样才能让 Hilt 知道我们需要这个接口的实例作为依赖呢这时候需要使用注释annotationBinds来告诉 Hilt。不过有一项准备工作我们还要先做那就是先得有一个接口的实现类。这样 Hilt 才会知道使用哪个实现类去实现这个接口相关示例代码如下
interface AnalyticsService {!-- --fun analyticsMethods()
}/*接口AnalyticsService 的实现类需要 Constructor-injected
Hilt 需要使用到*/
class AnalyticsServiceImpl Inject constructor(...
) : AnalyticsService {!-- -- ... }Module
InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {!-- --Bindsabstract fun bindAnalyticsService(analyticsServiceImpl: AnalyticsServiceImpl): AnalyticsService
}AnalyticsService 是一个接口AnalyticsServiceImpl 是 AnalyticsService 接口的实现类同理 AnalyticsServiceImpl 的构造函数前也需要加上注释 Inject 告诉 Hilt 在哪可以找到它。
最后就到我们的 Hilt Module 了它前面有两个注释Module 和 InstallIn(ActivityComponent::class)。这两个注释总结起来就是告诉 Hilt这个 Module 的可见范围是所有的Activity类。因为返回的是 AnalyticsService 接口类型所以我们这里使用抽象类和抽象函数bindAnalyticsService 来实现这个 Hilt Module。
抽象函数 bindAnalyticsService 前面是 Binds 注释它的参数就是接口 AnalyticsService 的实现类 AnalyticsServiceImpl函数返回类型就是接口 AnalyticsService。这样Hilt就可以准确地把它的实例注入到依赖于它的地方了。
总结一下就是对于接口实例的依赖注入需要使用三个注释ModuleInstallIn 和 Binds。
2.7 InstallIn 注释
在 Module 注释的使用过程中我们已经提到了 InstallIn 注释的使用。通过添加 InstallIn 注释我们将此模块类提供的依赖对象的使用限制为特定的安卓组件。以下是另一个例子
Module
InstallIn(SingletonComponent::class)
object ApiModule {
...
}Hilt 提供了七个与 InstallIn 注释兼容的组件。Module 注释告诉 hilt 框架它提供的依赖对象只能在 InstallIn 注释中命名的特定组件的生命周期内被注入和使用。
InstallIn(ApplicationComponent) — present for the lifetime of the application.InstallIn(ActivityComponent) — present for the lifetime of the Activity.InstallIn(ActivityRetainedComponent) — present for the lifetime of a configuration surviving activity (i.e) surviving orientation changes just like the ViewModel.InstallIn(FragmentComponent) — present for the lifetime of the fragment.InstallIn(ServiceComponent) — present for the lifetime of the service.InstallIn(ViewComponent) — present for the lifetime of the view that is directly inside an activity.InstallIn(ViewWithFragmentComponent) — present for the lifetime of the view inside a fragment.
2.8 Provides 注释
当我们需要对第三方库进行依赖注入的时候就不能使用注释annotationBinds 了为了以示区分Hilt 提供了一个新的注释 Provides。
其实 Hilt 的注释取名还是很考究的接口是需要“绑定”Binds的而第三方库是需要“提供”Provides的。
假如一个 AnalyticsService 类实现了一个第三方库 Retrofit这时候我们只需要在 Hilt 的 Module 中实现一个函数函数名可以任意起目的是要告诉 Hilt怎么来构建这个 AnalyticsService 类的实例对象示例代码如下
Module
InstallIn(ActivityComponent::class)
object AnalyticsModule {!-- --Providesfun provideAnalyticsService(): AnalyticsService {!-- --return Retrofit.Builder().baseUrl(https://example.com).build().create(AnalyticsService::class.java)}
}与前面的注解 Binds 例子相比在这个例子中主要是把 Binds 改成了 Provides函数 provideAnalyticsService 返回的是一个使用 Retrofit 构建的 AnalyticsService 类的实例。
这样Hilt 就可以在需要 AnalyticsService 类实例依赖的地方把它的实例注入进去。
3 Hilt 的简单案例
在这里我们介绍一个非常简单的 Hilt 案例大家可以跟着我们一起在 Android Studio 上新建一个安卓的应用然后按照下面的步骤一步步添加相关依赖和代码。
3.1 AndroidManifest.xml
Hilt 框架会在我们首次构建项目时为项目创建一个基类 Hilt_MyApp。 我们不需要直接扩展基类因为它会自动创建带注释的类 MyApp。这里我们需要在 AndroidManifest.xml 文件中添加 android:name.MyApp如下所示
applicationandroid:name.MyApp... activity android:name.MainActivityintent-filteraction android:nameandroid.intent.action.MAIN /category android:nameandroid.intent.category.LAUNCHER //intent-filter/activity
/application3.2 build.gradle (Project)
我们需要在 build.gradle (Project) 中添加 Hilt 对应的 classpath。代码如下
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {dependencies {classpath com.google.dagger:hilt-android-gradle-plugin:2.43.2}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {id com.android.application version 7.2.0 apply falseid com.android.library version 7.2.0 apply falseid org.jetbrains.kotlin.android version 1.6.10 apply false
}task clean(type: Delete) {delete rootProject.buildDir
}3.3 build.gradle (Module)
我们需要在 build.gradle (Module) 中添加 Hilt 对应的依赖
implementation com.google.dagger:hilt-android:2.43.2
kapt com.google.dagger:hilt-compiler:2.43.2并且 plugins 中也需要添加
id kotlin-kapt
id dagger.hilt.android.plugin代码如下
plugins {id com.android.applicationid org.jetbrains.kotlin.androidid kotlin-kaptid dagger.hilt.android.plugin
}android {compileSdk 32defaultConfig {applicationId com.example.hiltex1minSdk 27targetSdk 32versionCode 1versionName 1.0testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget 1.8}
}dependencies {implementation androidx.core:core-ktx:1.7.0implementation androidx.appcompat:appcompat:1.3.0implementation com.google.android.material:material:1.4.0implementation androidx.constraintlayout:constraintlayout:2.0.4testImplementation junit:junit:4.13.2androidTestImplementation androidx.test.ext:junit:1.1.3androidTestImplementation androidx.test.espresso:espresso-core:3.4.0//Hiltimplementation com.google.dagger:hilt-android:2.43.2kapt com.google.dagger:hilt-compiler:2.43.2
}3.4 Application Class MyApp.kt
在我们的安卓工程项目中如果要使用Hilt必须要有一个自定义的Application才行。这里我们的 MyApp.kt 文件中需在该类上方标注 HiltAndroidApp
HiltAndroidApp
class MyApp : Application() {
...
}3.5 MainActivity.kt
在这里我们写一个很简单的 MainActivity.kt 文件代码如下
AndroidEntryPoint
class MainActivity : AppCompatActivity() {Injectlateinit var databaseAdapter: DatabaseAdapteroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)Log.d(TAG,DatabaseAdapter : $databaseAdapter)databaseAdapter.log(Hey Hilt)}
}这里我们使用到了 AndroidEntryPoint 注释。AndroidEntryPoint 也就是上是其所相关联的依赖项的入口点EntryPoint。 我们需要告诉 Hilt 我们希望在何处注入依赖项。这就是AndroidEntryPoint的作用。它定义了将在何处提供依赖项。
Hilt 在这里的主要目的是尝试标准化注入功能并消除尽可能多的组件耦合。AndroidEntryPoint 会自动为系统构建这些依赖项和组件。 所以我们不必再像前一章节那样去进行手动的依赖注入。当然需要再强调一次如果我们使用 AndroidEntryPoint 注释 fragment 并且它包含依赖项则还必须注释使用到该 fragment 的 Activities。即任何依赖于我们正在注释的这个类的相关类依赖项组件等同样需要被注释。
在我们往下看其他代码之前关注一下这行代码
Inject
lateinit var databaseAdapter: DatabaseAdapter看似平平无奇但实际上这就是前面提到依赖注入的三种形式之一setter/field injection类字段注入。在这个例子中我们希望将 databaseAdapter 注入到 main activity 中。
3.6 DatabaseAdapter.kt
这里有提到依赖注入的三种形式之一constructor injection类的构造函数注入。在 DatabaseAdapter.kt 中我们就要使用到
class DatabaseAdapter Inject constructor(var databaseService: DatabaseService) {fun log(msg: String){Log.d(TAG,DatabaseAdapter : $msg)databaseService.log(msg)}
}创建依赖dependency的最基本、最简单的方法是通过构造函数注入。 一个组件component本质上是一个不带参数的类我们只需要添加 inject 注解这样就可以随时将其注入到需要的地方又比如 class SampleClass Inject constructor()。
3.7 DatabaseService.kt
这也是一个类的构造函数注入的例子。
class DatabaseService Inject constructor() {fun log(msg: String){Log.d(TAG,DatabaseService msg : $msg)}
}大家有没有发现在用了 Hilt 之后依赖注入的使用变得非常简单代码也变得非常简洁我们可以对比一下系列2中的手动依赖注入案例。