Android Jetpack에서 Hilt 사용

Hilt는 프로젝트에서 수동 종속성 주입을 수행하는 상용구 코드를 줄이는 Android용 종속성 주입 라이브러리입니다. 수동 종속성 주입을 수행하려면 각 클래스와 해당 종속성을 수동으로 구성하고 컨테이너의 도움으로 종속성을 재사용 및 관리해야 합니다.

Hilt는 프로젝트의 모든 Android 클래스에 대한 컨테이너를 제공하고 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI(종속성 주입)를 사용하는 표준 방법을 제공합니다. Hilt는 인기 있는 DI 라이브러리 Dagger를 기반으로 구축되었으므로 Dagger의 컴파일 시간 정확성, 런타임 성능, 확장성 및 Android Studio 지원의 이점을 누릴 수 있습니다. 이 문서에서는 사용 방법만 설명합니다. 단계는 다음과 같습니다.

프로젝트에 Hilt를 도입합니다.

아래에project/build.gradle Kotlin 및 Hilt 플러그인을 추가하세요.

buildscript {
    ext.kotlin_version = '1.5.31'
    ext.hilt_version = '2.40'
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.0.3'
        //kotlin编译插件
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        //hilt编译插件
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

app/build.gradleKotlin에 가입하고 아래에서

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-parcelize'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    compileSdkVersion 31
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.android.hilt"
        minSdkVersion 16
        targetSdkVersion 31
        versionCode 1
        versionName "1.0"


        javaCompileOptions {
            annotationProcessorOptions {
                arguments["room.incremental"] = "true"
            }
        }
    }

    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
    implementation 'androidx.recyclerview:recyclerview:1.2.1'

    // Room
    implementation "androidx.room:room-runtime:2.3.0"
    kapt "androidx.room:room-compiler:2.3.0"

    // Hilt dependencies
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}

프로젝트에서 힐트를 사용하세요.

1단계: @HiltAndroidApp주석 사용

종속성 주입을 사용할 수 있는 애플리케이션 기본 클래스를 포함하여 상속된 새 클래스를 만들고 Application주석을 추가하여 Hilt의 코드 생성을 트리거합니다. @HiltAndroidApp앱 컨테이너는 앱의 상위 컨테이너입니다. 즉, 다른 컨테이너가 앱이 제공하는 종속성에 액세스할 수 있음을 의미합니다.

@HiltAndroidApp 
class LogApplication : Application()

2단계: @AndroidEntryPointAndroid 클래스에 종속성을 주입하는 데 사용합니다.

Application 클래스에서 Hilt를 설정하고 애플리케이션 수준 구성요소를 보유한 후 Hilt는 @AndroidEntryPoint주석을 사용하여 다른 Android 클래스에 대한 종속성을 제공할 수 있습니다. Hilt는 현재 다음 Android 클래스를 지원합니다.

  • 애플리케이션(@HiltAndroidApp 사용)
  • 활동
  • 파편
  • 보다
  • 서비스
  • 방송수신기

Android 클래스에 주석을 추가 하는 경우 @AndroidEntryPoint해당 클래스에 종속된 Android 클래스에도 주석을 추가해야 합니다. 예를 들어 Fragment조각에 주석을 추가하는 경우 해당 조각을 사용하는 모든 활동에도 주석을 달아야 합니다.

@AndroidEntryPoint 
class LogsFragment : Fragment() { .... }

3단계: 필드 주입에 힐트 사용

@Inject주석을 사용하면 Hilt가 다양한 유형의 인스턴스를 삽입할 수 있습니다. 실제로 이 주석은 변수를 선언할 때 사용됩니다.

@AndroidEntryPoint
class LogsFragment : Fragment() {

    @Inject lateinit var logger: LoggerLocalDataSource
    @Inject lateinit var dateFormatter: DateFormatter

    ...
}

4단계: Hilt는 예시를 제공합니다.

step4-condition1: 생성자를 사용하여 @Inject인스턴스를 얻습니다.

주석이 달린 변수 의 경우 @Inject인스턴스를 제공할 때 생성자를 통해 생성된 인스턴스 인 경우 생성자 의 주석을 직접 사용하여 @InjectHilt가 다음 DateFormatter와 같은 클래스의 인스턴스를 생성하도록 할 수 있습니다.

/**
 * 通过构造器创建依赖
 */
class DateFormatter @Inject constructor() {

    @SuppressLint("SimpleDateFormat")
    private val formatter = SimpleDateFormat("d MMM yyyy HH:mm:ss")

    fun formatDate(timestamp: Long): String {
        return formatter.format(Date(timestamp))
    }
}

또 다른 예는 Step3에 있는 것입니다 logger. DateFormatter와 차이점은 구성 매개변수에 매개변수가 있다는 것입니다. 따라서 이 경우 Hilt에게 LogDao 인스턴스를 가져오는 방법도 알려주어야 합니다. 즉, 생성자를 통해 LogDao를 구축할 수 있다면 @Inject주석을 직접 추가하면 됩니다. 하지만 여기의 logDao는 인터페이스이므로 구현 클래스를 수동으로 추가할 수 없습니다(Android 방의 DAO입니다). 그래서 우리는 다른 방법을 사용하여 획득해야 합니다.

@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao) {
    ...
}
step4-condition2: 다음과 같은 @Provides예시를 제공합니다.

@ProvidesHilt 모듈의 함수에 주석을 달아 생성자를 주입할 수 없는 유형을 제공하는 방법을 Hilt에 알릴 수 있습니다 . hilt 모듈은 주석이 달린 클래스를 사용 @Module 합니다 @InstallIn . (범위 지정)을 통해 인스턴스를 제공하기 위해 생성자@Inject 에 Annotation을 추가하여 객체 인스턴스 제공 방법을 선언할 수 없습니다 . 이것은 모듈입니다. 모듈을 사용하여 Hilt에 바인딩을 추가해야 합니다. 즉, Hilt에 다양한 유형의 인스턴스를 제공하는 방법을 알려줍니다 . Hilt 모듈에서는 프로젝트에 포함되지 않은 인터페이스나 클래스와 같이 생성자를 삽입할 수 없는 유형에 대한 바인딩을 추가합니다. 예를 들어 OkHttpClient - 인스턴스를 생성하려면 해당 빌더를 사용해야 합니다. 여기서는 실제로 데이터베이스 연산을 제공하기 때문에 범위가 전역적이어야 하므로 이를 사용한다 . 여기에는 다른 구성 요소가 있습니다.@Module@InstallInModuleSingletonComponent

@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
//这个可以是个class,但是在 Kotlin 中,只包含 @Provides 函数的模块可以是 object 类。
//这样,提供程序即会得到优化,并几乎可以内联在生成的代码中。

    /**
     * 用 @Provides 提供实例。我们可以在 Hilt 模块中用 @Provides 注释函数,
     * 以告诉 Hilt 如何提供无法注入构造函数的 类型。
     */
    @Provides
    fun provideLogDao(database: AppDatabase): LogDao {
//
        return database.logDao()
        //Hilt 可从上述代码中得知,在提供 LogDao 的实例时需要执行 database.logDao()。
        //由于我们拥有 AppDatabase 作为传递依赖项,因此我们还需要告诉 Hilt 如何提供这种类型的实例。
    }

    //因为我们一直希望 Hilt 提供相同的数据库实例,所以我们用 @Singleton 注释 @Provides provideDatabase 方法。
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context):AppDatabase{
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "logging.db"
        ).build()
    }

}
step4-condition3: @Binds인터페이스를 제공하기 위해 사용합니다.

인터페이스에는 생성자 주입을 사용할 수 없습니다. 인터페이스에 사용할 구현을 Hilt에 알리려면 Hilt 모듈 내의 함수에 @Binds주석을 사용하면 됩니다. 추상 함수에는@Binds 주석을 달아야 합니다 (함수는 추상이므로 코드가 없으며 클래스도 추상이어야 합니다). 추상 함수의 반환 유형은 구현을 제공하려는 인터페이스(예: AppNavigator)입니다. 구현은 인터페이스 구현 유형(예: AppNavigatorImpl)과 함께 고유한 매개변수를 추가하여 지정됩니다. 예를 들어 MainActivity에서 우리가 의존하는 인터페이스는

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var navigator: AppNavigator
    ....
}

따라서 이를 위해 유형에 범위가 있는 경우 @Binds 메소드에 범위 주석이 있어야 하는 새로운 module용도를 만들어야 합니다.@Binds

//我们的新导航信息(即 AppNavigator)需要特定于 Activity 的信息
//(因为 AppNavigatorImpl 拥有 Activity 作为依赖项)。
// 因此,我们必须将其安装在 Activity 容器中,而不是安装在 Application 容器中,因为这是有关 Activity 的信息所在。
@InstallIn(ActivityComponent::class)
@Module
abstract class NavigationModule {

    @Binds
    abstract fun provideNavigator(impl: AppNavigatorImpl):AppNavigator
    //参数为具体的实现类,所以要告知hilt如何提供实现类的实例。下面的实现类通过构造函数提供实例
}


//======AppNavigatorImpl.ktx========//

//AppNavigatorImpl 会依赖于 FragmentActivity。由于系统会在 Activity 容器中提供 AppNavigator 实例
// (亦可用于 Fragment 容器和 View 容器,因为 NavigationModule 会安装在 ActivityComponent 中),所以 FragmentActivity 目前可用
class AppNavigatorImpl @Inject constructor(private val activity: FragmentActivity) : AppNavigator {

    override fun navigateTo(screen: Screens) {
        val fragment = when (screen) {
            Screens.BUTTONS -> ButtonsFragment()
            Screens.LOGS -> LogsFragment()
        }

        activity.supportFragmentManager.beginTransaction()
            .replace(R.id.main_container, fragment)
            .addToBackStack(fragment::class.java.canonicalName)
            .commit()
    }
}
step4-condition4: 한정자 사용

동일한 유형의 다양한 구현(다중 결합)을 제공하는 방법을 Hilt에 알리려면 한정자를 사용할 수 있습니다. 그 정의는 실제로 주석입니다.

@Qualifier 
annotation class InMemoryLogger 
@Qualifier 
annotation class DatabaseLogger

예를 들어 로그 추가 및 삭제 확인을 위한 메모리 기반 구현 방법을 제공하려는 경우 인터페이스를 정의합니다.

interface LogDataSource {
    fun addLog(msg: String)
    fun getAllLogs(callback: (List<Log>) -> Unit)
    fun removeLogs()
}

Room 기반 구현은 다음과 같으며 실제로는 처음에 언급한 구현이지만 인터페이스만 구현한 것이다.

@Singleton
class LoggerLocalDataSource @Inject constructor(private val logDao: LogDao):LogDataSource {

    private val executorService: ExecutorService = Executors.newFixedThreadPool(4)
    private val mainThreadHandler by lazy {
        Handler(Looper.getMainLooper())
    }

    override fun addLog(msg: String) {
        executorService.execute {
            logDao.insertAll(
                Log(
                    msg,
                    System.currentTimeMillis()
                )
            )
        }
    }

    override fun getAllLogs(callback: (List<Log>) -> Unit) {
        executorService.execute {
            val logs = logDao.getAll()
            mainThreadHandler.post { callback(logs) }
        }
    }

    override fun removeLogs() {
        executorService.execute {
            logDao.nukeTable()
        }
    }
}

메모리 기반 구현은 다음과 같습니다.

@ActivityScoped
class LoggerInMemoryDataSource @Inject constructor():LogDataSource {
    private val logs = LinkedList<Log>()

    override fun addLog(msg: String) {
        logs.addFirst(Log(msg, System.currentTimeMillis()))
    }

    override fun getAllLogs(callback: (List<Log>) -> Unit) {
        callback(logs)
    }

    override fun removeLogs() {
        logs.clear()
    }
}

위의 소개를 바탕으로 인터페이스를 사용할 때 구현 클래스를 다음과 같이 정의합니다.

@Module
@InstallIn(SingletonComponent::class)
abstract class LoggingDatabaseModule {
    @DatabaseLogger
    @Binds
    @Singleton
    abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LogDataSource
}

@Module
@InstallIn(ActivityComponent::class)
abstract class LoggingInMemoryModule {
    @InMemoryLogger
    @Binds
    @ActivityScoped
    abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LogDataSource
}

두 개의 모듈을 정의한 것을 볼 수 있는데 module, 두 모듈이 하나의 모듈이 아닌 이유는 두 구현의 범위가 다르기 때문입니다 . 그리고 구현 클래스에 범위가 지정되어 있기 때문에 필요한 InMemory 메서드 @Binds에도 추가했습니다 . 같은 방식으로 여기에 인스턴스를 제공하기 위해 어떤 방법을 선택해야 하는지 알려주는 맞춤 주석도 추가했습니다. 한정자가 추가되지 않으면 오류가 보고됩니다. 이 인터페이스의 실제 사용은 다음과 같습니다.@ActivityScopedInMemoryLogger

class ButtonsFragment : Fragment() {
    @InMemoryLogger
    @Inject lateinit var logger: LogDataSource
    ...
}

3단계 와의 차이점은 여기서 변수의 타입이 특정 구현이 아닌 인터페이스이고, 두 번째로 한정자가 추가된다는 점을 알 수 있습니다 . 요약하면 Hilt의 기본 사용법은 다음과 같습니다.

안드로이드 학습 노트

Android 성능 최적화 기사: https://qr18.cn/FVlo89
Android 차량 기사: https://qr18.cn/F05ZCM
Android 역방향 보안 연구 노트: https://qr18.cn/CQ5TcL
Android 프레임워크 기본 원칙 기사: https://qr18.cn/AQpN4J
Android 오디오 및 비디오 기사: https://qr18.cn/Ei3VPD
Jetpack 제품군 버킷 기사(Compose 포함): https://qr18.cn/A0gajp
Kotlin 기사: https://qr18.cn/CdjtAF
Gradle 기사: https://qr18.cn/DzrmMB
OkHttp 소스 코드 분석 메모: https://qr18.cn/Cw0pBD
Flutter 기사 : https://qr18.cn/DIvKma
Android의 8가지 지식 기관: https://qr18.cn/CyxarU
Android 핵심 노트: https://qr21.cn/CaZQLo
전년도 Android 면접 질문: https://qr18.cn/CKV8OZ
2023년 최신 Android 면접 질문: https://qr18.cn/CgxrRy
Android 차량 개발 직책 면접 연습: https://qr18.cn/FTlyCJ
오디오 및 비디오 인터뷰 질문:https://qr18.cn/AcV6Ap

Acho que você gosta

Origin blog.csdn.net/maniuT/article/details/132714544
Recomendado
Clasificación