이번 포스팅에서는 Future Flutter에서 에이든님의 강연을 바탕으로 Flutter의 렌더링 엔진의 내부 구조를 알아보려고 한다.

[HotReload를 통한 위젯의 부분 렌더링을 퍼즐로 표현해봤다]
Flutter가 부드럽고 효율적인 UI를 제공할 수 있는 이유는 렌더링 파이프라인 덕분이다.
플러터에서 모든 UI는 widget으로 시작하고 끝난다.
버튼에서 컨테이너에 이르기까지, 모든 시각적 요소가 widget이다.
이러한 구조 덕분에 우리는 widget을 중첩하고 조합하여 UI를 구성하게 된다.
우리가 앱을 구동하면 시작되는 runApp 함수부터 widget을 호출하고 앱이 종료될때까지 widget을 보게 된다.
플러터에서는 widget을 모든 시각적 요소를 나타낸다고 표현하고 있다.
runApp 함수는 모든 Flutter 앱의 시작점이다.
이 함수는 위젯 트리를 생성하고 이를 화면에 연결하는 역할을 한다.
runApp이 호출되면, 내부적으로 _runWidget 함수가 실행되고,
이는 다시 attachRootWidget을 호출하여 루트 위젯을 빌드 소유자에 연결한다.
이 과정을 통해 Flutter의 렌더링 사이클이 시작된다.
플러터에서 렌더링은 widget과 RenderObject의 계층을 통해 이루어진다.
widget은 UI의 모양을 정의하고, RenderObject는 실제로 이를 화면에 그린다.
최종적으로 이 모든 과정은 캔버스에 그려지며, 사용자가 볼 수 있는 화면이 완성된다.
runApp → _runWidget → attachRootWidget: 위젯 트리 시작
Widget → Element → RenderObject: 계층 구조 생성
RenderObject → Canvas: 최종 렌더링 및 그리기
Canvas → 화면: 사용자에게 표시
이러한 과정은 각 단계가 유기적으로 연결되어 작동하며, Flutter가 효율적인 UI 렌더링을 가능하게 한다.
강연에서는 간단한 위젯인 ColoredBox를 통해 렌더링 예시를 보여주셨다.
간단해 보이는 ColoredBox 위젯도 실제로는 깊은 계층 구조를 갖는다.
ColoredBox는 SingleChildRenderObjectWidget을 확장하고, 이는 다시 RenderObjectWidget을 확장하며, 궁극적으로는 Widget 클래스를 상속받는다.
이처럼 플러터는 가장 기본적인 위젯조차 복잡한 시스템을 기반으로 한다.
왜 이렇게 복잡한 구조로 ColoredBox가 구현되었을까?
플러터는 복잡한 UI의 구현을 위해 기본 위젯을 가지고 다양한 용도에 쓸 수 있도록 확장할 수 있다. ColoredBox같은 간단한 위젯도 여러 클래스 계층을 통해 다양한 기능을 지원하고 커스터마이징 할 수 있게하기 때문이다.
사실 이러한 부분은 대부분의 최신 UI에서 채택하는 구조이기도 하다.
에이든의 강연에서 중요한 포인트 중 하나는 Flutter 렌더링을 최적화하는 방법이었다.
불필요한 위젯 재구성을 피하고, 위젯의 크기, 색상 등의 파라미터를 효율적으로 관리함으로써 성능을 크게 향상시킬 수 있다.
예를 들어, 커스텀 위젯이 불필요한 전체 핫 리로드를 요청하지 않도록 하면 UI 스레드의 작업 부하를 줄일 수 있다.
그러면 왜 계층을 통해 유연하고 효율적으로 UI를 구성해야 할까?
그리고 어느정도 수준의 목적을 만족시켜야 하는지 알아보자.
Flutter에서 Element는 UI 트리를 구성하고 BuildContext를 호출하는 요소이다.
각 widget은 생성될 때 Element를 갖고 Element는 BuildContext와 연결되며,
이는 widget의 위치를 관리하는 역할을 한다.
(Element가 widget보다 lifecycle이 길다)
이러한 구조는 Flutter가 UI를 효율적으로 빌드하고 업데이트하는 데 중요한 기초가 된다.
이 Element의 업데이트 주기와 횟수를 통해 우리는 UI를 보고 자연스럽게 움직이거나 이동하는것을 느끼게 된다.
Flutter는 Fragment Shader를 통해 GPU의 성능을 활용하여 렌더링 작업을 처리한다.
이 셰이더는 수백만 개의 픽셀을 병렬로 처리할 수 있어 고해상도 이미지나 복잡한 그래픽을 효율적으로 다룰 수 있다.
이를 이용해 부드러운 애니메이션과 상호작용을 보장하려면 플러터는 build와 paint 단계가 각각 16ms 내에 처리가 이루어져야 한다.
초당 (build<= 16fps) + (paint<= 16fps) => 30fps를 유지할 수 있다.
최신 플래그십 기기들은 60fps, 120fps를 지원할 정도로 하드웨어 성능이 좋아졌기 때문에 효율적이고 빠른 빌드와 페인트 수준을 유지하는 것이 중요하다.
성능이 제한된 염가형 기기나 보급형 장치에서도 이 프레임 속도를 유지하려면 빌드 프로세스를 최적화하고 페인트 시간을 줄이는 것이 핵심이다.
Flutter 렌더링 최적화의 핵심은 "불필요한 그리기를 하지 않는 것"이었다.
재구성되는 위젯을 최소화하고, 최적화된 커스텀 위젯을 사용하는 것이 앱을 가볍고 효율적으로 만드는 비결이다.
상태 관리도 중요한 역할을 하며, 최대한 UI 상태가 변경될 때만 업데이트가 호출되도록 해야 한다고 언급하셨다.
출처 : 에이든님의 플러터 렌더링 해부학
Future Flutter에서의 강연을 기반으로 작성되었으며, 문제 시 삭제하겠습니다.
안녕하세요 에이든 입니다:)
정성스럽게 발표 정리해주셔서 감사합니다.
전하고 싶은 이야기는 더 많았으나 시간에 대한 압박 때문에 휘뚜루마뚜루 넘어간 이야기들 많았는데,
그런 이야기까지 담겨있는것 같아 감사했어요.
송도까지 찾아와주셔서 감사하고 오고가며 인사 나눌께요.