Hilt的使用(基本用法)二

一、前言

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

四、依赖传递@Module的使用

刚才简单的介绍了Hilt的使用,但是有时候类的生成不仅仅是无参构造,有时候是有参构造。这里演示如何注入参数。官方提供了两种方式进行注入参数@Binds@Providers。分别针对不同场景这里进行分别演示。需要注意的是不管那种方式都需要使用@Module进行标注

@Binds

创建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

@Provides

使用@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}.
 *
 * <p>Example usage:
 *
 * <pre>
 *   public class Car {
 *     &#064;Inject <b>@Named("driver")</b> Seat driverSeat;
 *     &#064;Inject <b>@Named("passenger")</b> Seat passengerSeat;
 *     ...
 *   }</pre>
 */
@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 ) { ... } 

这些限定符可以直接使用不用做其它处理

六、@EntryPoint的使用

在官方文档中知道Hilt目前支持以下类
Hilt 目前支持以下 Android 类:

  • Application(通过使用 @HiltAndroidApp)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

所以如果想要使用@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()
    }

}

这些代码也是可以运行的。
这里简单附录一张快速使用参考指南
在这里插入图片描述

七、参考链接

  1. 使用 Hilt 实现依赖项注入
  2. 在 Android 应用中使用 Hilt
  3. 使用 Hilt 在 Android 上进行依赖注入
  4. Gradle 构建设置
  5. Plugin [id: ‘dagger.hilt.android.plugin’] was not found in any of the following sources
  6. dagger
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值