[Flutter] 간단한 UI 렌더링 과정

저우웅·2025년 10월 4일

Flutter

목록 보기
2/8
post-thumbnail

플러터는 OS에 관계 없이 일관된 UI를 보여주기 위해 만들어진 크로스 플랫폼 프레임워크입니다.

이번에는 플러터 앱의 UI가 화면에 어떻게 랜더링되는지에 대해서 알아보겠습니다.

해당 포스트의 내용은 개인적인 공부를 위한 것으로, 틀린 내용이 있을 수 있습니다




플랫폼별 렌더링 과정


Native - Android

View

  • 기본적으로 View라는 트리 구조로 UI를 구성함
  • 각 View는 UI가 화면에 어떻게 보일지를 정의하는 역할

SurfaceFlinger

  • View로 정의된 UI를 GPU에 전달하는 역할
  • 어디에 뭐를 넣어야 하는지 지시하는 역할로 보면 쉽다!

GPU + Hardward Composer

  • GPU는 SurfaceFlinger의 명령을 받아 버튼 등을 실제 픽셀로 칠한다.
  • Hardward Composer는 GPU가 그림을 효율적으로 그릴 수 있도록 돕는 도우미 역할

Choreographer

  • 보통 화면은 1초에 60장 그려지면 부드럽게 보인다.
  • 앱이 위 리듬에 맞춰 화면을 그려 부드럽게 보일 수 있게 조율하는 역할


Native - iOS

UIView

  • iOS 앱은 UIView로 이루어진 트리 구조

Core Animation

  • 화면을 더 부드럽고 빠르게 보여주기 위해 사용한다.
  • 미리 애니메이션 동작(위치, 크기, 색상 변화 등)을 계산해놓음.
  • 따라서 애니메이션이 시작되면 CPU가 계속 연산할 필요 없이 GPU가 바로 실행할 수 있음!

CALayer

  • UIView 뒤에는 CALayer라는 꾸미기 레이어가 붙어있다.
  • 그림자, 모서리, 배경색 등 시각적 효과 담당
  • (UIView → 구조) , (CALayer → 꾸미기 & 효과) 라고 보면 쉽다!

GPU + Metal/OpenGL

  • 계산된 결과는 Metal이나 OpenGL 같은 그래픽 엔진을 통해 GPU에 전달된다.

WindowServer

  • 여러 앱이 하나의 화면에 뜨는 경우가 생길 수 있음! (알림창 등)
  • 여러 화면 요소를 한 장의 화면으로 합쳐서 보여주는 역할


Cross platform - React Native

JavaScript

  • React.js 기반으로 화면을 어떻게 보여줄지 적습니다.
  • React의 문법으로 모바일 화면을 만들 수 있게 확장한 것!

Bridge

  • 당연히 JS는 모바일 화면을 직접 그릴 수 없음
  • Bridge를 건너서 네이티브 언어로 번역 (안드로이드야 버튼 하나 만들어 줘...)

Virtual DOM

  • React.js와 같이 Virtual DOM이 변경을 감지한 후 업데이트
  • (웹과의 차이) 변경 감지의 흐름: Virtual DOM → Native Bridge → Native View

Native UI 그리기

  • JS에서 시작되어 번역된 내용은 최종적으로 UIView(iOS), View(Andriod)가 된다.
  • 이걸 GPU가 그려서 우리 눈에 보이게 됨.

JavaScript 엔진

  • 당연히 React Native 코드는 혼자 앱 실행 못 함!

  • JS코드를 읽고 실행해주는 두뇌(= JS엔진)가 필요

    • JSC (JavaScriptCore)
      • Apple이 만든 엔진, Safari에서도 쓰임
      • React Native 초창기부터 쓰임
      • 모바일(특히 안드로이드)에서 느림 + 메모리 사용량 많음
    • Hermes
      • Meta가 만든 모바일 전용 최적화 JS엔진
      • 시작 속도 빠르고, 메모리 절약(가벼움)
    • V8(React Native 사용 X)
      • Google이 만든 엔진, Chrome에서 사용중
      • iOS 문제, 메모리 사용량 등의 문제로 사용 X


Cross platform - Flutter


플러터의 렌더링에서는 세 개의 핵심 트리 구조가 존재합니다.

이 세 트리는 서로 연결되어,
설계도(Widget Tree) → 상태관리(Element Tree) → 실제 그리기(Render Tree)의 과정을 담당합니다.

Widget

  • Flutter의 UI는 전부 다 위젯으로 구성됩니다.
  • UI의 설계도 역할을 한다고 보면 쉽습니다..

Element

  • Widget은 설계도의 역할을 하기 때문에, 실제 화면에 배치될 때는 Element로 변환됩니다.
  • Element는 Widget의 인스턴스를 관리하면서 변동사항을 추적합니다.

RenderObject

  • Element를 통해 만들어진 구조를 실제로 그리는 역할을 수행합니다.
  • 크기 계산(Layout)과 화면에 그리기(Painting)을 수행

Skia / Impeller

  • Flutter는 기본 View 시스템을 이용하지 않고 자체 엔진을 사용
    • Skia: 2D 그래픽을 빠르게 처리, 안정적
    • Impeller: 부드러운 애니메이션, GPU효율 ⬆️



Flutter의 렌더링 과정


파이프라인

  1. User input
  • 터치, 스와이프와 같은 사용자의 입력
  • UI 업데이트의 트리거가 됩니다.
  1. Animation
  • 애니메이션 컨트롤러가 시간에 따라 상태를 갱신합니다.
  • 매 프레임마다 애니메이션 값(위치 등)이 바뀌면 다음 단계들이 실행됩니다.
  1. Build
  • build() 가 호출되면 새로운 Widget Tree가 만들어집니다.
  • 기본적으로 Widget은 immutable입니다.
  1. Layout
  • 부모가 자식에세 constraints를 내려줌 → 자식이 constraint 내에서 자신의 크기를 선택 → 부모에게 자신의 크기를 전달
  • 위 규칙을 = constraints go down, sizes go up 이라고 합니다.
  • 이 과정에서 RenderObject가 performLayout()을 실행해 위치/크기를 결정 → 레이아웃 및 페인팅 책임자 = RenderObject

  1. Paint
  • 레이아웃된 결과를 바탕으로 paint()가 호출되어 그리기 명령을 작성합니다. (아직 픽셀은 아님!)
  1. Composition
  • 여러 UI 요소들을 Layer 단위로 관리
  • 어떤 Layer를 composite해서 GPU한테 넘길 건지 결정
  • RepaintBoundary로 레이어를 분리하면 특정 부분만 다시 그리게 할 수 있음.*
  1. Rasterize
  • Layer Tree(= 합성된 Layer들, 네 번째 트리)가 GPU로 넘어가서 Skia/Impeller 같은 엔진이 실제 픽셀 명령(삼각형 등)으로 변환하여 화면에 출력.



특징

자체 렌더링 엔진(Skia)

  • Flutter는 Android나 iOS의 기존 UI 위젯 시스템을 전혀 사용하지 않음.
  • Skia를 통해 하나하나 직접 그린다!
  • 플랫폼 일관성 - OS에 구애받지 않고 동일한 UI 구현 가능

레이어 트리

  • 위젯 → Element → RenderObject → Layer Tree로 이어지는 구조
  • build()호출시 Widget Tree 다시 생성 → 기존 Element Tree와 비교하여 필요한 Element만 업데이트 하는 방식

네이티브 브리지 없음

  • React Native처럼 JS → Native UI로 가는 Bridge가 필요 없음.
  • Flutter는 Dart 코드 → 바로 엔진으로 연결됨. → Bridge 병목 X



저번 블로그에서 호기롭게 Flutter의 UI 렌더링에 대해서 알아보겠다고 했는데, 생각보다 방대하고 복잡한 개념 + 플랫폼별 렌더링 과정에 분량을 잡아먹혀 자세히 알아보긴 무리였습니다...🙂‍↕️
다음에는 조금 더 가볍고 재미있는 주제로 찾아오겠습니다.

profile
ㅠㅠ

10개의 댓글

comment-user-thumbnail
2025년 10월 4일

플러터의 렌더링 구조는 다른 프레임워크와 확인히 다르네요! 트리 구조를 기반으로, 필요한 부분만 업데이트한다면 상당히 효율적으로 렌더링을 진행할 수 있겠어요. 자체 렌더링 엔진을 활용해서 플랫폼 종속성도 없고, 병목현상도 없다니! 최근 앱 개발의 트렌드가 플러터로 이동하고 있는지 알 수 있는 내용이었습니다!

1개의 답글
comment-user-thumbnail
2025년 10월 7일

랜더링 과정이 정말 신기하네요!
이건 플러터의 특징일까요, 아니면 대부분의 앱 프레임워크의 특징일까요?
웹 프런트엔드는 화면 랜더링을 브라우저 단에서 처리해주니 깊게 고민할 필요가 없는데, 앱에서는 랜더링 과정에서 GPU 의 처리도 함께 고려하는군요!

1개의 답글
comment-user-thumbnail
2025년 10월 7일

플러터를 쓸 때마다 어떻게 부분적으로 업데이트를 하는지, 왜 빠른지 궁금했었는데 렌더링 구조를 보니 조금은 이해가 되는 듯 하네요! 플러터의 장점이자 단점인 자체 렌더링 시스템이 어떤 구조로 이루어져 있는지 알 수 있어서 재밌게 읽었습니다!

1개의 답글
comment-user-thumbnail
2025년 10월 7일

플러터에선 별도의 Bridge 패턴이 필요 없다는 사실이 신기하군요! Dart가 네이티브 코드로 바로 컴파일된다고 알고 있긴 한데, 그 이후 Skia라는 엔진을 통해 아예 네이티브 UI 구조를 사용하지 않는다는점은 처음 접해서 좋았습니다.

1개의 답글
comment-user-thumbnail
2025년 10월 8일

자체 렌더링이라 병목은 없지만, 이래서 iOS 앱에 리퀴드 글래스 적용이 바로 안되는거군요!

1개의 답글