Jetpack Compose의 Composition
Jetpack Compose의 Composition
컴포지션(Composition)은 Jetpack Compose가 @Composable 함수를 실행하여 UI 트리를 구축하고 관리하는 핵심 프로세스입니다. @Composable 함수가 처음 실행되면, Compose 런타임은 해당 함수 호출을 내부 데이터 구조에 기록하고, UI 계층 구조를 나타내는 노드를 생성합니다. 이렇게 기록된 구조가 바로 Composition이며, 현재 화면에 표시되는 내용에 대한 단일 진실 공급원(single source of truth) 역할을 수행합니다.
Composition이 어떻게 생성되는지, 안드로이드 뷰 시스템과 어떤 방식으로 연결되는지, 그리고 생명주기를 어떻게 관리하는지를 이해하는 것은 Compose 개발의 핵심 토대가 됩니다. 실제 면접에서도 Composition의 내부 구조와 동작 원리에 대한 질문이 빈번하게 출제되므로, 이 개념을 확실하게 정리해 두시면 큰 도움이 될 것입니다.
이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- Composition이 무엇을 나타내며, UI 트리와 어떤 관계에 있는지 설명할 수 있습니다.
ComponentActivity.setContent가ComposeView를 통해 어떻게 Composition을 생성하는지 이해할 수 있습니다.ComposeView가 기존 안드로이드 뷰 계층 구조에 Compose를 어떻게 통합하는지 파악할 수 있습니다.- Composition이 언제 생성되고 소멸되며, 리컴포지션(Recomposition)이 어떻게 Composition을 갱신하는지 식별할 수 있습니다.
Composition이 나타내는 것
Composition은 Compose 런타임이 @Composable 함수를 실행할 때 구축하는 트리 구조입니다. 이 트리의 각 노드는 하나의 @Composable 함수 호출에 대응하며, 런타임은 슬롯 테이블(slot table)이라는 내부 데이터 구조를 활용하여 모든 노드의 상태(state), 매개변수, 위치를 추적합니다.
슬롯 테이블에는 Composition 과정에서 생성된 다양한 값이 저장됩니다. remember의 결과값, 상태 객체, 그리고 트리 내 각 @Composable의 고유 식별 정보(identity) 등이 여기에 포함됩니다. 상태가 변경되면, 런타임은 슬롯 테이블을 조회하여 어떤 @Composable 함수를 다시 실행해야 하는지 결정합니다. 이처럼 선택적으로 재실행하는 과정이 바로 리컴포지션입니다.
Composition은 직접 픽셀을 그리지 않는다는 점에 유의해야 합니다. Composition은 레이아웃 노드로 이루어진 트리를 생성하며, 이후 Layout 페이즈와 Drawing 페이즈에서 이 트리를 소비하여 최종 UI를 렌더링합니다. 즉, Composition은 "무엇을 그릴지"를 결정하는 단계이고, 실제 화면에 그리는 작업은 이후 단계에서 수행됩니다.
setContent를 통한 Composition 생성
안드로이드 앱에서 Composition을 생성하는 가장 일반적인 진입점(entry point)은 ComponentActivity.setContent입니다. 이 함수는 안드로이드 Activity 생명주기와 Compose 런타임을 연결하는 다리 역할을 합니다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
내부적으로 setContent는 먼저 Activity의 콘텐츠 뷰에 ComposeView가 이미 존재하는지 확인합니다. 이미 존재한다면 콘텐츠만 갱신하고, 존재하지 않는다면 새로운 ComposeView를 생성하여 윈도우에 연결한 뒤 @Composable 콘텐츠를 설정합니다.
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
if (existingComposeView != null) with(existingComposeView) {
setParentCompositionContext(parent)
setContent(content)
} else ComposeView(this).apply {
setParentCompositionContext(parent)
setContent(content)
setOwners()
setContentView(this, DefaultActivityContentLayoutParams)
}
}