
안드로이드 single activity 구조가 자주 보이는 이유는, 액티비티를 적게 쓰는 것이 멋져 보여서가 아닙니다. 핵심은 화면 이동, back stack, 공유 상태, state restoration을 한 흐름으로 묶어 다루기 쉬워졌기 때문입니다. 이번 글에서는 왜 이런 구조가 많아졌는지, 그리고 언제는 오히려 과할 수 있는지 실무 관점으로 정리해보겠습니다.
single activity를 오해하면 앱에 Activity는 하나만 있어야 한다고 받아들이기 쉽습니다. 하지만 실제 포인트는 그것보다 화면 전환의 주인이 바뀌었다는 데 있습니다. multi-activity에서는 화면 전환이 곧 Activity 전환인 경우가 많았지만, single activity에서는 Activity를 화면 컨테이너에 가깝게 두고 Fragment나 Compose destination이 이동의 중심이 됩니다.
- 화면 이동 규칙을 navigation graph 쪽에 모으기 쉽다
- 같은 흐름 안의 back stack을 한 곳에서 읽기 쉽다
- 여러 화면이 공유해야 하는 상태를 한 Activity 범위에서 다루기 쉽다
- Compose처럼 선언형 UI와 결합할 때 구조가 단순해진다
single activity가 많아진 이유는 activity를 없애려는 집착보다 화면 흐름의 주인을 바꾸는 편이 더 편해졌기 때문입니다.
back stack을 보는 관점이 달라졌다
single activity가 퍼진 가장 큰 이유 중 하나는 back stack을 다루는 감각이 달라졌기 때문입니다. multi-activity에서는 화면 A에서 B로 가면 보통 Activity A 위에 Activity B가 올라갑니다. 단순한 앱에서는 직관적이지만, 로그인 이후 특정 화면으로 보내기, 바텀 탭마다 다른 히스토리 유지하기, deep link로 중간 화면부터 진입하기 같은 장면에서는 어떤 Activity가 쌓였는지보다 어떤 destination 스택을 유지할지가 더 중요해집니다.
Navigation Component는 바로 이 지점을 다루기 좋습니다. 공식 문서도 NavController의 back stack을 사용자가 방문한 destination을 관리하는 LIFO 구조로 설명합니다. 즉, 화면 히스토리를 Activity 묶음보다 앱 내부 흐름의 스택으로 보는 감각이 강해졌다고 볼 수 있습니다.
사실 single activity 감각이 완전히 새로운 것은 아닙니다. Fragment 시절에도 Activity 하나 안에서 여러 Fragment를 교체하고 addToBackStack()으로 이전 화면으로 돌아가는 흐름은 이미 있었습니다. 다만 앱이 커질수록 어떤 transaction을 back stack에 넣을지, 같은 화면 중복 진입을 어떻게 막을지, deep link와 하단 탭 규칙을 어떻게 읽을지 직접 관리해야 할 것이 많았습니다.
Navigation Component 이후에는 그 흐름을 graph와 destination 중심으로 더 일관되게 다룰 수 있게 됐습니다. 그래서 single activity는 예전엔 못 하던 새 기술이라기보다, 이미 하던 패턴이 더 관리 가능한 형태로 정리된 결과에 가깝습니다.
single activity가 편하게 느껴지는 또 다른 이유는 shared state 때문입니다. 예를 들어 목록 화면에서 필터를 고르고 상세로 갔다가 다시 돌아왔을 때 같은 목록 상태를 유지하고 싶다면, multi-activity에서는 Activity 경계를 넘을 때마다 intent extras나 공통 저장소를 더 적극적으로 써야 할 때가 많습니다. 반대로 single activity에서는 같은 호스트 안에서 화면들이 움직이기 때문에 상태 소유권을 더 단순하게 잡을 수 있습니다.
- 같은 navigation graph 범위의 ViewModel 공유
- Activity 범위 shared ViewModel 사용
- 화면별 UI state와 공통 state의 경계 구분
- event는 짧게 흘리고 state는 holder에 유지하는 패턴 정리
이 구조는 Android가 권장하는 single source of truth, unidirectional data flow와도 잘 맞습니다. 즉, 화면 전환이 일어나도 상태의 주인이 여기저기 흩어지지 않게 만들기 쉬워집니다.
state restoration을 한 호스트 안에서 생각하기 좋아졌다
single activity가 많아진 이유를 state restoration 없이 설명하면 반쪽짜리가 됩니다. 실무에서는 화면을 한 번 그리는 것보다 돌아왔을 때 어떻게 이어질지가 더 중요할 때가 많기 때문입니다. 바텀 탭 A에서 스크롤을 내린 상태로 상세 화면에 갔다가 다른 탭으로 갔다가 다시 복귀하거나, 앱이 백그라운드에 있다가 프로세스가 정리된 뒤 재진입하는 상황을 떠올려보면 이해가 쉽습니다.
Navigation 문서도 popUpTo와 saveState/restoreState를 통해 destination 상태와 back stack을 저장했다가 나중에 복원할 수 있다고 설명합니다. Compose 쪽에서도 rememberSaveable, SavedStateHandle 같은 도구가 화면 상태 복원 문제를 더 세밀하게 다룰 수 있게 해줍니다. 즉, single activity는 복원 가능한 흐름을 한 호스트 안에서 설계하기 쉬운 구조라고 보는 편이 맞습니다.
Compose와 만나면서 더 자연스러워졌다
Compose 시대에 single activity가 더 흔하게 보이는 이유도 여기 있습니다. Compose에서는 화면을 독립 Activity보다 composable destination 단위로 보는 감각이 훨씬 자연스럽습니다. NavHost 하나를 두고 route를 이동시키는 모델이 기본 흐름처럼 받아들여집니다.
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onItemClick = { itemId ->
navController.navigate("detail/$itemId")
}
)
}
composable("detail/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = requireNotNull(itemId))
}
}
}이 구조에서는 화면 전환의 주인공이 Activity가 아니라 route와 back stack입니다. 게다가 Compose는 상태 호이스팅, remember, rememberSaveable, ViewModel 연계가 중요합니다. 이런 방식은 액티비티를 여러 개 두는 모델보다 한 호스트 안에서 상태와 이동을 묶어 보는 모델과 더 잘 어울립니다.
single activity의 단점도 분명하다
single activity가 많아졌다고 해서 언제나 더 낫다고 말할 수는 없습니다. 외부 진입점이 강하게 분리된 앱, task 분리가 중요한 앱, 로그인 영역과 메인 앱을 OS 수준에서 뚜렷하게 끊고 싶은 경우, 매우 단순해서 graph를 두는 이점이 거의 없는 앱에서는 multi-activity가 더 자연스러울 수 있습니다.
- Activity 하나가 너무 많은 책임을 떠안을 수 있다
- navigation graph가 비대해지면 오히려 읽기 어려워질 수 있다
- 호스트 규칙을 여러 feature가 함께 건드리게 될 수 있다
- 화면 전환 문제가 state/event 문제로 숨어 디버깅 포인트가 바뀐다
즉, single activity는 구조를 단순화하기도 하지만 잘못 잡으면 문제를 한곳에 모으는 구조가 되기도 합니다.
실무 판단 체크리스트
- 이 앱의 화면 이동 규칙은 한 흐름으로 묶이는가
- back stack을 destination 중심으로 제어해야 할 일이 많은가
- 여러 화면이 shared state를 자주 나눠 가지는가
- Compose나 Navigation Component 중심 구조와 잘 맞는가
- 복원해야 할 화면 상태가 많고 그것을 한 호스트 안에서 관리하는 편이 유리한가
- 반대로 task 분리나 외부 진입점 분리가 더 중요한가
앞의 질문에 많이 해당하면 single activity가 꽤 자연스럽습니다. 반대로 뒤쪽 질문이 더 중요하면 multi-activity도 충분히 합리적입니다.
정리
안드로이드 single activity가 많아진 이유는 단순한 유행이 아닙니다. Navigation Component가 destination 중심 back stack을 더 잘 다루게 했고, Fragment 시절보다 흐름을 graph로 읽기 쉬워졌고, shared state와 state restoration을 한 호스트 안에서 묶어 설계하기 쉬워졌고, Compose까지 이 방향과 잘 맞았기 때문입니다.
그래서 지금은 화면 전환을 Activity보다 screen flow로 보는 프로젝트가 많아졌습니다. single activity는 정답이라서가 아니라 navigation과 state 문제를 더 싸게 풀 수 있는 경우가 많아서 널리 쓰이는 구조입니다.
함께 보면 좋은 글로는 안드로이드 클린 아키텍처, 작은 앱에도 필요할까, SavedStateHandle은 언제 써야 할까, remember와 rememberSaveable은 언제 다르게 써야 할까, StateFlow와 SharedFlow 차이를 이어서 읽어보면 흐름이 더 잘 잡힙니다.
공식 기준은 Guide to app architecture, Recommendations for Android architecture, Navigation and the back stack, Fragment manager, Save UI state in Compose 문서를 같이 보면 더 분명해집니다.