성능 최적화 — DevTools, 리빌드 추적, const 생성자
성능 최적화 — DevTools, 리빌드 추적, const 생성자
Flutter는 기본적으로 성능이 좋지만, 앱이 커지면서 프레임 드롭이나 버벅임이 발생할 수 있습니다. 성능 문제를 찾고 해결하는 방법을 정리해보겠습니다.
Flutter DevTools
DevTools는 Flutter 공식 성능 분석 도구입니다.
# DevTools 실행 (앱이 실행 중일 때)
flutter run
# 터미널에 표시된 DevTools URL 접속
# 또는 직접 실행
dart devtools
주요 탭
| 탭 | 용도 |
|---|---|
| Flutter Inspector | 위젯 트리 탐색 |
| Performance | 프레임별 성능 분석 |
| CPU Profiler | 함수별 CPU 사용량 |
| Memory | 메모리 사용량, 누수 탐지 |
| Network | 네트워크 요청 모니터링 |
불필요한 리빌드 방지
1. const 생성자 사용
// 나쁜 예: 매 빌드마다 새 인스턴스 생성
Widget build(BuildContext context) {
return Column(
children: [
Text('제목'), // 매번 새로 생성
SizedBox(height: 16), // 매번 새로 생성
Icon(Icons.star), // 매번 새로 생성
],
);
}
// 좋은 예: const로 재사용
Widget build(BuildContext context) {
return Column(
children: [
const Text('제목'), // 재사용
const SizedBox(height: 16),
const Icon(Icons.star),
],
);
}
2. 위젯 분리
// 나쁜 예: 전체 위젯이 리빌드
class MyScreen extends StatefulWidget {
@override
State<MyScreen> createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
// count가 바뀌면 이것도 리빌드됨
const HeavyWidget(), // 불필요한 리빌드!
Text('$_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('증가'),
),
],
);
}
}
// 좋은 예: 변하는 부분만 별도 위젯으로 분리
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const HeavyWidget(), // 리빌드 안 됨
const CounterWidget(), // 이것만 리빌드
],
);
}
}
3. ListView.builder 사용
// 나쁜 예: 모든 아이템을 한 번에 생성
ListView(
children: items.map((item) => ListTile(title: Text(item))).toList(),
)
// 좋은 예: 보이는 것만 생성
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)
4. RepaintBoundary
자주 변하는 위젯을 격리하여 다른 위젯의 리페인트를 방지합니다.
RepaintBoundary(
child: AnimatedWidget(), // 이 위젯의 페인트가 부모에 영향 안 줌
)
빌드 메서드 최적화
// 나쁜 예: build에서 무거운 작업
@override
Widget build(BuildContext context) {
final processed = heavyProcessing(data); // 매 빌드마다 실행!
return Text(processed);
}
// 좋은 예: 상태 변경 시에만 처리
class _MyState extends State<MyWidget> {
late String _processed;
@override
void initState() {
super.initState();
_processed = heavyProcessing(data);
}
@override
Widget build(BuildContext context) {
return Text(_processed); // 캐싱된 값 사용
}
}
이미지 최적화
// 1. 적절한 크기로 리사이즈
Image.network(
imageUrl,
cacheWidth: 200, // 디코딩 크기 제한
cacheHeight: 200,
)
// 2. CachedNetworkImage 사용
CachedNetworkImage(
imageUrl: url,
memCacheWidth: 200,
)
// 3. precacheImage로 미리 로드
@override
void didChangeDependencies() {
super.didChangeDependencies();
precacheImage(const AssetImage('assets/logo.png'), context);
}
애니메이션 성능
// 나쁜 예: setState로 애니메이션
_controller.addListener(() {
setState(() {}); // 전체 build 재실행
});
// 좋은 예: AnimatedBuilder 사용
AnimatedBuilder(
animation: _controller,
child: const MyStaticChild(), // child는 리빌드 안 됨
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * pi,
child: child, // 재사용
);
},
)
프로파일 모드에서 테스트
# 프로파일 모드로 실행 (릴리스에 가까운 성능)
flutter run --profile
# 릴리스 모드
flutter run --release
디버그 모드는 JIT 컴파일, 어설션 체크 등으로 느립니다. 성능 테스트는 반드시 프로파일 모드에서 하세요.
면접 포인트: "디버그 모드에서 느린데요?"라는 질문에 "프로파일 모드에서 테스트해야 합니다. 디버그 모드는 JIT, 어설션, DevTools 오버헤드가 포함되어 실제 성능과 다릅니다"라고 답하세요.
체크리스트
□ const 생성자를 최대한 활용했는가?
□ ListView.builder를 사용했는가?
□ 무거운 위젯을 분리했는가?
□ build에서 무거운 연산을 하고 있지 않은가?
□ 이미지 크기를 제한했는가?
□ 애니메이션에 AnimatedBuilder를 사용했는가?
□ 프로파일 모드에서 테스트했는가?
□ RepaintBoundary를 적절히 사용했는가?
정리
const생성자로 위젯 재생성을 방지하세요 (가장 쉽고 효과적)- 변하는 부분과 변하지 않는 부분을 별도 위젯으로 분리하세요
ListView.builder()로 보이는 아이템만 생성하세요AnimatedBuilder의child파라미터로 정적 부분을 캐싱하세요- 성능 테스트는 반드시 프로파일 모드 에서 하세요
- Flutter DevTools의 Performance 탭으로 프레임 드롭을 분석하세요
댓글 로딩 중...