유저의 액션이 어떻게 UI에 렌더링될까? 먼저 큰 그림부터 살펴보자
플러터 아키텍쳐는 위와 같이 세가지 레이어로 이루어져있다. 먼저 Embedder Layer는 플러터앱을 각 플랫폼별 OS에 띄우도록 브릿징하는 역할을 수행한다. 따라서 이부분은 플랫폼별 언어(자바, 옵젝씨, c++)로 작성되어 있다. Engine Layer는 플러터의 저수준엔진으로 C++ 언어로 작성되어 그래픽,텍스트등의 렌더링을 수행한다. 이곳에는 우리가 접하는 dart:ui도 있는데 C++ 코드를 다트로 쉽게 다룰 수 있도록 래핑한 라이브러리이다. 위 그림에서 볼 수 있듯이 우리는 다트로 ui를 렌더링하려면 반드시 저수준 C++코드를 호출해야하고, 이러한 저수준코드를 다트로 호출하려면 dart:ui 를 쓸 수 밖에 없다. 즉, 다트로 ui를 렌더링하는 어떤 코드를 쓰든 결국 dart:ui를 통하게 된다.
다음으로 Framework Layer는 우리가 직접 쓰는 코드들로 대표적으로 Material 이 있다. 유저의 액션은 당연히 우리가 작성한 다트코드로 들어와 세레이어를 거쳐 platform 별 언어로 밑바닥까지 전달되어 렌더링되게 된다. 자 그러면 우리가 가장 자주쓰는 Framework Layer를 좀더 자세히 살펴보자
Widget Tree | Flutter Framework Layer | '어떻게 보여야 할지'를 선언하는 설계도 |
Element Tree | Flutter Framework Layer | 실제 앱 UI의 위치와 상태를 관리하는 중간 레이어 |
Render Tree | Flutter Framework Layer (정의), Flutter Engine Layer (구현) |
"레이아웃 계산"과 "그림 그리기"를 담당하는 객체들. dart:ui 엔진 코드와 연결되어 진짜 화면에 그림. |
우리가 사용하는 프레임워크 레이어는 세가지 트리로 되어있다. 먼저 UI껍데기 역할을 하는 위젯트리, UI의 생명주기와 상태를 관리하는 엘레멘트 트리, 레이아웃 계산과 실제 렌더링을 위한 C++ 코드 호출을 위해 dart:ui 를 부를 수 있는 렌더트리로 구성되어있다.
렌더트리 이후부터는 어떤 과정이 일어날까? RenderTree에서 레이아웃과 페인팅 정보를 담아 만들어진 RenderObject는 DisplayList를 만들게 된다. 그리고 이러한 리스트에 그려야할 정보가 차곡차곡 쌓이게 되고 Flutter Engine은 이러한 Display List를 GPU에게 전달하고 GPU는 이정보를 기반으로 실제화면에 픽셀로 렌더링하게된다.
User Input Phase | - 터치, 키보드 입력, 스크롤, 제스처 등을 받는다. - 입력 이벤트는 Flutter 이벤트 시스템을 통해 위젯 트리로 전파된다. - 예: 버튼 클릭 → onPressed 호출 → 상태(state) 변경 |
Animation Phase | - 애니메이션 프레임을 계산한다. - 시간(tick) 기반으로 위치, 크기, 투명도 등을 변화시킨다. - Flutter는 Ticker, AnimationController로 애니메이션을 부드럽게 만든다. |
Build Phase | - 바뀐 상태(state)에 따라 필요한 부분만 다시 build() 한다. - 즉, 변경된 위젯만 트리에서 다시 구성(rebuild)한다. - StatelessWidget은 build() 다시 호출, StatefulWidget은 setState() 통해 트리 일부만 갱신된다. |
Layout Phase | - 각 위젯의 크기와 위치를 계산한다. - 부모 → 자식에게 제약(constraints) 을 전달하고, 자식 → 부모에게 사이즈(size) 를 보고한다. - 리프(leaf)부터 시작해서 트리를 거슬러 올라간다. |
Paint Phase | - 위젯의 실제 모양을 정의한다. - 각 RenderObject의 paint(Canvas canvas, Offset offset) 메서드 호출 - 텍스트, 선, 도형, 그림자 등 시각적 요소를 캔버스에 그린다. |
Compositing Phase | - 여러 레이어(layer)를 GPU에 보내 하나로 합친다. - 화면 최적화를 위해 레이어를 구분해서 따로 그림(스크롤, 오버레이, 투명효과 등 최적화). |
지금까지 단계를 6 Phase 로 정리할 수 있다. 여기서 눈여겨볼점은 BuildPhase이다. BuildPhase에서 build 메서드가 호출되는 것을 확인할 수 있는데 여기서 호출되는 build 메서드가 의미하는 바가 크기 때문이다.
위 사진은 플러터 공식문서에서 플러터는 선언형(declarative) UI 프레임워크이다! 라면서 제시하는 이미지이다. 위 공식이 의미하는 바는 State에 따라 산출되는 UI는 불변이라는 것이다.
you change the state, and that triggers a redraw of the user interface. There is no imperative changing of the UI itself (like widget.setText)—you change the state, and the UI rebuilds from scratch.
공식문서 언급에서도 알 수 있듯이 UI는 수정되지 않는다. 상태에 따라 "다시 그려질 뿐"이다. 그리고 여기에 있는 state를 감싸서 실행하는 function이 바로 buildPhase의 build() 메서드이다.
즉 빌드메서드가 불리기 이전의 단계(User input phase 와 Animation Phase에서는 상태가 바뀌지않기 때문에 이미 만들어진 트리를 재사용하여 프레임워크 레벨에서 까다로운 상태 관리와 UI 수정로직을 분리시켰다. 이를 통해 애니메이션이나 UI변화가 복잡한 앱에서도 플러터는 상태관련 트리는 그대로 재사용하여 60fps 이상의 매끄러운 ui변화를 렌더링할 수 있는 것이다.
그래서 렌더링 과정을 알면 프로젝트에 어떻게 활용할 수 있을까?
먼저 플러터가 트리를 불필요하게 재구성하지 않는 것이 성능상 중요하다. 다시말해 setState, build 메서드 호출을 필요할 때만 해야한다. 예를들어 특정 ui에서 애니메이션 작업을 한다고하자. 그런데 애니메이션의 변화를 setState를 통해서 주고있다..?? 이것만은 막아야함을 직감적으로 알 수 있다. 왜냐하면 애니메이션은 build phase 를 뛰어넘고 바로 Layout phase나 paintphase 로 넘어가서 동작시켜줘야하기 때문이다. 따라서 setState대신 AnimationController, Tween, AnimatedBuilder 등으로 리팩토링하며 layout/paint만 해줄 수 있는 최적화를 할 수 있다.
두번째는 setState와 build 메서드의 scope를 적절하게 설정해야한다. 만약 작은 버튼 하나만 바꾸면 될걸 Column에 setStaet 메서드가 걸려있으면 해당 Column과 그 밑에 있는 모든 자식들이 다시 build가 되어야하기 때문이다. 이러한 "dirtSubtree" 가 커질수록 build 호출 비용자체도 커진다.
마지막으로는 UI코드를 짤때 기본적인 자세이다. 예를 들어 텍스트 위젯의 텍스트를 바꾼다고 하자
잘못된 자세 | 올바른 자세 |
"버튼을 누르면 Text 위젯의 텍스트를 수정해야지" | "버튼을 누르면 상태를 바꿔야지 → Flutter가 알아서 Text를 새로 만들겠지" |
"UI를 직접 수정하자" | "상태만 바꾸고, UI는 다시 만들어라" |
렌더링 과정을 이해한다면 절대로 위젯 자체에 접근하여 바꿀 생각은 하지 않을 것이다 . 텍스트 위젯과 바인딩 되어있는 "텍스트" element를 어떻게 바꿀 것인가에 대해 생각하면 되기 때문이다. 플러터 개발자가 할 일은 state를 바꿔주고 그부분만 setState() 를 호출해주면 된다. (StatlessWidget이라면 바로 build메서드 호출) 나머지는 플러터 프레임워크의 동작에 맡기면 된다.
'Mobile Develop' 카테고리의 다른 글
즉시 평가와 지연평가 (0) | 2025.04.25 |
---|---|
외부 객체에서 State 함수에 접근하는 방법 (0) | 2025.04.19 |
Stateless Widget 과 Stateful Widget 의 선택 기준 (0) | 2025.02.02 |
Jenkins가 Flutter 경로를 찾지 못해 발생한 이슈 트러블 슈팅 (0) | 2025.01.23 |
이벤트에 반응하여 Stateless 위젯 아이콘 이미지 색 바꾸기 (0) | 2024.08.13 |