" Park 기술 블로그 "
# 카테고리
# 도구
[Android 안드로이드] Dagger2에 대해서
2021-11-24 11:11:14

 오늘은 많은 Android 앱에 사용되는 Dagger2에 대해서 정리하려고 한다.

Dagger2에 대한 사용법에 대해서는 이미 좋은 레퍼런스가 많이 있으므로 이 글에서는 다루지 않을 예정이고 내부적으로 어떻게 동작하는지 정리할 예정이다.

 

이 글을 통해서 알 수 있는 것은 아래와 같다.

  1. Dagger2 사용을 위해 작성하는 interface들이 어떤 구현체가 생성되는가

각 구현체가 어떻게 작동하는가

모듈을 통한 의존성 주입과 생성자 주입에 차이는 무엇인가

왜 안드로이드 공식 문서에서 가능하면 생성자 주입을 사용할 것을 권장하는가

 

이 글에서 다루고 있는 githubsearcher라는 프로젝트는 오른쪽의 링크에서 확인할 수 있다. [GithubSearcher]

 

 Dagger2를 사용하기 위해 작성하는 interface들을 통해 Dagger2가 어떤 코드를 만들어 내는지를 확인하는 방법으로 동작원리를 파악할 수 있다. Dagger2가 만들어 낸 코드는 아래의 사진처럼 generated 디렉토리에 있다.

이 generated 디렉토리 안에는 Dagger2만 아니라 DataBinding처럼 빌드했을 때 생성되는 코드들이다. 더 정확히는 아래의 사진과 같이 AndroidStudio가 모듈을 위해 생성한 Java 코드라고 한다.

 


 

Dagger2에서 생성되는 파일들은 아래처럼 크게 4가지로 정리할 수 있었다.

  • Dagger{Component class name} 
    • @Compoent 어노테이션이 붙은 클래스의 구현체 파일이다.
    • {Subcomponent class name}Factory 이름으로 중첩 클래스가 만들어 지는 경우가 있는데 이는 SubComponent를 만든 경우이고, @SubComponent 어노테이션이 붙은 클래스의에 대한 클래스이다.
  • {Module class name}_{Provide method name}Factory
    • @Module 클래스 안에 @Providers 어노테이션이 붙은 메소드의 구현체 파일이다.
  • {Class name}_Factory
    • 생성자 주입을 사용한 클래스에 대해서 생성하는 방법이 구현된 파일이다.
    • 예를 들어 Kotlin의 경우 class UserRepository @Inject constructor( ... ) { ... } 이다.
  • {Class name}_MembersInjector
    • Dagger2를 통해 의존성 주입을 요청하는 클래스에 대해서 주입하는 방법이 구현된 파일이다.
    • 예를 들어 Activity나 Fragment에서 의존성 주입을 요청하는 경우다.

 


 

  • Dagger{Component class name}
  • {Module class name}_{Provide method name}Factory
  • {Class name}_Factory

Dagger{Component class name}을 설명하기에 같이 설명하는 것이 좋을 것 같아서 위의 3개를 한 번에 묶었다. 이 Dagger{Component class name} 파일에서는 크게 4가지에 대한 영역으로 나눌 수 있을 것 같다.

  1. 의존성 제공을 위한 클래스들을 Provider 형태로 참조하고 있는 멤버 변수에 대한 영역
  2. 멀티 바인딩에 대한 영역
  3. Dagger2가 전달해주는 클래스에 대해서 Provider 형태로 만들고 이 클래스가 필요로하는 의존성을 전달해주며, @Scope 어노테이션에 맞는 Provider 클래스로 변형하는 영역
  4. Factory, Builder 클래스들에 대한 구현 및 SubComponent에 대한 중첩 클래스 구현하는 영역

 

1번 영역의 경우는 아래의 사진과 같다.

위의 사진처럼 Provider interface 클래스 형태로 참조하고 있었다. 이 Provider 클래스는 get() 함수 한 개만 갖고 있는 형태이고 이에 대한 생성 및 전달은 3번 영역에서 하고 있다.

 

2번 영역에서는 아래의 사진처럼 멀티 바인딩에 대한 부분이 구현된다.

 

3번 영역의 경우는 아래와 같다.

위의 사진을 보면 1번 영역에서 참조하고 있는 멤버 변수들을 초기화해주고 있다. 이 초기화에는 나름의(?) 순서가 있는데, 다른 클래스에서 의존성이 필요한 클래스를 먼저 초기화 하는 식으로 순서가 정해진다.

 

예를 들어 위의 사진에서는 this.providerRetrofitProvider가 가장 먼저 초기화 되고 있고 그다음에 this.provideGithubAPIProvider가 초기화 되고 있다. 이는 providerGithubAPIProvider가 provideRetrofitProvider에 대해서 의존성이 있기 때문이다.

 

그리고 DoubleCheck.provider( ... ) 이런 식으로 초기화되는 멤버 변수가 있고 없는 경우도 있다. 이 기준은 @Scope 어노테이션의 유무로 결정된다. Dagger2에서는 기본으로 제공하는 @Singleton 어노테이션을 포함해서 Dagger2 안드로이드 공식 문서에 있는 @ActivityScope와 같이 Component와 SubComponent에 대해서 @Scope 어노테이션을 사용할 수 있는데, 이를 사용하면 내부적으로 싱글톤 패턴으로 관리되는 Provider 구현체 클래스로 전달됩니다. 아래는 이 DoubleCheck.provider( ... ) 내부에서 싱글톤 패턴으로 관리되는 부분에 대한 사진이다.

위의 사진에서 /* ... */ 이렇게 주석된 부분에 설명되어 있는 것처럼 DoubleCheck.provider( ... )에서 전달된 Provider 클래스의 get() 메소드로 인스턴스를 만들고 나면 이 인스턴스를 계속 재사용할 것이기 때문에 Provider를 사용할 필요가 없어지고 이 Provider 클래스를 GC의 대상으로 만들기 위해서 null로 참조를 끊고 있는 모습을 볼 수 있다.

이 DoubleCheck.provider를 먼저 설명한 이유는 이 부분에 대해서 제외하고 3번 영역에서 초기화하는 사진을 다시보면 공통적인 부분이 있기 때문이다. DoubleCheck.provider( ... )를 제외하고 공통적인 부분은 아래 두 개와 같다.

 

  • {Module class name}_{Provider method name}.create( ... )
  • {Class name}_Factory.create( ... )

 

여기서 ( ... ) 부분은 필요로 하고 있는 의존성에 대해서 전달하고 있느냐 없느냐에 따라서 있고 없고가 결정되기 때문으로 보면 된다.

{Module class name}_{Provider method name}Factory.create( ... )와 {Class name}_Factory.create( ... ) 로 나뉘어지는 이유는 의존성 전달에 대한 방법이 2개이기 때문이다. Dagger2에서는 Module로 의존성을 전달할 수도 있고 생성자에서 의존성을 전달할 수도 있다. {Module class name}_{Provider method name}Factory.create( ... )처럼 Module에서 @Providers를 사용해서 의존성을 전달하는 부분은 아래의 사진과 같고

이에 대해서 제공해주는 구현 클래스는 아래와 같다.

위의 사진처럼 NetworkModule이라는 Module 클래스의 Retrofit 인스턴스를 제공해주는 @Providers 메소드에 대해서 클래스가 만들어지는 모습을 볼 수 있다. 여기서 기억해야하는 점은 아래 static final 부분의 INSTANCE 부분이다. 이는 Module 클래스의 @Providers 메소드마다 인스턴스가 생성되고 클래스 변수로 메모리에 상주됨을 의미한다.

 

{Class name}_Factory.create( ... )처럼 의존성을 전달하려는 클래스의 생성자에서 전달하는 부분은 아래의 사진과 같고

이에 대해서 전달하는 구현 클래스는 아래와 같다.

구현된 내용은 @Providers에 비해 크게 다르지 않다. 하지만 NetworkModule_ProvideRetrofitFactory에 대해서는 필요로 하는 의존성이 없어서 멤버 변수로 Provider를 갖는 부분이 없는 것을 볼 수 있고 UserRemoteDataSourceImpl_Factory에 대해서는 필요로 하는 의존성이 GithubAPI가 있어서 멤버 변수로 Provider를 갖는 부분이 있는 차이가 있다.

그러나 이 부분은 예시의 경우에 따라 다른 부분이니까 Provider 멤버 변수의 유무가 모듈에서 @Providers로 전달하는 것과 생성자 의존성 전달(@Inject constructor(...))에 대한 차이로 보기 어렵고 이 차이에 대해서는 한 가지가 있다. 바로 위에서 언급했던 INSTANCE 클래스 변수에 대한 것이다. 생성자 의존성 전달 방법으로 Dagger2에게 알려주면 @Providers로 알려줬을 때보다 INSTANCE라는 클래스 변수가 없으므로 메모리 측면에서 효율적이다. 그래서 Dagger2에 대한 문서에서도 직접 만든 클래스에 대해서는 생성자 의존성 전달 방법으로 사용할 것을 권장하고 있는 것 같다. @Providers는 외부에서 작업된 라이브러리를 인스턴스로 만들거나 Builder 패턴처럼 다른 인스턴스를 조합해서 인스턴스를 생성해야 하는 경우에 사용할 것을 권장하고 있다.

 

여기서 @Singleton처럼 scope에 대한 Singleton관리를 위한 부분과 저 INSTANCE에 대해서 혼동이 있을 수도 있을 것 같다고 생각한다. 하지만 위에서 본 것처럼 내부적으로는 별개의 로직임을 알 수 있다.

 

4번 영역 중에서 Component를 초기화하는 Factory에 대한 구현 부분은 아래와 같다.

그리고 이 Component 안에서 만든 SubComponent는 아래의 사진처럼 Component 안에 중첩 클래스 형태로 구현된다.

SubComponent에도 Component에 대한 구현 클래스와 비슷한 구조를 갖고 있는 것을 볼 수 있다.

 


 

  • {Class name}_MembersInjector

이 파일은 Dagger2에게 멤버 인젝션을 요청하는 클래스에 대해서 생성된다. 예를 들어 우리가 익히 사용하는 Activity, Fragment가 된다. 아래의 사진은 멤버 인젝션을 요청하는 부분이다.

위의 사진처럼 ViewModelProvider.Factory를 요청하는 상황이라면 아래처럼 생성된다.

위의 사진을 확인 해보면  MembersInjector를 생성할 때 필요한 의존성을 생성자로 받고 멤버 변수로 참조하고 있는 부분을 볼 수 있다. 그리고 실제 injectMembers 메소드 안에서 injectViewModelFactory 클래스 메소드를 통해 의존성 주입하고 있는 부분을 볼 수 있다. Dagger2에서 멤버 주입을 받을 때 private로 선언하면 에러가 나는데 그 이유는 위의 사진 중 아래 부분에 instance.viewModelFactory처럼 참조해서 인스턴스를 넣어줄 수 없기 때문에 에러가 발생하는 것이다.

 

그렇다고 한다면 이 MembersInjector는 언제 어디에서 호출될까? 답은 Component 혹은 SubComponent interface 클래스 안에서 의존성을 요청하기 위해 inject 라는 메소드를 작성하는데 이 inject의 구현 부분에서 호출된다. 아래는 Component 혹은 SubComponent에서 작성된 inject 메소드이고

이 부분에 대한 구현되는 부분은 아래와 같다.

 

위의 사진처럼 예를 들어 Activity의 onCreate(:Bundle) 부분에서 inject 호출할 때 MembersInjector가 호출되고 의존성 주입이 된다.

0
# 댓글 + 새 댓글 작성
# 새 댓글 작성
댓글 암호는 댓글 삭제 시 필요합니다.
# 님에게 답변 작성
대댓글 암호는 댓글 삭제 시 필요합니다.
# 님의 댓글 삭제
댓글 작성 시 입력했던 암호를 입력해주세요.
아직 댓글이 없습니다