플러터의 렌더링 작동 방식을 공부하자.
참고 이미지 : Flutter의 Framework 레이어
![]()
지난 시간에 플러터의 아키텍처 레이어 구조를 공부했었다.
오늘은 그 중에서 프레임워크 레이어에 속하는 렌더링 라이브러리가 무엇인지, 플러터에서 렌더링은 어떤 과정을 거치는지 알아보자.
참고 문서
Flutter 공식 문서 중 rendering library
https://api.flutter.dev/flutter/rendering/rendering-library.html
Flutter 공식 - 아키텍처 문서 중 Rendering and layout 섹션
https://docs.flutter.dev/resources/architectural-overview#rendering-and-layout
렌더링 라이브러리는 플러터의 렌더링 트리를 구성하는 데 쓰인다. 렌더링 트리가 무엇인지 알아보기 전에 우선 플러터가 어떤 과정으로 렌더링하는지 살펴보자.
보통 크로스플랫폼 프레임워크는 운영체제의 시스템 라이브러리를 사용하기 위해 그 위로 추상 클래스를 생성한다.
예를 들어 안드로이드에서 앱을 실행하고 화면에 위젯을 그린다고 가정해보자. 안드로이드 네이티브에는 위젯을 표시하는 기능이 있다. 프레임워크는 Javascript같은 인터프리터 언어를 써서 코드를 다듬어 해당 시스템만의 고유 위젯 기능을 호출하도록 바꾼다.
Interpreted 언어란?
Compiled 언어 : 소스코드 -> (컴파일러->) 어셈블리어 -> 실행
Interpreted 언어 : 소스코드 -> (인터프리터->) 실행
참고 문서 : https://www.freecodecamp.org/news/compiled-versus-interpreted-languages/
하지만 플러터는 추상 클래스 생성을 최소화한다.
다른 프레임워크가 시스템 UI 라이브러리를 빌려서 쓴다면 플러터는 그렇게 하지 않고 내장된 위젯 세트로 해결한다. 그 후 렌더링을 위해 작성한 dart 언어를 플랫폼에 맞는 언어로 컴파일해서 앱을 실행한다. 그래서 훨씬 빠르다고 한다...
(빠른거 말고 다른 장점이 더 있을지 궁금한데 못 찾음)
이미지 출처 : 공식 문서
위젯을 관리하려면 트리가 필요하다. 그런데 트리는 처음 정해진 뒤로 변하지 않는다는 속성을 가진다. 모든 위젯을 한 트리에서 관리한다면 앱이 조금 바뀔 때마다 트리를 전부 지우고 처음부터 다시 빌드해야 하므로 비효율적이다.
그래서 플러터는 불변하는 위젯(immutable)과 가변 위젯(mutable)을 구분하고, 또 렌더링을 담당하는 부분까지 해서 렌더링 트리를 총 세 갈래로 나누어 관리한다.
첫 번째 그림에 해당하는 위젯 트리는 불변 위젯을 표현한다.
트리는 기본적으로 1위젯 = 1트리라고 생각하면 된다. Scaffold안에 Container가 있고 그 안에 Row가 있고...이런 구조를 생각해보자.
플러터에서 화면에 위젯을 그릴 때는 먼저 크기부터 정해야 한다. 그래서 부모 위젯이 최초로 크기를 가진 뒤에야 그 안에 자식 위젯을 넣을 영역을 정하고, 자식 위젯의 크기 안에 또 자식 위젯을 넣을 수 있다.
예를 들어 ListView의 자식으로 ListView를 넣는 상황을 생각해보자. 트리 구조는 간략하게 표현해서
Scaffold - ListView - ListView
처럼 생겼을 것이다.
그럼 부모 ListView는 Scaffold의 크기를 참조해 영역을 정할 수 있다. (여기까지는 괜찮음)
하지만 ListView는 자체적으로 크기를 가지지 않기 때문에?
자식 ListView가 자리를 잡지 못하니까 이 단계에서 에러가 난다.
플러터의 빌드 함수에서는 이처럼 트리 형태로, 부모 위젯에서 자식 위젯 순서로 화면을 렌더링한다.
트리는 기본적으로 불변이다. 때문에 Widget tree
는 자식 노드 중에서 한 가지라도 변하면 바로 파기한 뒤 새 것을 만든다.
Element tree
도 변경사항이 있으면 새로 뽑지만, 이 때 변경된 부분 외의 기본 요소는 플러터가 상태 객체를 이용해 따로 관리(캐싱)한다. 그래서 위젯을 다시 만들더라도 비용이 적고, 겉으로 보기에는 마치 새로고침하는 것처럼 보인다.
정리하면
Element tree
에서 상태 객체를 관리한다.레이아웃을 배치하고 그리는 역할을 담당하는 트리이다. 사실상 플러터가 참조하는 것은 위젯 트리가 아니라 렌더 트리라고 한다.
렌더 트리는 부모에서 자식으로 위젯 크기를 전달하고 렌더링 준비를 마친다.
RenderView(렌더트리의 생성자)
-> compositeFrame()
(RenderView의 함수)
-> Scenebuilder
(화면 그리는 클래스)
-> 빌드 완료
-> Window.render()
(dart:ui의 빌트인함수)
-> GPU로 전달하여 렌더링 완료
끝..^^