Hilt
是使用Dragger2
做的封装,目前也是官方推荐的使用库。这里记录下如何使用这个Hilt
这个库。这里需要注意的是Hilt
还用了一部分javax.inject
的代码,但是网上没有详细使用javax.inject
库的方式,所以目前认为这个库不能单独使用。
项目跟目录下的gradle文件配置如下
build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.41'
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
在gradle升级后插件的依赖方式修改了,这里贴出新版的依赖方式
plugins {
id 'com.android.application' version '7.1.3' apply false
id 'com.android.library' version '7.1.3' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
id 'com.google.dagger.hilt.android' version '2.41' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
app包的内容不需要修改。
app包下面的配置如下
build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation "com.google.dagger:hilt-android:2.41"
kapt "com.google.dagger:hilt-android-compiler:2.41"
testImplementation 'junit:junit:4.+'
implementation 'androidx.webkit:webkit:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
这里演示一个简单的依赖注入,用尽可能少的代码完成一个demo,然后再逐步完善
首先创建一个Application
App.kt
@HiltAndroidApp
class App: Application()
其次是一个普通的类
DateFormatter.kt
class DateFormatter @Inject constructor(){
fun testDateFormatter(){
Log.e("YM--->","---->获取DateFormatter")
}
}
编写一个页面
HiltActivity.kt
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
@Inject
lateinit var dateFormatter: DateFormatter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
dateFormatter.testDateFormatter()
}
}
以上就是一个完整的例子。
这里对代码进行下简单的解释,使用Hilt
必须要用到Application,然后用@HiltAndroidApp
进行绑定。其次页面所在的类必须使用@AndroidEntryPoint
进行绑定。注入的点的地方需要使用@Inject
进行关联
其中@AndroidEntryPoint
不仅仅可以绑定到Activity
,还可以绑定到其他地方,具体参考
https://developer.android.google.cn/training/dependency-injection/hilt-android
刚才简单的介绍了Hilt
的使用,但是有时候类的生成不仅仅是无参构造,有时候是有参构造。这里演示如何注入参数。官方提供了两种方式进行注入参数@Binds
和@Providers
。分别针对不同场景这里进行分别演示。需要注意的是不管那种方式都需要使用@Module
进行标注
创建AnalyticsService.kt
iinterface AnalyticsService
class AnalyticsServiceImpl @Inject constructor() : AnalyticsService
HiltModule,kt
@InstallIn(SingletonComponent::class) //表示全局,具体生命周期可以看文档进行选择
@Module
abstract class HiltModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
LoggerLocalDataSource.kt
@Singleton
class LoggerLocalDataSource @Inject constructor(private val service: AnalyticsService){
fun printLocalDataSource(){
Log.e("Ym","----->LoggerLocalDataSource")
}
}
HiltActivity.kt
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
@Inject lateinit var logger: LoggerLocalDataSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
logger.printLocalDataSource()
}
}
这里需要注意的是被@Binds
这个标注的函数必须是抽象函数。而且这个函数必须有一个参数,一个返回值,返回值必须是参数的接口或者父类,不能是同一种类型。参数的类型定义必须提供可被Hilt
初始化的方式, 比如使用@Inject
使用@Binds
的时候发现,最终创建的类必须要用到Hilt
去初始化,倘若这个数据类型是第三方提供的,那么我们就不能初始化,所以官方提供了@Providers
。演示如下:
LogDao.kt
class LogDao
DatabaseModule.kt
@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext appContext: Context): AppDatabase {
return AppDatabase()
}
// @Provides
// fun provideLogDao(database: AppDatabase): LogDao {
// return database.logDao()
// }
@Provides
fun provideLogDao(): LogDao {
return LogDao()
}
}
LoggerLocalDataSource.kt
@Singleton
//表示单例,生命周期持续整个应用期间,
//同@InstallIn(SingletonComponent::class)类似,后面再记录两者区别
class LoggerLocalDataSource @Inject constructor(private val service: LogDao){
fun printLocalDataSource(){
Log.e("Ym","----->LoggerLocalDataSource")
}
}
HiltActivity.kt
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
@Inject lateinit var logger: LoggerLocalDataSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
logger.printLocalDataSource()
}
}
需要注意的是被@Provides
绑定的函数必须是静态函数,传入参数的话可以有也可以没有,如果有的话,需要再使用一个@Provides
提供一个函数返回需要的参数,见上面注释的代码示例。这样就可以看到不要求生成的对象的类型也被Hilt
标记了。
假如有这么一种场景,同一个接口有多种实现,那么Hilt
在进行创建对象的时候就需要使用限定符@Qualifier
进行区分。这里的代码继续对上述代码进行改动
LoggerDataSource.kt
interface LoggerDataSource {
fun type()
}
LoggerLocalDataSource.kt
@Singleton
class LoggerLocalDataSource @Inject constructor(private val service: LogDao): LoggerDataSource{
fun printLocalDataSource(){
Log.e("Ym","----->LoggerLocalDataSource")
}
override fun type() {
Log.e("Ym","---type-->LoggerLocalDataSource")
}
}
LoggerInMemoryDataSource.kt
class LoggerInMemoryDataSource @Inject constructor() : LoggerDataSource {
override fun type() {
Log.e("Ym","---type-->LoggerInMemoryDataSource")
}
}
LoggerModule.kt
@Qualifier
annotation class InMemoryLogger
@Qualifier
annotation class DatabaseLogger
@InstallIn(SingletonComponent::class)
@Module
abstract class LoggerModule {
// @DatabaseLogger
@Named("local")
@Binds
abstract fun bindLocalDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
// @InMemoryLogger
@Named("memory")
@Binds
abstract fun bindMemoryDatabaseLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}
HiltActivity.kt
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
// @DatabaseLogger
@Named("local")
@Inject
lateinit var loggerLocal: LoggerDataSource
// @InMemoryLogger
@Named("memory")
@Inject
lateinit var loggerInMemory: LoggerDataSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
loggerLocal.type()
loggerInMemory.type()
}
}
需注意的是用来区分不同实现的话可以使用@Qualifier
和@Named("memory")
两种方式。其@Named("memory")
是对@Qualifier
的进一步封装,代码如下:
package javax.inject;
import java.lang.annotation.Retention;
import java.lang.annotation.Documented;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* String-based {@linkplain Qualifier qualifier}.
*
* Example usage:
*
*
* public class Car {
* @Inject @Named("driver") Seat driverSeat;
* @Inject @Named("passenger") Seat passengerSeat;
* ...
* }
*/
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
以下参考官方文档:
Hilt 提供了一些预定义的限定符。例如,由于您可能需要来自应用或 Activity 的 Context 类,因此 Hilt 提供了
@ApplicationContext 和 @ActivityContext 限定符。假设本例中的 AnalyticsAdapter 类需要 Activity 的上下文。以下代码演示了如何向 AnalyticsAdapter
提供 Activity 上下文:@ActivityContext private val context: Context, private val service: AnalyticsService ) { ... }
这些限定符可以直接使用不用做其它处理
在官方文档中知道Hilt
目前支持以下类
Hilt 目前支持以下 Android 类:
所以如果想要使用@Inject
对某个对象进行依赖注入需要跟上面的类有关联,否则是无法进行初始化的。这里我们编写以下代码进行验证:
User.kt
class User @Inject constructor() {
val name = "张三"
}
HiltActivity.kt
class HiltActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
val user = getUser(this)
Log.e("YM---->","--->用户信息:${user.name}")
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ExampleDaoEntryPoint {
fun findUser(): User
}
private fun getUser(appContext: Context): User {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
appContext,
ExampleDaoEntryPoint::class.java
)
return hiltEntryPoint.findUser()
}
}
这个代码是模拟一种不受支持的环境,可以看到这个代码是没有使用@AndroidEntryPoint
标记的。然后生成的User是被@Inject
标记的,所以换而言之,可以使用这种方式生成任何被Hilt
创建的对象,比如前文提到的LoggerDataSource
接口的具体对象。所以这些代码可以用在其他不被Hilt
支持的类里面。这里改成前文的LoggerInMemoryDataSource
对象进行演示
class HiltActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hilt)
val user = getUser(this)
user.type()
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ExampleDaoEntryPoint {
@Named("memory")
fun findUser(): LoggerDataSource
}
private fun getUser(appContext: Context): LoggerDataSource {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
appContext,
ExampleDaoEntryPoint::class.java
)
return hiltEntryPoint.findUser()
}
}
这些代码也是可以运行的。
这里简单附录一张快速使用参考指南