• 29.10.2026 23:15:00
  • İbrahim Gökyar

Bu makale, Flutter state management kaynaklı gereksiz rebuild'leri azaltarak ve Dart heap churn'u düşürerek gerçek performans kazanımları elde etme adımlarını anlatır. Kod, DevTools profili ve CI entegrasyonu içerir.

Flutter mobil uygulama geliştirme: selective rebuild ve heap churn optimizasyonu

Neden selective rebuild? Flutter state management maliyet analizi

Selective rebuild uygulamak gerektiğini söylemek yeterli değil: mekanizma şu. setState veya ChangeNotifier.notifyListeners() çağrısı Element.markNeedsBuild() tetikler; bu, widget ağacındaki ilgili Element'in build metodunu yeniden çalıştırır ve eğer subtree büyükse layout/paint maliyeti ve geçici nesne tahsisi doğurur. Bunun somut olarak ölçülmesi için Flutter DevTools -> Performance -> CPU profiler kullanın: önce "flutter run --profile -d " ile senaryoyu çalıştırıp 10s timeline kaydedin, sonra kod değişikliğinizi uygulayıp aynı senaryoyu tekrar kaydedin; karşılaştırma için 'Average Frame Build' ve 'GC time' metriklerini kullanın. Ayrıca geliştirme aşamasında debugPrintRebuildDirtyWidgets = true kullanarak hangi widget'ların sık rebuild olduğunu hızlıca görebilirsiniz:

import 'package:flutter/widgets.dart';

void main() {
  debugPrintRebuildDirtyWidgets = true; // sadece debugda
  runApp(MyApp());
}

Pratik ipucu: Provider paketinde Provider.of(context) doğrudan kullanmak tüm tüketici widget'ı tekrar oluşturur. Bunun yerine provider package'tan Selector veya context.select kullanın; bu, yalnızca seçilen alan değiştiyse rebuild tetikler. Neden işe yarıyor? Çünkü Selector öğeyi kendi içinde küçük bir ChangeNotifier gibi davranacak şekilde sarmalar ve 'shouldRebuild' karşılaştırmasıyla gereksiz call'ları eler. Bu bölümde kullandığımız profil aracına dair somut adım: DevTools CPU profiler'da 'Record CPU Profile' çalıştırın, sonra 3-5 tekrarlı senaryo (ör. listeyi kaydırma) yapın ve 'Profile' sonucu save edin; değişiklikten sonra tekrar alın ve 'Total self time' farkına bakın.

Profiling adımları: CPU, memory ve frame-level ölçümler

Profesyonel performans çalışmasında kesin komutlar ve araçlar gereklidir. Önerilen yöntem: 1) Test cihazını USB ile bağlayın, 2) flutter run --profile -d , 3) DevTools'u açın (flutter pub global activate devtools; flutter pub global run devtools veya IDE'den), 4) Performance -> 'Start recording' ile 10-20s senaryo kaydedin. Ölçümler: 'Rasterizer', 'Engine', 'UI' thread zamanlarını karşılaştırın ve 'Average Frame Build' ile 'Average Frame Rasterizer' değerlerini kullanın. Bellek için DevTools -> Memory -> Allocation profile ile 'Allocation rate' ve 'Live instances' istatistiklerini alın. Bir önce/sonra karşılaştırması için aynı cihaz/aynı senaryo/aynı profil modu (profile) kullanın; farklılıkları yüzde olarak hesaplayın (ör. GC time % = (baseline.GC - optimized.GC)/baseline.GC * 100).

Ek somut araçlar: Android'de systrace veya Perfetto ile platform tarafı gecikmeleri görün, iOS'ta Instruments'in Time Profiler ve Allocations sekmelerini kullanın. Örnek komut (Perfetto trace başlatma):

adb shell perfetto -c /data/misc/perfetto/traces/configs/scheduled.config -o /data/misc/perfetto-traces/trace.pb
Bu trace'i Perfetto UI'da açıp GPU/CPU zamanlamalarını UI thread ile çapraz karşılaştırın.

Uygulamalı: Provider/Selector ile selective rebuild örneği (kod ve ölçüm)

Aşağıdaki örnek, bir liste öğesinin sadece kendi verisi değişince yeniden oluşturulmasını sağlayan Provider+Selector kullanımını gösterir. Kodda ayrıca build sayaçlı küçük bir araç var; bunu debugPrintRebuildDirtyWidgets ile birlikte kullanarak azaltma etkisini ölçün. Önce baseline: tüm öğelerin setState ile güncellendiği versiyonu profile ederek 'Average Frame Build' değerini alın. Sonra Selector'a geçin ve aynı senaryoyu tekrar çalıştırın.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ItemModel extends ChangeNotifier {
  final int id;
  String _text;
  ItemModel(this.id, this._text);
  String get text => _text;
  set text(String v) {
    if (v != _text) {
      _text = v;
      notifyListeners();
    }
  }
}

class ListItem extends StatelessWidget {
  final int id;
  const ListItem({Key? key, required this.id}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('Build ListItem $id'); // build sayacı
    return Selector(
      selector: (_, model) => model.text,
      shouldRebuild: (prev, next) => prev != next,
      builder: (_, text, __) => Text('#$id: $text'),
    );
  }
}

// Provider ağacını kurup test edin: her öğe için ayrı ItemModel yaratın ve sadece tek öğeyi güncelleyin.

Bu örnekte ölçekleme için: 1) 1000 öğeli bir liste oluşturun, 2) rastgele 100 öğeyi sırayla güncelleyen bir benchmark senaryosu yazın, 3) her iki durumda (setState vs Selector) DevTools'ta CPU timeline kaydı alın. Beklenen mekanizma: Selector yalnızca ilgili tüketici widget'ın rebuild edilmesini sağlar çünkü notifyListeners() sadece o ItemModel örneğini tetikler; setState ile tüm liste veya parent widget'lar yeniden oluşturulursa build maliyeti ve geçici nesne üretimi artar.

Dart heap churn azaltma: allocate-free rendering ve object pooling

Heap churn'un temel sebebi short-lived object allocation; GC sıklaştıkça frame jank artar. Mekanizma: her frame'de yeni nesne tahsisi yapıldığında gen0 hızla dolar, GC çalışır, main thread durur; bunun göstergesi DevTools Memory -> Allocation Profile'da 'Allocated objects/sec' metrik artışıdır. Aşağıda CustomPainter içinde Paint ve Path nesnelerini yeniden kullanarak allocation azaltan örnek var. Ölçüm olarak DevTools'ta 'Allocation rate' before/after karşılaştırmasını kullanın.

class ReusePainter extends CustomPainter {
  final Paint _paint = Paint()..color = Color(0xFF00FF00);
  final Path _path = Path();

  @override
  void paint(Canvas canvas, Size size) {
    _path.reset(); // reuse path, avoid new Path() per frame
    _path.addRect(Rect.fromLTWH(0, 0, size.width, size.height));
    canvas.drawPath(_path, _paint);
  }

  @override
  bool shouldRepaint(covariant ReusePainter oldDelegate) => false; // önemli: gereksiz paint'i engelle
}

Ek uygulamalar: const widget'lar kullanılacak yerde const anahtarını kullanın (ör. const Text('...')), List.generate yerine List.filled + mutate pattern'i düşünün; byte buffer'lar için tek seferlik Uint8List allocate edip yeniden kullanın. Ölçümlerde GC süresini azaltmak için DevTools Memory -> 'Take Allocation Profile' ile trace'i alın, 'Objects Allocated' tablosundaki top allocators'a bakın ve hangi fonksiyonların en çok allocate ettiğini bulun. Bu insight'e göre örneğin bir paket (ör. image decode) tarafından üretilen geçici Uint8List'leri pool ile yeniden kullanabilirsiniz.

Edge case'ler, regresyon testleri ve CI profil entegrasyonu

Edge case örneği: Selector kullanırken seçilen alanın immutable olmayan bir referansla (ör. List) dönmesi durumunda her setState'te liste referansı değişirse tekrar rebuild olur. Çözüm: select ile primitive/immutable değer seçin veya deep equality sağlayan küçük wrapper objeleri kullanın. Test olarak CI'da regression yakalamak için integration_test + profile modunu kullanın: örnek komut dizisi (CI agent üzerinde bağlı bir emulator ile) — 1) flutter drive --profile --driver=test_driver/driver.dart --target=integration_test/app_test.dart, 2) DevTools'ta export edilen timeline.json dosyasını saklayın ve baseline ile karşılaştırın. Baseline/PR pipeline'ı için otomatik fark hesaplama: timeline json içindeki 'frame' sürelerini toplayıp ortalamayı alın; eğer ortalama frame süresi > baseline * 1.10 ise pipeline'ı fail edin. Bu, measurable (ölçülebilir) bir regresyon politikasıdır.

CI entegrasyonu için somut araçlar: GitHub Actions runner üzerinde bir Android emulator başlatıp headless test çalıştırın, timeline dosyasını artifact olarak toplayın. Örnek GitHub Action step (özet):

- name: Run perf tests
  run: |
    sdkmanager "system-images;android-30;google_apis;x86"
    echo no | avdmanager create avd -n test -k "system-images;android-30;google_apis;x86"
    emulator -avd test -no-window &
    flutter drive --profile --driver=test_driver/driver.dart --target=integration_test/perf_test.dart --keep-app-running
Bu yaklaşım ile her PR için 'Average Frame Build' ve 'GC time' değerlerini otomatik olarak toplayıp karşılaştırabilirsiniz.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter state management ile selective rebuild nasıl yapılır?

Selective rebuild için Provider paketinde Selector veya context.select kullanın; örneğin Selector(selector: (_, m) => m.field, builder: ...) sadece field değişince rebuild eder. Ölçmek için debugPrintRebuildDirtyWidgets = true ve DevTools CPU profiler ile baseline/sonrası karşılaştırması yapın.

Dart programlama eğitimi alan biri heap churn'u nasıl ölçer ve azaltır?

Ölçüm için Flutter DevTools -> Memory -> Allocation profile çalıştırın; 'Allocated objects/sec' ve top allocators'ı inceleyin. Azaltmak için: const widget kullanımı, CustomPainter içinde yeniden kullanılan Paint/Path, object pooling uygulaması ve büyük veri için reuse edilen Uint8List örneklerini kullanın. Değişiklikleri 'flutter run --profile' ile test edip DevTools'ta karşılaştırın.

Cross platform mobil uygulama geliştirme bağlamında CI'de performans regresyonu nasıl yakalanır?

CI'de emulator/simulator üzerinde integration_test veya flutter drive --profile çalıştırıp timeline/trace JSON dosyalarını artifact olarak kaydedin. Pipeline'da ortalama frame süresi ve GC time için eşik belirleyin (ör. +10% üstünde fail) ve her PR için otomatik rapor üretin.

AI / LLM Discovery

Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt

📚 Bu konuyu Opendart Akademi'de öğrenin: Flutter eğitimi · Flutter eğitimi hakkında · YouTube video dersleri · Abone ol