Çarpıntı Performansı: Pervane, İşleme Boru Hattı ve Yankların Ortadan Kaldırılması
Animasyondaki o sinir bozucu kekemelikler, kaydırmadaki eksik kareler - ve mobil kullanıcı deneyiminin bir numaralı düşmanı. Flutter'ın tarihsel nedeni en yaygın olanı şuydu gölgelendirici derlemesi: GPU ilk kez Grafiksel bir efekt oluşturmak zorunda kaldığında, gölgelendirici JIT'i derleyerek donma görülebilir. İle Pervane — yeni Flutter oluşturucu, Flutter 3.10'dan bu yana iOS'ta ve Flutter 3.24'ten beri Android 10+'da varsayılan olarak kullanılıyor - bu sorun kökten çözüldü.
Ancak Çark tüm performans sorunlarını ortadan kaldırmaz. Yararsız rekonstrüksiyonlar Widget ağacının özellikleri, UI iş parçacığında yoğun işlemler, optimize edilmemiş görüntüler: bu sorunlar oluşturucudan bağımsız olarak mevcuttur. Bu kılavuz bunu kapsar hem Çark mimarisi hem de tanımlama ve tanımlama için pratik araçlar Uygulamanızdaki performans darboğazlarını çözün.
Ne Öğreneceksiniz
- Impeller vs Skia: yeni oluşturucu nasıl çalışıyor ve neden gölgelendirici yanmasını ortadan kaldırıyor
- Flutter işleme hattı: UI iş parçacıkları, raster iş parçacıkları, çerçeve bütçeleri
- Performans Yer Paylaşımı: Grafikleri gerçek zamanlı olarak okuyun
- Flutter DevTools - Yavaş kareleri tanımlamak için zaman çizelgesi profil oluşturucusu
- Yararsız yeniden oluşturmalar: const, RepaintBoundary, bunları azaltmak için selectContext
- Ağır görüntüler: CacheWidth, CacheHeight, PrecacheImage
- Optimize Edilmiş ListView: itemExtent, prototipItem, addRepaintBoundaries
Pervane: Nasıl çalışır ve neden Skia'dan daha iyidir?
Kayak (önceki oluşturucu) derlenmiş OpenGL/Vulkan gölgelendiricileri Çalışma zamanında JIT: Bir efekt ilk kez oluşturulduğunda derlendi gölgelendirici, kullanıcının görebileceği 50-500 ms'lik bir donmaya neden olur. Bu şuydu Impeller Flutter öncesi uygulamaların "ilk kare sarsıntısı" özelliği.
Pervane bunu derleyerek çözer herkes gölgelendiriciler uygulama oluşturma sırasında derleme zamanında. Çalışma zamanında tüm gölgelendiriciler zaten hazır: sıfır JIT derlemesi, sıfır ilk kare sarsıntısı. Pervane ayrıca kullanır daha modern bir işleme hattı (iOS'ta Metal, Android'de Vulkan).
// Verifica se Impeller e attivo nella tua app
// Option 1: controlla in runtime
import 'package:flutter/foundation.dart';
void checkRenderer() {
// Solo in debug/profile mode
if (kDebugMode || kProfileMode) {
debugPrint('Flutter renderer: ${FlutterView.rendererInfo}');
}
}
// Option 2: flutter run con flag esplicito
// flutter run --enable-impeller (forza Impeller)
// flutter run --disable-impeller (forza Skia, per confronto)
// Option 3: controlla nel pubspec o AndroidManifest
// Per iOS: Impeller e ON per default (Flutter 3.10+)
// Per Android: ON per default su Android 10+ (Flutter 3.24+)
// Per Android < API 29: ancora Skia (Impeller richiede Vulkan 1.1)
// android/app/src/main/AndroidManifest.xml
// Per disabilitare Impeller su Android (debugging):
// <meta-data
// android:name="io.flutter.embedding.android.EnableImpeller"
// android:value="false" />
İşleme İşlem Hattı: UI İş Parçacığı ve Raster İş Parçacığı
Flutter, render için iki ana thread kullanır. Nasıl etkileşime girdiklerini anlayın ve performans sorunlarının hatalarını ayıklamak için gereklidir:
// Due thread, un obiettivo: 60fps (16ms per frame) o 120fps (8ms per frame)
// UI Thread (Dart main isolate):
// - Esegue il tuo codice Dart
// - Gestisce gestures, layout, build() dei widget
// - Crea i "layer trees" da inviare al raster thread
// Budget: metà del frame budget (8ms per 60fps)
// Raster Thread (C++ renderer):
// - Prende il layer tree dal UI thread
// - Renderizza su GPU (Impeller o Skia)
// - Invia il frame completato alla GPU
// Budget: l'altra metà del frame budget (8ms per 60fps)
// Se uno dei due thread supera il suo budget:
// - UI thread lento: build() troppo pesante, layout complesso
// - Raster thread lento: shader compilation (Skia), clipping complesso, immagini grandi
// Performance Overlay: abilita in DevTools o nel codice
MaterialApp(
showPerformanceOverlay: true, // Mostra i grafici in app
// ...
)
// I due grafici:
// - Grafico superiore: UI thread (rosso = frame slow)
// - Grafico inferiore: GPU/Raster thread (rosso = frame slow)
// La linea verde = 16ms (60fps budget)
// Ogni barra sopra la linea verde = frame dropped
Flutter DevTools: Yavaş Kareleri Tanımlayın
// Come usare Flutter DevTools per il profiling
// 1. Avvia in profile mode (non debug: ottimizzazioni attive)
// flutter run --profile
// 2. Apri DevTools
// flutter pub global activate devtools
// flutter pub global run devtools
// 3. Tab "Performance" > Timeline
// Cosa cercare nel timeline:
// - Frame droppati: barre rosse/gialle nella overview
// - Clic su un frame lento per vedere breakdown
// - UI thread: quali widget hanno build() lento?
// - Raster thread: quali layer causano overdraw?
// Strumento "Widget Rebuild Stats" in DevTools:
// Mostra quante volte ogni widget e stato ricostruito
// Cerca widget con rebuild count elevato senza motivo
// Identificare rebuild inutili nel codice:
// Aggiungi questo in debug mode:
class DebugBuildCounter extends StatelessWidget {
final Widget child;
const DebugBuildCounter({required this.child, super.key});
@override
Widget build(BuildContext context) {
debugPrint('BUILD: ${child.runtimeType} at ${DateTime.now().millisecondsSinceEpoch}');
return child;
}
}
Gereksiz Yeniden Oluşturmaları Azaltın
// 1. const: il widget piu efficiente possibile
// SBAGLIATO: ricreato ad ogni rebuild del parent
Text('Hello World')
// CORRETTO: const = immutabile, mai ricostruito
const Text('Hello World')
// Usa const il piu possibile:
const SizedBox(height: 16)
const Divider()
const Icon(Icons.star)
// 2. RepaintBoundary: isola porzioni del widget tree
// Senza RepaintBoundary: tutta la pagina viene ridisegnata
// quando l'animazione aggiorna il contatore
class AnimatedCounterPage extends StatefulWidget { ... }
// Con RepaintBoundary: solo il contatore viene ridisegnato
class AnimatedCounterPage extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// Contenuto statico: non viene ridisegnato
const PageHeader(),
const PageContent(),
// Solo questa parte e re-painted ogni frame
RepaintBoundary(
child: AnimatedCounter(),
),
],
);
}
}
// 3. select() con BLoC/Riverpod: rebuild solo per i dati che cambiano
// SBAGLIATO: il widget si ricostruisce per qualsiasi cambiamento dello UserState
BlocBuilder<UserBloc, UserState>(
builder: (context, state) => Text(state.name),
)
// CORRETTO: rebuild solo quando state.name cambia
BlocSelector<UserBloc, UserState, String>(
selector: (state) => state.name,
builder: (context, name) => Text(name),
)
// Con Riverpod:
// SBAGLIATO
ref.watch(userProvider);
// CORRETTO: rebuild solo per la proprieta name
ref.watch(userProvider.select((user) => user.name));
Liste Görünümü optimizasyonu
// ListView.builder: non costruisce mai widget fuori dalla viewport
// Ma puoi ottimizzarlo ulteriormente:
// 1. itemExtent: se tutti gli elementi hanno la stessa altezza
// Flutter salta il layout e usa solo l'aritmetica per posizionare gli elementi
ListView.builder(
itemExtent: 72.0, // Altezza fissa in pixel
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(item: items[index]),
)
// 2. prototypeItem: per altezze uguali ma non conosciute a priori
ListView.builder(
prototypeItem: const ItemTile(item: Item.empty()),
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(item: items[index]),
)
// 3. addRepaintBoundaries: abilita automaticamente (default: true)
// Mette ogni elemento in una RepaintBoundary separata
// Performance win per liste con elementi che si animano
// 4. Immagini nella lista: usa cacheWidth e cacheHeight
// Per evitare di decodificare immagini piu grandi del necessario
ListView.builder(
itemBuilder: (context, index) => Image.network(
items[index].imageUrl,
cacheWidth: 150, // Decodifica a 150px invece che alla dimensione originale
cacheHeight: 150,
),
)
// 5. precacheImage: pre-carica le immagini prima che siano visibili
// Utile per le prime N immagini della lista
@override
void initState() {
super.initState();
// Pre-carica le prime 10 immagini
for (final item in widget.items.take(10)) {
precacheImage(NetworkImage(item.imageUrl), context);
}
}
İyileştirmenin Ölçülmesi
// flutter_frames_package: misura FPS in produzione
// Oppure usa PerformanceMonitor manuale
class FrameMonitor extends StatefulWidget {
final Widget child;
const FrameMonitor({required this.child, super.key});
@override
State<FrameMonitor> createState() => _FrameMonitorState();
}
class _FrameMonitorState extends State<FrameMonitor> {
int _droppedFrames = 0;
DateTime? _lastFrame;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addTimingsCallback(_onTimings);
}
void _onTimings(List<FrameTiming> timings) {
for (final timing in timings) {
final frameDuration = timing.totalSpan.inMilliseconds;
if (frameDuration > 16) {
setState(() => _droppedFrames++);
debugPrint('Dropped frame: ${frameDuration}ms');
}
}
}
@override
void dispose() {
WidgetsBinding.instance.removeTimingsCallback(_onTimings);
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}
Flutter Performans Kontrol Listesi
- Tüm statik widget'lar için const kullanın
- Ağır animasyonları RepaintBoundary'ye sarın
- Ayrıntılı yeniden yapılandırmalar için BLoC/Riverpod'da select() işlevini kullanın
- Öğelerin yüksekliği sabitse itemExtent ile ListView
- Listelerdeki görseller için önbellek genişliğini/önbellek yüksekliğini ayarlayın
- Profil, --profile modunda, hata ayıklama modunda değil
- Yayınlanmadan önce Performans Katmanı ile FPS'yi kontrol edin
Sonuçlar
Impeller, Flutter uygulamalarının en sinir bozucu tarihsel sorununu çözdü: jank derleme gölgelendiricisi. Ancak bir uygulamanın performansı aynı zamanda şunlara da bağlıdır: ne kadar işe yaramaz rekonstrüksiyon yapılıyor, görüntüler nasıl yönetiliyor? ve widget ağacının nasıl yapılandırıldığı. Enstrümanlar — Performans Katmanı, DevTools zaman çizelgesi, FrameTiming geri aramaları — her birini tanımlanabilir hale getirin alt çerçeve hassasiyetinde darboğaz.







