Android

Android Hilt : Hilt Component와 Scope 톺아보기

정자이노 2023. 10. 29. 20:33

현재 프로젝트에 의존성 주입을 위해 Hilt를 사용하고 있다. Hilt는 Android에서 Dagger를 쉽게 사용할 수 있게 만들어주는 매퍼 라이브러리이다. 필자는 Hilt를 도입하면서, 제대로 알지 못하고 사용하여 많은 문제를 겪었다. 

 

1. Hilt Scope

리뷰처럼, 필자는 코드의 유스케이스를 Hilt Scope Annotation을 처리하였다. 단순히 매번 호출할 때마다 인스턴스 생성하는 것을 방지하기 위함이였다.

출처 : 강사룡의 앱 안정성과 확정성을 위한 Android 아키텍처

리뷰를 통해 레퍼런스를 찾아보다, Hilt Scope를 잘못 사용하면 메모리 누수를 발생시키고 더 나아가 성능 저하까지 일으킨다는 내용이 많았다. 아무래도 생명주기와 연관있다 보니, GC에 수집되지 않는 부분이 가장 큰 것 같다. 

 

2. Hilt Component 

ActivityComponent를 꼭 Activity에서 사용해야 한다는 잘못된 지식을 가지고 있어, Fragment에서 어떻게 사용하지? 라는 문제로 삽질을 했던 적이 있다. 역시 Hilt Component에 대해 잘 모르고 설계를 하다가 발생한 문제다. 

Droid Knights  안성용 - Dagger Hilt로 의존성 주입부터 멀티모듈화까지

해당 droidKnights 연사 혹은 다른 레퍼런스를 통해, 하위 컴포넌트에서는 상위 컴포넌트에 접근할 수 있다는 점을 알게되었다. 사실 이전에 Fragment나 ViewModel에서 SingletonComponent에 접근하여 구현하고 있었으니, 당연했다... 

 

따라서 이번 기회에  Hilt, Dagger에 대해 알아 보고자 한다.

 

Injector 

Injector는 따로 의존성 주입 라이브러리를 사용하지 않을 경우에 사용한다. 

주입되는 모든 타입들을 위한 생성 방법을 정의한 클래스이다. Injector 클래스에서 의존성 주입에 대한 구현이 발생한다. 

class Injector {
    fun getNewsApi(): NewsApi = ProdNewsApi()
    fun getNewsRemoteDataSource(): NewsRemoteDataSource = NewsRemoteDataSource(getNewsApi())
}

 

Bindings (Hilt)

각 객체의 생성이 실제로 정의되는 곳을 의미한다. 어떤 클래스가 key고 어떤 클래스를 생성하는 방법을 value로 만든 해쉬테이블과 같은 개념이다.  함수에 매개변수가 있는 경우, 또 다른 바인딩이 있는 지 검색하여 주입한다.

@Provides
fun provideNewsRemoteDataSource(newsApi: NewsApi): NewsRemoteDataSource =
	NewsRemoteDataSource(newsApi)

@Provides
fun provideNewsApi(newsApi: ProdNewsApi): NewsApi = newsApi

 

Module (Hilt)

단순히 Bindings의 모음이며, 컴포넌트 안에 설치되거나 다른 모듈에 설치될 수 있다. @InstallIn 어노테이션을 통해 어떤 컴포넌트에 모듈이 설치할지 정의할 수 있다. 

@Module
@InlstallIn(SingletonComponent::class)
class ProdNewsModule {
    @Provides
    fun provideNewsRemoteDataSource(newsApi: NewsApi): NewsRemoteDataSource =
        NewsRemoteDataSource(newsApi)

    @Provides
    fun provideNewsApi(newsApi: ProdNewsApi): NewsApi = newsApi
}

참고로 모듈 내에서 Bindings의 범위를 지정하기 위해서는 Component의 범위와 일치해야 한다. 예를 들면, ActivityComponet에서 범위 지정은 @ActivityScoped를 통해서만 가능하다. 

 

Component 

Component는 의존성 객체를 생성하고, 의존성 주입을 관리하는 객체이다. (원형이 Injector 이다.) 
Dagger 프레임워크는 Component 인터페이스를 상속받아, 주입되는 객체의 생성하는 방법을 정의한 클래스를 생성한다.
따라서, Component는 interface와 abstract class로 정의되어야 한다.

 

Dagger와 다르게, Hilt Component는 표준 Android 구성요소, Activity, Fragment, View, ViewModel, Service에서 사용할 수 있는 Component를 제공하고 있다. 

자동으로 생명주기 및 계층구조와 같은 세부적인 설정을 자동으로 구현한다. 

Hilt Component 계층 구조 출처 : https://dagger.dev/hilt/components.html

 

컴포넌트는 다음과 같은 계층 구조를 가진다. 이러한 계층구조에서 화살표는 하위 컴포넌트를 의미하며, 하위 컴포넌트 바인딩은 상위 컴포넌트 바인딩의 종속성을 가질 수 있다. 

 

Droid Knights  안성용 - Dagger Hilt로 의존성 주입부터 멀티모듈화까지

 

이러한 이유는 Dagger가 만들어내는 코드를 확인하면 알 수 있다. Component 어노테이션을 통해 만들어지는, CImpl 클래스들의 생성자에, 상위 컴포넌트를 받도록 구현되어 있기 때문이다. 

 

Scope 

Hilt에서 Scope Anotation을 사용하면, 객체의 수명을 해당 구성요소의 수명으로 제한할 수 있다. 

 

Module에서 Scope를 지정하지 않으면, 어떻게 될까? 컴포넌트의 생명주기에 맞게 scope가 구성될까? 

 

정답은 unscoped 상태가 된다. 따로 지정되지 않은 상태를 의미하며, 주입받은 객체를 호출할 때마다 새로운 객체가 생성된다. 

 

범위가 지정된 바인딩은 범위가 지정된 구성 요소의 인스턴스당 한 번만 생성되며 해당 바인딩에 대한 모든 요청은 동일한 인스턴스를 공유한다. (ActivityComponent로 선언하면, ActivityScoped로 지정해야 하며, ActivityScoped로 생성된 인스턴스는 Activity 생명주기가 끝나기 전까지 파괴되지 않는다.)

 

Dagger 공식 홈페이지에서는 다음과 같이 Scope Annotation 사용을 설명한다.

Scoping a binding has a cost on both the generated code size and its runtime performance so use scoping sparingly. The general rule for determining if a binding should be scoped is to only scope the binding if it’s required for the correctness of the code. If you think a binding should be scoped for purely performance reasons, first verify that the performance is an issue, and if it is consider using @Reusable instead of a component scope.
(스코프를 지정하는 것은 내부적으로 많은 코드를 만들어내고, 비용을 많이 증가시킨다. 대부분의 경우에서 사용하지 말고, 특수한 목적이 있는 경우에서만 사용해라.) 

Hilt Custom Component 활용하기
해당 블로그의 필자분은 따로 회원의 상태에 따라 처리하기 위해, Scope를 사용하였다. 
필자와 같이, 인스턴스를 계속 초기화하는 것을 방지하기 위해서 Scope를 사용하는 것은, 오히려 많은 비용을 초래하며, 권장하지 않는 방법인 것 같다. 


참고 문헌

https://dagger.dev/hilt/components.htmlhttps://myungpyo.medium.com/hilt-custom-component-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0-a0452fe2566a

https://www.youtube.com/watch?v=iE_2llWUI6A&t=170s