[Flutter]렌더링 과정 이해하기

한상욱·2024년 8월 21일
0

Flutter

목록 보기
17/26
post-thumbnail

들어가며

이전 글을 참고하시면 본 글을 읽을 때 굉장히 도움이 될 것이라고 생각합니다. 아래 링크를 통해서 이전에 Flutter의 위젯에 대한 이야기를 미리 읽어주세요.

[Flutter]StatelessWidget & StatefulWidget

위 글에 상단부에는 위젯에 대한 내용이 언급되어 있습니다. 위젯은 계층적으로 선언되며 모든 위젯은 불변선언이라고 하였습니다. 위 글에서는 계층적으로 구성되어 어떻게 Flutter가 사용자의 상호작용을 통해 새롭게 UI를 갱신시키는지에 대하여 알아보았습니다.

하지만, 불변선언이라는 것에 조금 의문이 생길 수 있습니다. 기본적으로 Flutter는 자식 위젯의 build()메소드를 재실행하여 새로운 UI를 그린다고 하였습니다. 이렇게 되면 계층형 즉 트리구조로 이루어진 위젯은 UI가 갱신됨에 따라서 새로운 트리 구조를 이루게 될 것입니다.

실제로는 위 도시화된 위젯트리처럼 사용자의 상호작용으로 인해 위젯트리가 가변적입니다. 즉, mutable하다고 할 수 있을 것입니다. 하지만, 공식문서에서는 imutable, 불변적이라고 했습니다. Flutter는 어떻게 이를 가능하게 하는 것일까요?

크로스 플랫폼 렌더링 과정

크로스 플랫폼 프레임워크는 완벽하게 Native의 퍼포먼스를 따라갈 수 없습니다. 당연하지 않을까요?? iOS는 Swift, Android는 Kotlin으로 개발하는 것을 각 회사가 각 언어로 지정했으니, Swift, Kotlin은 해당 OS에 최적화된 성능을 보여줄테니 말이죠.

크로스 플랫폼 프레임워크는 Native 라이브러리 위에 추상화 계층을 만들어서 각 OS에 맞는 UI를 작성하려고 합니다. 예를 들어, RN(React Native)에 경우 Javascript Bridge라는 것을 이용하여 각 OS에 맞는 코드로 변환하게 됩니다.

이러면 어떠한 문제가 발생하게 될까요? 일단 iOS, Android는 독립적인 OS이므로 서로 공통적인 부분이 존재하지 않을 수 있겠죠. UI 혹은 로직이 복잡할 수록 이를 해결하기 위해서 추상화 계층에서 많은 오버헤드가 발생할 수 있습니다.

Flutter 렌더링 엔진

반면에, Flutter는 놀랍게도 단일 플랫폼 프레임워크와 비슷한 퍼포먼스를 보여줍니다. Flutter는 이러한 크로스 플랫폼의 추상화를 최소화하기 위하여 각 OS의 시스템 라이브러리를 우회하여 자체 위젯 세트를 사용합니다.

쉽게 이야기 하자면, Flutter는 각 OS에 맞는 코드로 변환하는 것이 아니라 UI를 각 OS에 그림을 그리듯이 그린다고 할 수 있습니다. 이를 가능하게 하는 것이 바로 Skia(iOS에서는 Impeller) Engine입니다. Skia Engine은 C++로 작성되어 굉장히 빠르게 동작합니다.

위젯 트리, 요소 트리, 렌더 트리

Flutter가 사용자의 입력부터 렌더링까지의 과정을 공식문서에서는 아래와 같이 도시화하였습니다. 제일 중요한, Build 과정을 알아보죠.

Flutter로 코드를 작성하면 위젯이 계층화 구조로 나타나는 위젯트리를 볼 수 있다고 했습니다. 하지만 실제로는 세가지 트리를 갖습니다. 각각 위젯 트리, 요소 트리, 렌더 트리라고 합니다. Flutter에서 UI를 생성하기 위해 사용하기 위하여 이 세가지 트리를 결합하게 됩니다.

1. 위젯트리(Widget Tree)

위젯트리는 코드로 작성된 실질적인 위젯 계층 구조를 의미합니다. 아래와 같은 코드를 Dart로 작성한다고 하겠습니다.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text("Hello World!"),
        ),
      ),
    );
  }
}

위 코드에서 위젯트리는 MaterialApp >> Scaffold >> Center >> Text라고 할 수 있습니다. 이는 코드를 작성하는 개발자가 UI를 작성하기 위해 선언한 위젯의 계층 구조이죠.

2. 요소 트리(Element Tree)

빌드를 통해서 우리가 선언한 위젯 트리는 요소 트리로 변환되게 됩니다. 위젯 트리를 구성하는 모든 위젯은 고유한 요소를 갖고 있습니다. 각각의 요소는 위젯의 인스턴스를 나타냅니다.

요소는 위젯의 상태도 함께 관리합니다.

3. 렌더 트리(Render Object Tree)

이제 렌더 트리에 대해서 알아보겠습니다. 변환된 요소를 통해 이제 본격적으로 화면을 렌더링 하기 위하여 각 요소들은 RenderObject라는 것으로 변환됩니다. RenderObject는 부모 자식 관계의 위젯들의 관계에 따라서 위치와 크기를 결정합니다. 각 위젯이 화면에 어떻게 그려질지, 레이아웃 계산과 관련된 모든 작업을 처리합니다.

Flutter에서 이제 UI를 렌더링 하기 위해서 렌더 트리를 깊이 우선 순회를 합니다. 순회를 하며 부모에서 자식으로 크기 제약을 전달합니다. 이 전달된 제약 조건을 자식은 무조건 존중하며 자식은 부모에게 제약 조건 내에서의 크기를 결정하면 크기를 전달하여 응답합니다.

이를 통해서 Flutter가 UI를 렌더링할 준비를 마치게 됩니다.
각 트리의 관계는 아래와 같다고 할 수 있습니다.

요소들이 각 위젯을 가르키는 것은 현재 위젯트리에서의 위치를 가르킨다고 생각하면 됩니다.

Flutter 렌더링 과정

위젯 트리, 요소 트리, 렌더 트리를 알아보았으니 어떻게 Flutter가 이 세 트리를 이용하여 UI를 렌더링하는지 알아보겠습니다.

빌드를 통해서 선언된 위젯을 바탕으로 새 위젯 인스턴스를 생성합니다. 이 위젯 인스턴스는 불변적이므로 생성 후 상태를 변경할 수 없습니다. 만약 UI 갱신이 발생한다면 이전 트리와 비교하여 새로운 트리를 생성합니다.

생성된 트리를 기반으로 렌더 트리를 생성합니다. 그리고 렌더 트리를 순회하면서 제약 조건을 전달 및 응답받으며 UI에서 렌더링 위치를 결정하게 됩니다.

마지막으로, 이 결과를 기반으로 Flutter Engine에 전달하여 GPU를 통하여 UI를 화면에 그리게 되는 것입니다.

이 과정이 Skia Engine을 통해 빠르게 수행되므로 Flutter는 단일 플랫폼 라이브러리와 비슷한 수준의 퍼포먼스를 보여줄 수 있는 것입니다.

profile
자기주도적, 지속 성장하는 모바일앱 개발자가 되기 위해

0개의 댓글