Dynamic Feature Module 적용 후기

다이내믹 피쳐 모듈이란?

다이내믹 피쳐모듈 (Dynamic Feature Module, 이하 DFM)을 사용하면 베이스 모듈(일반적으로 app모듈)로부터 특정 기능과 리소스를 분리하여 App Bundle에 포함시킨다. 그런 다음 Dynamic Delivery 를 통해 안드로이드 5.0 이상을 실행하는 기기가 앱의 기능을 다운로드하는 시기와 방법을 제어할 수 있게 된다.

앱의 전체를 다운로드 받지 않고 일부기능만 실행할 경우에 유용하며, 추가적인 기능을 실행시에 다운로드 후 해당 기능을 사용할 수도 있다.

DFM 도입 이유

DFM을 적용하면 여러가지 장점이 있고, 대부분은 초기 설치 APK사이즈를 줄여 사용자 경험을 개선하기 위해 사용한다. 하지만 나의 경우에는 빌드 타임을 개선하기 위해 이를 프로젝트에 도입했다. 

현재 진행하고 있는 프로젝트의 빌드타임은 평균 2~3분 정도 소요된다. 간단한 비즈니스 로직 또는 UI 변경사항 등을 적용하고 결과를 확인하기 위해 이 시간을 기다려야 한다는 것이 많이 부담스러웠다.

어떻게든 빌드시간을 줄여야만 생산성을 늘리고, 나와 우리 팀원 모두의 퇴근시간을 앞당길 수 있다고 생각했다. 그래서 빌드 시간을 줄이기 위해 DFM을 통한 모듈화 작업을 하기로 결정했다

DFM으로 겪었던 문제

아직은 레퍼런스가 풍부하지 않은 DFM을 적용하며 겪었던 트러블슈팅을 공유하고자 한다. 
대표적으로 문제가 되었던 점은 다음 두 라이브러리와 DFM을 같이 사용했을 때다.

  • 데이터바인딩(databinding)
  • 의존성 주입을 위한 Dagger2

데이터바인딩과 관련된 이슈

1. 바인딩 어댑터 메서드 참조 에러

프로젝트 레벨에서 DFM이 app모듈을 의존함에도 불구하고, 기존 app모듈에 정의된 정적 커스텀 바인딩 어댑터 메서드가 DFM에서 참조되지 않는 점을 확인 했다.

DFM 공식 샘플 앱인 plaid를 참고해보니 바인딩 어댑터를 별도로 분리된 core모듈에서 관리하는것을 확인했고, app모듈과 DFM 모두 바인딩 어댑터 메서드를 정상적으로 참조하는 것을 확인했다. 하지만 기존에 사용하던 바인딩 어댑터 메서드들이 선언된 클래스를 통째로 core로 옮기려면 연관된 클래스를 모두 core로 이동해야 했고, 스파게티처럼 엉켜있는 레거시들을 당장 분리하는 것은 불가능해 보였다. 그래서 다음과 같은 시도를 해보았다.

  • app모듈에 선언된 커스텀 DataBindingComponent 를 바인딩 클래스에 적용했다.
    예를 들면 다음과 같다.

    DataBindingUtil.setContentView(
        this,
        R.layout.activity_main, 
        new ClickBindingComponent(getLifecycle())
    );
    

하지만 커스텀 DataBindingComponent에 선언된 바인딩 어댑터 메서드를 여전히 참조하지 못하였다. DFM에서 커스텀 DataBindingComponent를 선언하고 동일하게 작업을 해봤지만 이 또한 제대로 동작하지 않았다.

결국 app모듈에 있는 바인딩 어댑터 클래스를 한벌 복사해서 DFM 에 붙여 넣었다. 이렇게 하면 각 모듈에 있는 바인딩 어댑터를 각각 참조하므로써 올바르게 동작하는 것을 확인할 수 있었다.

데이터바인딩은 프로젝트의 일반적인 의존성 방향인 app->module에 대해서는 올바르게 동작하지만 DFM이 취하고 있는 app<-module 의존성방향에 대해서는 제대로 동작하지 않는 듯 하다. 결국 공통으로 의존할 수 있는 모듈(core)에 바인딩 어댑터를 선언하는 방법밖에 없는 듯 하다. 

2. 리소스 아이디 참조 에러

또 다른 문제는 생성되는 리소스 클래스인 BR 및 R 클래스의 canonical이름을 작성해야 한다는 점이다. 

모듈들의 패키지명은 다음과 같다고 가정하자.

base모듈 : com.charlezz.app
DFM : com.charlezz.app.feature1

그렇다면 DFM 내의 소스 코드에서 리소스 아이디를 참조하려면 다음과 작성해야한다.

//DFM에서 app모듈 리소스 참조시
com.charlezz.app.R.id.title
com.charlezz.app.R.drawable.launcher_icon
com.charlezz.app.BR.content

//DFM에서 자신의 리소스 참조시
R.id.name 또는 com.charlezz.app.feature1.R.id.name
R.drawable.my_icon 또는 com.charlezz.app.feature1.R.drawable.my_icon
BR.test 또는 com.charlezz.app.feature1.test

만약 DFM에서 core모듈로부터 리소스를 참조한다면 app모듈과 마찬가지로 풀패키지명을 적도록 해야한다. 별거 아니지만 이미 R부터 누르고 자동완성을 호출하는 것이 익숙해진 내 손가락은 패키지명을 적는 것을 거부하고 있었다. 

3. 프로가드와 DataBinderMapper 머지 이슈

모듈 별로 데이터바인딩 라이브러리를 사용하고 있고, 빌드를 수행하게 되면 모듈별로 DataBinderMapper를 생성하게 되는데 이때 Mapper를 내부에서 머지하게 되는듯 하다. 난독화를 하는 경우 DataBinderMapper를 지키기 위해 다음 코드를 프로가드 룰에 추가하자.

-keep class * extends androidx.databinding.DataBinderMapper { *; }

 

Dagger와 관련된 이슈

dagger.android 는 일반적인 싱글 모듈 앱 또는 일반 하위 모듈을 갖는 프로젝트에서 아주 훌륭한 의존성 주입 솔루션을 제공하는 프레임워크다.

결론부터 이야기하면 DFM과 Dagger는 기존에 사용하던 방식으로는 오브젝트 그래프를 형성할 수 없다.

android.dagger는 컴포넌트내에 서브 컴포넌트를 구성하는 방법을 사용하기 때문에 컴포넌트가 서브컴포넌트를 참조할 수 있어야 한다. DFM 이전의 일반적인 안드로이드 프로젝트에서는 app모듈이 하위 모듈을 참조하기 때문에, app모듈내의 컴포넌트가 하위 모듈의 서브컴포넌트를 참조할 수 있으므로 오브젝트 그래프가 성공적으로 컴파일 타임에 구성된다.

다음 일반적인 멀티 모듈 프로젝트에서 오브젝트 그래프를 구성한 그림을 살펴보자.

하지만 DFM을 사용하는 프로젝트는 dagger.android 와 사용할 수 없다.  프로젝트에서 DFM은 app모듈을 참조하며, 일반적인 멀티모듈 프로젝트와 비교했을 때 의존성의 방향이 완전히 역전된다. app모듈입장에서 다른 DFM에 대한 의존성은 모두 디커플링 되기 때문에 모듈간에 컴포넌트서브 컴포넌트 구조를 적용할 수 없게 된다. app모듈의 컴포넌트 입장에서 서브 컴포넌트를 참조할 있어야 하는데 프로젝트 레벨에서 모듈의 의존성이 끊기니 DFM내의 서브컴포넌트를 참조할 수 없기 때문이다. DFM모듈을 사용하는 멀티모듈 프로젝트 오브젝트 그래프를 구성한 그림을 살펴보자.

그렇다면 DFM을 사용하는 멀티모듈 프로젝트에는 Dagger를 사용할 수 없을까?

그렇지 않다. Dagger에서는 Component 의존성을 가질 있는 방법을 제공하고 있다.

이에 대한 내용은 이전 포스트인 멀티 모듈을 사용하는 앱에서 Dagger적용하기로 대체하겠다.

기타 문제들

  • CI에서 기존에 사용하던 Gradle 명령어는 DFM모듈을 포함시키지 않은 APK를 빌드했고, 해당 모듈의 액티비티 등으로 진입시 ClassNotFoundException이 발생했다. DFM이 전부 포함된 APK를 생성하기 위해서는 다음과 같은 universalAPK 커맨드를 사용할 수 있다. 자세한 내용은 공식문서를 참고하자.
    ./gradlew packageDebugUniversalApk
  • universalApk 빌드시 전체모듈을 통틀어서 중복된 파일명 또는 리소스 아이디를 갖는 레이아웃, drawable 등이 존재하면 안된다. 고유한 파일명 또는 아이디를 작성하도록 하자.
  • DFM의 모듈명은 문자, 숫자, 언더스코어만 허용하며 대쉬를 사용할 수 없다. 구글 개발자 Ben Weiss가 추천하는 네이밍 컨벤션은 카멜케이스다.(참조)

마치며…

DFM을 적용해서 개발을 하면, 프로젝트 레벨에서 의존성이 분리 되기 때문에 자연스레 개발시 코드간 의존성에 대해 좀 더 고민하고 코드를 작성하게 되는 것을 느꼈다.

프로젝트가 클수록 대규모 팀 협업에서 동시 개발시 충돌을 줄이, 필요한 모듈만 빌드하므로 빌드시간도 대폭 줄일 수 있다.

그렇다고 소규모 프로젝트라고해서 DFM을 적용하지 않을 이유는 없다. 이 경우 소규모 프로젝트가 금세 고도화 된 대형 프로젝트가 될 수도 있고, 초기에 DFM에 투자한 시간을 유지보수시 고스란히 몇배로 돌려받게 될것이다. 사용자 입장에서도 필요한 기능과 리소스를 적절한시기에 다운로드 받기 때문에 APK의 사이즈가 줄어드니 DFM 적용시기를 늦추지 말고 지금 바로 시작해보자.

 

카테고리: Dagger2

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.