시배's Android

Android | Build Optimization (1) 본문

Android/Android

Android | Build Optimization (1)

si8ae 2024. 10. 2. 17:02

Build Optimization

대규모 안드로이드 프로젝트에서 빌드 시간이 느려지는 것은 개발 생산성을 크게 저해하는 문제 중 하나입니다. 이를 해결하기 위한 방법으로 multi modularization이 자주 언급됩니다. 하지만, 여러 개의 모듈이 단순히 구현체 모듈을 참조하는 구조에서는 여전히 빌드 성능의 한계가 존재합니다. 이는 구현체 모듈에 변경 사항이 생길 때, 이를 참조하는 여러 모듈도 빌드 과정을 거쳐야 하기 때문입니다.

구현체 모듈과 참조 모듈의 문제점

다수의 모듈이 특정 구현체 모듈을 참조할 경우, 해당 구현체가 변경될 때마다 참조하는 모든 모듈이 다시 빌드되는 문제가 발생합니다. 이러한 상황에서는 모듈 분리만으로는 빌드 최적화가 부족하며, 참조 모듈이 불필요하게 빌드되지 않도록 하는 추가적인 전략이 필요합니다.

Dependency Inversion을 활용한 해결책

이 문제를 해결하기 위한 방법 중 하나는 impl(implementation) 모듈과 api 모듈을 분리하는 방식입니다. 이 구조에서는 impl 모듈이 오직 app 모듈에서만 참조되며, 나머지 모듈은 api 모듈을 참조하도록 합니다. 이를 통해 api 모듈을 참조하는 모듈들은 impl 모듈의 변경사항에 영향을 받지 않으며, 추가적인 빌드가 발생하지 않도록 할 수 있습니다.

Android 공식 문서에서도 이 방식을 Dependency Inversion이라는 개념으로 소개하고 있습니다. Dependency Inversion은 구현체(Concrete Implementation)와 추상화(Abstraction)를 분리하여 모듈 간 의존성을 줄이는 방법입니다.

 

일반적인 모듈화 패턴  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 일반적인 모듈화 패턴 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 모든 프로젝트에 맞는 하나의

developer.android.com

이 방식을 통해, 각 모듈은 구체적인 구현에 의존하지 않고, 추상화된 API에만 의존하게 됩니다.

예시: 데이터베이스 모듈

예를 들어, 어떤 기능 모듈이 데이터베이스에 의존한다고 가정해 봅시다. 이때 해당 기능 모듈은 구체적으로 어떤 데이터베이스를 사용하는지에 대해 알 필요가 없습니다. 로컬 Room 데이터베이스든, 원격 Firestore든 상관없이 데이터만 저장하고 불러오는 역할을 할 뿐입니다.

기능 모듈은 데이터베이스 API를 제공하는 추상화된 모듈에 의존하며, 구체적인 데이터베이스 구현은 별도의 구현체 모듈에서 처리됩니다. 이때 구현체 모듈 역시 추상화 모듈에 의존하여, 추상화된 API 규약을 따르게 됩니다.

의존성 주입(Dependency Injection) 활용

여기서 의문이 생길 수 있습니다. "기능 모듈은 어떻게 구현체 모듈과 연결되는가?" 그 해답은 의존성 주입(Dependency Injection)입니다. 기능 모듈은 필요한 데이터베이스 인스턴스를 직접 생성하지 않고, 외부에서 주입받습니다. 일반적으로는 app 모듈에서 이 역할을 수행하며, Hilt 같은 DI 프레임워크를 사용하여 구현체와 API를 연결할 수 있습니다.

이점

API와 구현체를 분리함으로써 얻을 수 있는 이점은 다음과 같습니다:

  • 교체 가능성(Interchangeability): 동일한 API를 기반으로 여러 구현체를 개발할 수 있으며, 이를 상황에 따라 자유롭게 교체할 수 있습니다. 예를 들어, 테스트 환경에서는 Mock 구현을 사용하고, 실제 배포 환경에서는 실제 구현을 사용할 수 있습니다.
  • 디커플링(Decoupling): 모듈들이 특정 구현체에 의존하지 않기 때문에, 예를 들어 Room에서 Firestore로 데이터베이스를 변경하더라도, 변경 사항은 구현체 모듈에만 영향을 미칩니다.
  • 테스트 용이성(Testability): 구현체를 분리함으로써 API 규약을 기준으로 테스트를 작성할 수 있으며, 다양한 시나리오를 테스트하기 위한 Mock 구현을 쉽게 활용할 수 있습니다.
  • 빌드 성능 향상(Build Performance): API와 구현체를 분리하면, 구현체 모듈의 변경이 API 모듈에 의존하는 다른 모듈에 영향을 주지 않습니다. 결과적으로 불필요한 빌드가 줄어들어 빌드 시간이 단축됩니다.

실제 성능 개선 사례

실제로 위 방식으로 data 모듈을 분리하고 증분 빌드 시간을 측정해본 결과, 약 20초에서 10초 가량의 유의미한 성능 차이를 확인할 수 있었습니다. 이러한 최적화는 대규모 프로젝트에서 개발 속도를 크게 향상시키며, 더 나은 개발 경험을 제공합니다.