Jetpack Hilt 프레임워크의 기본 사용

힐트란?

Hilt는 2020년에 Jetpack 제품군에 합류한 강력하고 사용하기 쉬운 종속성 주입 프레임워크입니다. Android 팀이 Dagger2 팀과 접촉하여 개발한 Android 전용 의존성 주입 프레임워크입니다. Dagger2와 비교할 때 Hilt의 가장 분명한 특징은 단순성이며 Android 전용 API를 제공합니다.

프로젝트에 Hilt 도입

이 섹션에서는 Java 17을 사용하는 새로운 Jetpack Compose 프로젝트를 예로 들어 개발 도구는 Android Studio 2023.1.1 Canary 버전을 사용합니다. 2023년 5월 현재 정보입니다.

첫 번째 단계는 gradle/libs.versions.toml 파일을 열고 Hilt의 Gradle 플러그인 관련 구성을 추가하는 것입니다.

[versions]
hilt = "2.46.1"

[plugins]
hiltAndroid = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

그런 다음 프로젝트의 build.gradle.kts 파일을 열고 플러그인을 가져옵니다.

plugins {
    alias(libs.plugins.hiltAndroid) apply false
}

두 번째 단계는 libs.versions.toml에 Hilt의 플러그인 및 종속 라이브러리를 추가하는 것입니다.

[libraries]
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }

Hilt는 컴파일 시간 주석을 기반으로 구현되므로 kotlin-kapt 플러그인을 추가해야 합니다. 앱의 build.gradle.kts 파일에 다음 구성을 추가합니다.

plugins {
    kotlin("kapt")
}

android {
    compileOptions {
        // 这里设置为 Java 8 或以上即可
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

dependencies {
    implementation(libs.hilt.android)
    kapt(libs.hilt.compiler)
}

이제 Hilt가 프로젝트에 성공적으로 도입되었습니다.

Hilt 기본 사용법

준비

Hilt를 사용할 때 애플리케이션 클래스를 사용자 지정해야 합니다. 그렇지 않으면 Hilt가 제대로 작동하지 않습니다. 사용자 지정 Application 클래스에 코드를 작성할 필요는 없지만 @HiltAndroidApp 주석을 추가해야 합니다.

@HiltAndroidApp
class MyApplication : Application() {
}

다음으로 AndroidManifest.xml에 MyApplication을 등록합니다.

<application
    android:name=".MainApplication">
</application>

준비 작업이 완료되었으며 다음 작업은 특정 비즈니스 로직에 따라 종속성 주입을 위해 Hilt를 사용하는 것입니다.

진입 지점

Hilt는 Dagger2의 작업을 단순화하므로 브리지 레이어 논리를 작성하기 위해 @Component 주석을 사용할 필요가 없으며 삽입 기능을 몇 가지 Android 고정 진입점(Application, Activity, Fragment, View)에서만 시작하도록 제한합니다. , 서비스, BroadcastReceiver.
이 중 Application 진입점만 @HiltAndroidApp 주석을 사용하여 선언하고 나머지 모든 진입점은 @AndroidEntryPoint 주석을 사용하여 선언합니다. 예를 들어 Activity에서 종속성 주입을 수행하려는 경우 다음과 같이 선언하기만 하면 됩니다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {        
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

매개변수 없이 의존성 주입

다음과 같이 클래스를 정의하고 해당 생성자에서 @Inject 주석을 선언합니다.

class MusicPlayer() @Inject constructor() {
    fun init() {
        Log.d("MusicPlayer", "init")
    }
}

Inject in Activity 위에서 작성한 init() 메서드를 성공적으로 호출할 수 있습니다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var musicPlayer: MusicPlayer

    override fun onCreate(savedInstanceState: Bundle?) {        
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        musicPlayer.init()
    }
}

매개변수를 사용한 의존성 주입

다음과 같이 플레이어 구성 요소가 의존하는 시스템 오디오 드라이버를 나타내는 위의 MusicPlayer 클래스 생성자에 AudioDriver 매개 변수를 추가합니다.

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    fun init() {
        Log.d("MusicPlayer", "init, audioDriver=$audioDriver")
    }
}

AudioDriver 클래스를 선언할 때 생성자에 @Inject 주석도 추가합니다.

class AudioDriver @Inject constructor() {}

코드를 수정하지 않고도 init() 메서드를 성공적으로 호출할 수 있고 audioDriver의 hashCode를 성공적으로 출력할 수 있습니다.

인터페이스 종속성 주입

오디오를 재생할 때 필요한 오디오 디코더를 나타내는 IDecoder 인터페이스를 정의합니다. 인터페이스에서 구현되는 두 가지 방법이 있으며 각각 디코더를 생성 및 파괴하고 메모리를 해제하는 데 사용됩니다.

interface IDecoder {
    fun create()
    fun destroy()
}

WAV 파일을 디코딩하기 위해 WavDecoder를 구현하고 생성자에 @Inject 주석을 추가합니다.

class WavDecoder @Inject constructor() : IDecoder {
    override fun create() {
        Log.d("WavDecoder", "create")
    }
    
    override fun destroy() {
        Log.d("WavDecoder", "destroy")
    }
}

또한 MP3 파일을 디코딩하기 위해 Mp3Decoder를 구현하려면 @Inject 주석도 선언해야 합니다.

class Mp3Decoder @Inject constructor() : IDecoder {
    override fun create() {
        Log.d("Mp3Decoder", "create")
    }
    
    override fun destroy() {
        Log.d("Mp3Decoder", "destroy")
    }
}

DecoderModule이라는 새 추상 클래스를 만들고 이 모듈에서 추상 함수를 정의하여 IDecoder 인터페이스의 필수 인스턴스를 제공합니다.

@Module
@InstallIn(ActivityComponent::class)
abstract class DecoderModule {
    @Binds
    abstract fun bindDecoder(wavDecoder: WavDecoder): IDecoder
}

방금 제공된 디코더를 호출하도록 MusicPlayer 클래스의 코드를 수정합니다.

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    @Inject
    lateinit var decoder: IDecoder

    fun init() {
        decoder.create()
        Log.d("MusicPlayer", "init, audioDriver=$audioDriver")
        decoder.destroy()
    }
}

이때 다시 init() 메소드를 호출하면 TAG가 WavDecoder인 로그를 볼 수 있다.

동일한 유형의 다른 인스턴스 주입

@Qualifer 인터페이스는 동일한 유형의 클래스 또는 인터페이스의 다른 인스턴스를 주입하는 데 사용됩니다. 다음과 같이 두 개의 주석을 개별적으로 정의합니다.

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindWavDecoder

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindMp3Decoder

DecoderModule로 돌아가서 두 개의 추상 함수를 정의하고 두 함수 위에 방금 정의한 두 개의 주석을 추가합니다.

@Module
@InstallIn(ActivityComponent::class)
abstract class DecoderModule {
    @BindWavDecoder
    @Binds
    abstract fun bindWavDecoder(wavDecoder: WavDecoder): IDecoder
    
    @BindMp3Decoder
    @Binds
    abstract fun bindMp3Decoder(mp3Decoder: WavDecoder): IDecoder
}

MusicPlayer 클래스로 돌아가서 플레이어가 동시에 두 가지 디코딩 형식을 지원하도록 할 수 있습니다.

class MusicPlayer() @Inject constructor(val audioDriver: AudioDriver) {
    @BindWavDecoder
    @Inject
    lateinit var wavDecoder: IDecoder
    
    @BindMp3Decoder
    @Inject
    lateinit var mp3Decoder: IDecoder

    fun init() {
        wavDecoder.create()
        mp3Decoder.create()
        Log.d("MusicPlayer", "init, audioDriver=$audioDriver")
        wavDecoder.destroy()
        mp3Decoder.destroy()
    }
}

타사 클래스의 종속성 주입

OkHttp가 제공하는 MainActivity에 OkHttpClient를 주입하려는 경우 생성자에 @Inject 주석을 추가할 수 없습니다. 이 경우 @Module 주석이 있는 비추상 클래스(여기서는 NetworkModule이라고 함)를 정의해야 합니다.
다음과 같이 이 클래스에서 메서드를 정의하고 @Provides 주석을 추가하고 함수 본문에 OkHttpClient 인스턴스를 제공합니다.

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
                    .connectTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .build()
    }
}

MainActivity로 돌아가서 @Inject를 사용하여 OkHttpClient를 삽입하면 성공적으로 실행됩니다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var okHttpClient: OkHttpClient
}

개발자의 편의를 위해 NetworkModule에 Retrofit 유형의 인스턴스를 제공하고 다음 코드를 작성합니다.

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
                            .addConverterFactory(GsonConverterFactory.create())
                            .baseUrl("http://example.com/")
                            .client(okHttpClient)
                            .build()
        }
}

메소드 provideRetrofit()의 okHttpClient 매개변수는 Hilt가 provideOkHttpClient() 메소드를 사용하여 자동으로 생성합니다. 이때 MainActivity에서 Retrofit을 다시 주입하면 정상적으로 실행될 수도 있습니다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var retrofit: Retrofit
}

Hilt 내장 구성 요소

@Module을 사용하여 주입된 클래스는 @InstallIn 주석을 사용하여 주입 범위를 지정해야 합니다. Hilt는 다양한 시나리오에 삽입하는 데 사용되는 총 7가지 구성요소 유형을 제공합니다.

구성 요소 이름 사출 범위
응용 프로그램 구성 요소 애플리케이션
활동 유지 구성 요소 모델 보기
활동구성요소 활동
FragmentComponent 파편
ViewComponent 보다
ViewWithFragmentComponent @WithFragmentBindings를 사용하여 정의된 뷰
서비스 구성 요소 서비스

위에서 정의한 NetworkModule을 전체 프로젝트에서 사용하려면 다음과 같이 수정하면 됩니다.

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {
}

Hilt 구성 요소 범위

기본적으로 Hilt는 각 의존성 주입 동작에 대해 다른 인스턴스를 생성합니다. 이전의 7개 내장 구성요소에 해당하는 Hilt는 다음과 같은 7개의 구성요소 범위 주석도 제공합니다.

구성 요소 범위 해당 내장 구성 요소
@하나씩 일어나는 것 응용 프로그램 구성 요소
@ActivityRetainedScope 활동 유지 구성 요소
@ActivityScoped 활동구성요소
@FragmentScoped FragmentComponent
@ViewScoped ViewComponent
@ViewScoped ViewWithFragmentComponent
@ServiceScoped 서비스 구성 요소

NetworkModule에서 전역적으로 제공되는 Retrofit 및 OkHttpClient 인스턴스를 하나만 생성하려면 @Singleton 주석을 추가하기만 하면 됩니다.

@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
                    .connectTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .build()
    }

    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
                            .addConverterFactory(GsonConverterFactory.create())
                            .baseUrl("http://example.com/")
                            .client(okHttpClient)
                            .build()
        }
}

범위 주석은 이전에 추가한 AudioDriver 클래스와 같은 주입 가능한 클래스 바로 위에 선언할 수도 있습니다.

@Singleton
class AudioDriver @Inject constructor() {
}

이는 AudioDriver가 전역적으로 동일한 인스턴스를 공유하고 AudioDriver 클래스가 전역적으로 종속성 주입될 수 있음을 의미합니다.

위의 그림과 같이 클래스에 대해 특정 스코프 주석을 선언한 후 주석의 화살표가 가리키는 곳이면 어디든지 클래스에 대해 종속성 주입을 수행할 수 있으며 동시에 동일한 인스턴스를 해당 클래스 내에서 공유할 수 있습니다. 범위.

프리셋 한정자

위에서 정의한 AudioDriver 클래스에 컨텍스트 매개변수가 필요한 경우 매개변수 앞에 @ApplicationContext 주석을 추가해야 하며 Hilt는 AudioDriver 클래스에 애플리케이션 유형 컨텍스트를 제공하고 코드를 컴파일하고 전달할 수 있습니다.

@Singleton
class AudioDriver @Inject constructor(@ApplicationContext val context: Context) {
}

활동 또는 다른 유형의 컨텍스트가 필요한 경우 Hilt에서 사전 설정한 다른 한정자를 사용할 수 있습니다.

@Singleton
class AudioDriver @Inject constructor(@ActivityContext val context: Context) {
}

이때 AudioDriver 클래스가 Singleton으로 Qualifier 범위와 일치하지 않기 때문에 코드를 컴파일할 때 오류가 보고됩니다.
활동 및 애플리케이션의 두 가지 유형에 대해 Hilt에는 미리 설정된 주입 기능이 있습니다. 클래스가 Activity 또는 Application에 의존하는 경우 Hilt는 다음과 같이 주석을 추가하지 않고 클래스를 자동으로 식별할 수 있습니다.

class AudioDriver @Inject constructor(val application: Application) {
}

class AudioDriver @Inject constructor(val activity: Activity) {
}

두 유형은 Application 및 Activity여야 하며 해당 하위 유형이 선언되더라도 컴파일이 실패합니다.

HiltViewModel 사용

먼저 다음과 같이 libs.versions.toml에서 관련 종속성을 선언합니다.

[versions]
hilt-lifecycle-viewmodel = "1.0.0-alpha03"

[libraries]
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hilt-lifecycle-viewmodel" }

build.gradle.kts에 종속성을 추가합니다.

dependencies {
    kapt(libs.androidx.hilt.compiler)
}

@HiltViewModel 주석을 통해 ViewModel을 제공합니다.

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
}

그런 다음 @AndroidEntryPoint 주석이 달린 Activity 또는 Fragment는 ViewModelProvider 또는 viewModels() 확장을 사용하여 평소와 같이 ViewModel 인스턴스를 가져올 수 있습니다.

class MainActivity : AppCompatActivity() {
    private val mainViewModel: MainViewModel by viewModels()
}

추천

출처blog.csdn.net/qq_39312146/article/details/130957791