Jetpack Compose가 UI를 그리는 방법: 구체화 (요약 버전)
How Jetpack Compose draws the UI: Materialize
Jetpack Compose 를 사용하다보면 무의식적으로 궁금해지는것이 있습니다.
다 Unit함수로 이루어져 있는데 어떻게 UI 가 그려지지?
컴포즈가 컴포저블 트리를 읽고 해석하여 UI로 산출하는 과정을 컴포즈 내부에서는 “구체화(Materializing)” 하는 과정 이라고 부릅니다.
이 구체화 과정을 마치게 되면 우리에게 보여지게 되는것이고, 이제부터 구체화에 대해 알아보겠습니다. 그 전에, 컴포저블이 어떻게 동작하는지에 대해 알아야 합니다. 모든 컴포저블은 슬릇 테이블(slot table)에 올라가는 순간 바로 UI로 그려지는게 아닌(= 구체화가 진행되는게 아닌) 변경 사항(change list)으로 내보내집니다. 이렇게 변경 사항으로 내보내지는 과정을 “방출(emitting)” 하는 과정 이라고 부르고, 매우 빠른 시간 안에 동일 컴포저블의 리컴포지션이 발생할 수 있으므로(예를 들어 애니메이션) 성능 최적화를 위해 컴포지션 이후로 이 과정을 거치게 됩니다.
방출 과정을 통해 변경 사항에 쌓인 컴포저블이 구체화가 진행됩니다. 모든 UI 컴포저블은 공통적으로 Layout 컴포저블을 통해 그려집니다.
Layout 컴포저블의 10번째 줄을 보면 ReusableComposeNode<T, E> 를 통해 방출 과정을 진행합니다. 이로 인해 함수가 Unit 리턴임에도 불구하고 UI 로 구성될 수 있게 됩니다.
ReusableComposeNode 의 구현을 보면 factory 와 Applier 을 제네릭으로 받고 있습니다. 이는 컴포즈가 멀티플랫폼 프레임워크이기 때문입니다. 멀티플랫폼이기 때문에 각 환경마다 사용하는 ComposeNode 의 타입이 달라집니다. 따라서 제네릭으로 factory 와 Applier 을 모두 처리해주고 있고, 안드로이드의 경우에는 ComposeUiNode 를 사용합니다. 위 Layout 코드의 10번째 줄을 다시 보면 T로 ComposeUiNode 를 받는것을 확인할 수 있습니다. 이를 통해 모든 유형의 ComposeNode 를 받아서 방출을 진행하는 ReusableComposeNode 는 컴포즈 런타임(androidx.compose.runtime)에 위치하고, ComposeNode 를 안드로이드 환경에 맞게 세부 구현한 ComposeUiNode 는 컴포즈 UI (androidx.compose.ui)에 위치한다는걸 추측해 볼 수 있습니다.
모든 컴포저블이 방출되면 이것을 구체화 하기 위해 컴포즈 UI 에 의해 구현되는 Applier 에 구체화 과정을 위임합니다. Applier 는 컴포저블 트리를 횡단하며 모든 노드를 해석하고 실제 UI 로 그리는 역할을 합니다. Applier 는 구체화하는 과정을 가지고 있으며, 이것 역시 컴포즈 멀티플랫폼을 위해 사용된 노드의 타입을 제네릭으로 받고 있습니다.
바로 위에서 컴포즈는 UI 에 의해 구현되는 Applier 을 사용한다고 강조했습니다. 따라서 위 Applier는 컴포즈 런타임에 위치한다는걸 예상할 수 있고, 이의 구현은 당연히 컴포즈 UI 에 위치할 것이라고 예상할 수 있습니다. (사실 위 강조가 아니더라도 Applier 가 인터페이스인걸로 유추할 수도 있습니다)
안드로이드의 경우 UiApplier 라는 구현을 사용합니다.
이 UiApplier 을 통해 컴포저블의 진입점으로 사용하는 setContent 함수가 구현되고, 모든 컴포즈 LayoutNode 를 자체의 캔버스에 그리는 최상위 window decor view 에 AndroidComposeView 를 추가합니다. 또한 모든 노드의 부모로 지정하여 안드로이드의 View 시스템과 연결됩니다. 여기서 Context 와 생명 주기와 같은 안드로이드 시스템 관련 CompositionLocal 이 컴포저블 트리로 provide 됩니다. 최종적으로 AndroidComposeView 를 통해 ViewGroup 에 할당되고, 구체화가 진행될 때 마다 ViewGroup 의 dispatchDraw 를 통해 안드로이드 캔버스로 그려지게 됩니다.
끝!
지금까지 컴포즈가 UI 를 실제로 그리는 방법에 대해서 간단히 알아보았습니다. 글을 읽으시면서 이해가 안되는 부분들이 분명 있으실거라고 생각합니다. slot table 과 change list 의 의미, Applier 가 실제로 구현되는 방법, 추가로 ReusableComposeNode 이면 재활용 불가능한 ComposeNode 도 있는건가? 등등 많은 내용들이 생략되었습니다. 추후 “A fully diving into Jetpack Compose runtime” 에서 이를 포함하여 더 자세하게 다룰 예정입니다(목차에서 확인 가능). 감사합니다.
+ 내용 확장판이 작성됐습니다: [Jetpack Compose가 UI 를 그리기 까지의 여정]
안드로이드 개발자 분들을 위한 카카오톡 오픈 채팅방을 운영하고 있습니다.