일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 내부 단편화
- recyclerview
- Kotlin
- 플로이드워셜
- AAC
- 절대 주소
- NestedScrollView
- 상태관리
- GetX
- 뷰홀더
- http 역사
- 물리 메모리
- http발전과정
- appcompatactivity
- DiffUtil
- flutter
- Android
- 자이고트
- 데코레이터 패턴
- apk 빌드 과정
- 프로세스
- 리사이클러뷰
- 운영체제
- Dispatchers
- 리사이클러뷰풀
- viewModelScope
- 디자인 패턴
- 안드로이드
- AsyncListDiffer
- appcompatacitivity
- Today
- Total
hong's android
[Flutter] Flutter는 무엇인가? 본문
플러터로 앱을 개발하기 전 학습했던 내용들에 대해 공유해보고자 합니다.
Flutter?
Flutter가 iOS와 Android와 같은 서로 다른 운영 체제(OS)에서 동일한 코드를 재사용하여 앱을 개발할 수 있도록 설계된 “UI 툴킷”이다.
공식문서에선 “UI 툴킷”이라는 표현을 사용했다. UI툴킷은 사용자 인터페이스(UI)를 설계하고 렌더링 하는 데 필요한 도구와 라이브러리의 집합이다. 그러므로 플러터는 좀 더 UI를 설계하고 개발하는데 초점을 맞춘 프레임워크라는 생각이 든다. 쉽게 말하면, Flutter는 화면을 어떻게 그릴지, 그리고 UI를 어떻게 더 효율적으로 관리할지를 고민하는 데 최적화된 프레임워크라고 볼 수 있을 것 같다.
그래서 OS와 관련된 기능들은 플러그인을 사용하거나, 네이티브 코드를 호출하는 플랫폼 채널을 사용할 수 있다.
플러터가 Dart를 선택한 이유가 뭘까? (Kotlin도 있잖아..) 🧐
Why did Flutter choose to use Dart?
JIT 컴파일러는 프로그램 실행 중에 실행되어 즉석에서 컴파일한다. 런타임 전 실행되는 컴파일러는 AOT( Ahead-Of-Time ) 컴파일러라고 한다. 그래서 AOT 컴파일러를 사용하면 개발 주기가 느려지고, JIT 컴파일러는 빠른 개발 주기를 지원하지만 실행 시간이 더 느리다.
Dart 런타임과 컴파일러는 Flutter를 위해 두 가지 핵심 기능의 combination을 지원한다. JIT 기반의 빠른 개발 주기 및 상태를 유지하는 핫 리로드(Stateful Hot Reload)를 가능하게 하며, AOT(Ahead-of-Time) 컴파일러를 통해 빠른 실행과 프로덕션 배포에서 예측 가능한 성능을 제공하는 효율적인 ARM 코드를 생성한다.
모바일은 주로 ARM 프로세서(CPU)를 사용한다. AOT 컴파일러에 의해 생성된 ARM 코드는 ARM 프로세서에서 사용되는 명령어를 의미한다. 즉, AOT 컴파일러는 Dart 코드를 ARM 프로세서에서 실행 가능한 네이티브 코드로 변환하므로 앱이 더 빠르게 실행된다.
선언형 UI
기존 명령형 UI에서는 개발자가 UI 구성 요소를 수동으로 생성하고, 상태 변화에 따라 해당 UI를 직접 변경해야 합니다. 직접 UI로직을 변경하고, 상태 변경 과정을 개발자가 관리하면 코드가 복잡해지고 유지보수가 점점 어려워진다.
선언형 UI를 사용한 이유
선언형 스타일에서, Flutter의 위젯은 불변하며, 단지 가벼운 "청사진" 역할만 한다.
UI를 변경하려면, 위젯이 스스로 리빌드를 트리거(대부분 Flutter의 StatefulWidget에서 setState()를 호출)하고 새로운 위젯 서브트리를 구성한다.
// 선언형 스타일
return ViewB(
color: red,
child: const ViewC(),
);
여기서, UI가 변경될 때 기존의 인스턴스 b를 변경하는 대신, Flutter는 새로운 위젯 인스턴스를 생성한다.
프레임워크는 전통적인 UI 객체의 많은 책임(예: 레이아웃 상태 유지)을 RenderObjects로 관리한다.
RenderObjects는 프레임 간에 지속되며, Flutter의 가벼운 위젯은 프레임워크에 상태 간 RenderObjects를 변경하도록 지시한다. Flutter 프레임워크가 나머지 작업을 처리한다.
RenderObject?… 처음 들어본다. RenderObject에 대해 알아보자.
렌더링과 레이아웃
플러터가 위젯 계층을 실제 화면에 그려지는 픽셀로 어떻게 변환하는지를 다룬다.
1. Flutter의 렌더링 모델
기존 Android 앱의 동작 방식
1) Java 코드로 UI 작성
2) Android 시스템 라이브러리가 Canvas 객체를 제공한다.
3) Skia라는 C/C++로 작성된 그래픽 엔진이 CPU 또는 GPU를 호출해 화면 위에 UI를 그린다.
일반적인 크로스 플랫폼의 동작
1) 대부분의 크로스 플랫폼 프레임워크는 각 플랫폼의 기본 UI 라이브러리(Android와 iOS의 네이티브 라이브러리)의 추상화 계층을 제공한다. 쉽게 말하면, 이 말은 크로스 플랫폼 프레임워크가 Android와 iOS 같은 서로 다른 운영체제의 네이티브 UI 라이브러리(예: Android의 View, iOS의 UIView)를 직접 사용하는 대신, 그 위에 공통된 추상화 계층을 제공한다는 뜻이다.
2) 브릿지를 통해서 플랫폼과 통신하고, 플랫폼의 네이티브 컴포넌트를 그대로 사용하기 때문에 플랫폼 별 UI가 다르게 그려질 수 있다.
Flutter 특징
1) 플러터는 그러한 추상화를 최소화하고, 플러트는 자체 위젯 set을 사용한다.
2) 플러터의 Dart 코드는 네이티브 코드로 컴파일되며, 렌더링에 Skia(또는 Impeller)를 사용한다.
3) Flutter 엔진은 Skia 복사본을 포함하고 있어, OS 업데이트와 무관하게 최신 렌더링 개선 사항이 적용된다.
즉, 자체 렌더링 엔진을 사용해서 UI를 직접 그리기 때문에, 디바이스에 제한 없이 동일한 UI를 제공할 수 있다. 렌더링을 담담하는 엔진은 Flutter 3.10에는 Impeller, 이전에는 Skia가 있다.
유저 input에서 GPU에 이르기까지 데이터는 위와 같은 파이프라인을 거칩니다.
알리바바 테크 블로그에선 공식문서의 파이프라인을 좀 더 이해하기 쉽게 도식화하였다.
위 파이프 라인 모형에서 UI 스레드는 Animate -> Build ->.. -> Submit 일련의 과정을 거친 후, Layer tree를 생성하여 해당 tree를 GPU Thread에게 넘긴다. 또한 상단을 보면 3개의 Tree를 생성한다.
각각의 과정을 자세히 살펴보자.
1. UI Thread
UI 스레드는 렌더링 파이프라인의 1~5단계를 처리한다. 이 스레드는 Dart VM(dart를 실행하는 가상머신)에서 Dart 코드(애플리케이션 코드와 Flutter 프레임워크 코드 포함)를 실행한다. 그 후, 위젯 트리, 엘리먼트 트리, 렌더 오브젝트 트리를 빌드하고, 레이아웃 단계에서 위젯의 크기와 크기를 계산하며, Layer Tree를 만든다.
2. GPU Thread
GPU 스레드는 렌더링 파이프라인의 6~7단계를 처리한다. 이 스레드는 Flutter 엔진 내의 그래픽 관련 코드(주로 Skia)를 실행한다.
GPU 스레드는 다음 작업을 수행합니다: Layer Tree 획득, 래스터화(Rasterization), 합성(Composition), 화면에 그리기
세 개의 트리(위젯 트리, 엘리먼트 트리, 랜더 오브젝트 트리)는 무엇일까?
Widget Tree
화면에 어떤 위젯들이 나타나야 하는지를 표현한다. UI의 레이아웃, 스타일, 구조 등을 추상적으로 정의하는 역할을 하며, build 메서드가 호출될 때 생성되거나 업데이트된다.
개발자가 사용하는 위젯들이 해당되고, Flutter가 아닌 개발자가 UI 구성을 직접 관리한다
Element Tree
위젯 인스턴스와 실제 상태를 관리하는 트리로 위젯의 상태와 생명주기를 유지하며, 화면에 구체적으로 연결하는 역할을 한다. 위젯 트리가 빌드될 때 같이 생성된다.
RenderObject Tree
위젯들이 화면에서 차지하는 공간을 계산, 실제로 그려질 위치와 크기를 결정한다. Layout 단계에서 구성되며, 화면에 그릴 준비를 완료한 후 Paint 단계에 이 트리를 기반으로 실제 렌더링 작업이 이루어진다.
이 세 가지 트리로 나누어 관리함으로써 변경에 따른 불필요한 계산을 줄여 성능을 최적화할 수 있다.
Android 개발자의 Flutter 개발
안드로이드에서의 View와 Widget의 차이점
만약 안드로이드 XML을 사용한다면, 명령형 UI를 통해 뷰를 구현하게 된다. 하지만 Compose 덕분에 개발자들이 상태에 따라 뷰를 직접 업데이트하는 로직을 구성할 필요가 없어, 개발 생산성이 증가했다고 생각한다.
Flutter와 Android Compose는 모두 선언형 UI 패러다임을 따른다.
플러터에선 Intent가 있을까?
안드로이드에서 Intent는 4대 구성요소(컴포넌트) 간 의도를 전달한다. 예를 들어, 액티비티 간의 이동 또는 서비스 실행, 브로드 캐스트 송신과 같은 역할을 담당한다.
Flutter에서는 Activity나 Fragment 같은 개념이 없으며, 모든 화면은 하나의 Activity 내에서 이루어집니다. 대신 Navigator와 Route를 사용하여 화면 간의 이동을 처리한다.
Android의 액티비티 라이프 사이클과 같은
Android의 메인스레드는 UI 스레드인데, 플러터는 어떻게 동작할까?
Android에서는 메인(UI) 스레드에서 네트워크 작업이나 디스크 접근과 같은 시간이 오래 걸리는 작업을 수행하면 UI가 멈추고, 사용자 경험이 저하된다. 그러므로 이러한 작업은 백그라운드 스레드에서 처리하도록 권장된다. 이 작업을 위해 AsyncTask, IntentService, JobScheduler, 또는 RxJava와 같은 스레드 관리 도구를 사용합니다.
메인 스레드가 일정 시간 이상 멈추면, 시스템이 ANR 오류를 발생시킨다. 그래서 간혹 스레드를 잘못 관리했을 경우 ANR 오류로 앱이 종료됐던 경험이 있다..
Flutter는 단일 스레드에서 실행되며, Dart의 이벤트 루프가 작업을 관리한다. (Node.js와 비슷한 동작 방식)
개발자는 스레드를 직접 생성하거나 관리하지 않아도 된다. Dart의 Future와 async/await를 사용하면 I/O 작업을 비동기적으로 처리할 수 있다. Dart 이벤트 루프는 작업을 기다리는 동안 UI를 차단하지 않으므로, 메인 스레드가 멈추는 일이 없다.
싱글 스레드 & 이벤트 루프
Dart는 단일 스레드 언어이다. 이벤트 루프는 Dart 코드 실행 방식을 관리하는 데 중요한 역할을 한다. Dart 앱을 시작하면 Main isolate가 생성되고 이벤트 루프가 시작된다. 메인 함수인 void main()이 동기적으로 실행되는 첫 번째 함수이다.
특이하게 플러터는 Isolate란 걸 지원한다. Isolate는 스레드와 비슷하지만 독립적인 메모리를 갖고 있으며, 이벤트 루프를 가지고 있다.
이벤트 루프는 ‘MicroTask’와 ‘Event’를 처리하게 된다.
먼저, Future는 비동기 작업이다. 동기 작업이 끝난 후에 비동기 작업이 끝난다. 비동기 작업이 동기 작업이 실행될 동안 보관되는 대기열을 만들 필요가 있다. 그래서 ‘EventQueue’라는 큐에 저장한다.
‘MicroTask’는 동기 작업에 해당한다. 그래서 ‘MircoTask’가 끝난 후에 Event Queue의 태스크들을 처리한다.
공부를 하다보니 아직 플러터에 대해 모르는게 너무 많은 것 같다.. 차근차근 또 학습해야할것 같습니다.. :)
출처
'Flutter' 카테고리의 다른 글
[Flutter] 상태 관리 (0) | 2025.01.22 |
---|