안드로이드 아키텍처 용어 사전
DI컴포넌트별 스코프 · @Singleton · @ViewModelScoped · ActivityRetainedScoped

DI 스코프

주입된 객체가 얼마나 오래 살아 있고 누구와 공유되는지를 정하는 범위.

주입된 객체가 얼마나 오래 살고 누구와 공유되는지를 정하는 범위.

  • @Singleton — 앱 프로세스 전체

  • @ActivityRetainedScoped — 구성 변경을 넘어 유지 (ViewModel 과 같은 수명)

  • @ViewModelScoped — ViewModel 하나의 수명

  • @ActivityScoped — Activity 하나 (회전하면 새로 만들어진다)

  • @FragmentScoped — Fragment 하나

  • @ServiceScoped — Service 하나

  • 스코프를 안 붙이면 = 주입할 때마다 새 인스턴스

무엇을 어디에 두나

  • @Singleton — OkHttpClient · Retrofit · Room 데이터베이스 · DataStore

    • 만드는 비용이 크고 상태가 안전하며 앱 전체가 공유한다
  • 스코프 없음 — Mapper · UseCase · 순수 함수 객체

    • 상태가 없어 만드는 비용이 거의 0 이다
  • @ActivityRetainedScoped

    • 한 화면 흐름 안에서 여러 ViewModel 이 공유할 상태
    • (예: 여러 단계로 나뉜 가입 플로우)
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
    @Provides @Singleton
    fun provideDb(@ApplicationContext ctx: Context): AppDatabase =
        Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db").build()

    @Provides                          // 스코프 없음 — DAO 는 DB 가 캐싱한다
    fun provideUserDao(db: AppDatabase): UserDao = db.userDao()
}

스코프를 잘못 잡으면

너무 길다 @Singleton 객체가 Activity 를 참조한다

  • Activity 가 파괴돼도 GC 되지 않는다 = 메모리 누수 @Singleton 객체가 사용자별 상태를 들고 있다

  • 로그아웃 후에도 이전 사용자 데이터가 남는다

너무 짧다 Retrofit 을 스코프 없이 제공한다

  • 주입될 때마다 새 OkHttp 커넥션 풀이 만들어진다
  • 커넥션 재사용이 안 되고 메모리가 는다

컴포넌트 계층

SingletonComponent
  └ ActivityRetainedComponent
      ├ ViewModelComponent
      └ ActivityComponent
          └ FragmentComponent
              └ ViewComponent

아래는 위의 바인딩을 볼 수 있다 (Fragment 가 @Singleton 객체를 주입받을 수 있다)
위는 아래를 못 본다 (@Singleton 이 @ActivityScoped 객체를 주입받으면 빌드 실패)

면접 함정

  • "@Singleton을 많이 붙일수록 빠르다" → 앱이 살아 있는 내내 메모리를 점유한다. 생성 비용이 클 때만이다.
  • "싱글턴이니 스레드 안전하다" → 오히려 반대다. 여러 곳이 동시에 접근하므로 상태가 있으면 위험하다.

실제로 누수가 어떻게 생기나

// ❌ 앱 전체 수명 객체가 Activity Context 를 붙든다
@Provides @Singleton
fun provideImageLoader(ctx: Context): ImageLoader = ImageLoader(ctx)

// ✅ Application Context 를 명시적으로 받는다
@Provides @Singleton
fun provideImageLoader(@ApplicationContext ctx: Context): ImageLoader = ImageLoader(ctx)

Hilt 는 @ApplicationContext 와 @ActivityContext 를 다른 한정자로 구분한다

  • SingletonComponent 에서 @ActivityContext 를 요청하면 빌드가 실패한다
  • 이 실수를 컴파일 타임에 막아 준다

로그아웃 시 상태를 어떻게 비우나

  • @Singleton 캐시에 사용자 데이터가 남아 다음 계정으로 로그인해도 보인다

  • 대응 1 — 로그아웃 시 명시적으로 clear() 를 부른다 (호출 누락 위험)

  • 대응 2 — 사용자 범위 컴포넌트를 직접 정의한다 (@DefineComponent)

  • 대응 3 — DB 를 SSOT 로 두고 로그아웃 시 테이블을 비운다 — 가장 단순하고 확실하다

확인하는 법

// 같은 인스턴스인지 로그로 확인한다
class HomeViewModel @Inject constructor(repo: UserRepository) : ViewModel() {
    init { Log.d("DI", "repo=${System.identityHashCode(repo)}") }
}
// 화면을 여러 번 열어 값이 같으면 공유, 다르면 매번 새로 생성이다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 의존성 주입 — Hilt·Koin