• 29.10.2026 05:19:59
  • İbrahim Gökyar

Bu makale, Flutter mobil uygulama geliştirme süreçlerinde görülen Dart heap churn ve image streaming darboğazlarını ölçme ve azaltma yöntemlerini, DevTools profili ve somut kod örnekleriyle anlatır. flutter eğitimi ve dart programlama eğitimi için uygulanabilir rehber.

Flutter mobil uygulama geliştirme: Dart Heap Churn Optimizasyonu

Sorun tespiti: Heap churn ve nasıl ölçülür (DevTools ile)

Heap churn'un kaynağını tespit etmeden optimizasyon yapmak zaman kaybıdır; bunun için DevTools ve VM Service API kullanılmalı. Adım adım: 1) uygulamayı profil modunda başlat: flutter run --profile, 2) DevTools'u aç: flutter pub global run devtools veya IDE içinden Dart DevTools'u başlat, 3) Memory -> Allocation profile bölümünde "Record" basılı tutarak aynı kullanıcı senaryosunu tekrar et. Bu kayıt, saniyedeki allocation count, en büyük tenured veya short-lived nesneler ve hangi fonksiyonların allocate ettiğini (allocation stack) gösterir; bu veriyi JSON olarak export edip önce/sonra karşılaştırması için saklayın.

Ölçüm mantığı: bir senaryoda (ör. galeri liste kaydırma) 30 saniyelik allocation rate'in ortalamasını ve peak heap usage'i alın. Karşılaştırma metrikleri olarak kullanın: allocation/sec, GC sıklığı (minor/major), retained bytes. Örnek komut: adb logcat -s DartVM ile GC mesajlarını izleyin ve DevTools'tan snapshot alıp vmService.getIsolateMemoryUsage() çağrısıyla otomatikleştirilmiş ölçüm yapın.

Image decoding'i Isolate'a taşımak: TransferableTypedData ile somut örnek

Neden: Flutter ana izinde (UI thread) büyük Uint8List'ler decode edilirken sık allocation ve GC tetiklenir; sonucu frame drop ve jank olur. Çözüm, dekodlama/parsing işini Isolate'a verip UI'ya sadece hazır, scaled bitmap göndermek. Aşağıdaki örnek TransferableTypedData kullanarak paylaşılan bellekle veri taşıma gösterir.

import 'dart:typed_data';
import 'dart:isolate';
import 'package:flutter/widgets.dart';

Future decodeOnIsolate(Uint8List bytes, int targetWidth, int targetHeight) async {
  final p = ReceivePort();
  await Isolate.spawn(_decodeEntry, [p.sendPort, TransferableTypedData.fromList([bytes]), targetWidth, targetHeight]);
  final completer = Completer();
  p.listen((msg) async {
    if (msg is TransferableTypedData) {
      final res = msg.materialize().asUint8List();
      // UI izinde Image.memory ile göster veya instantiateImageCodec ile daha fazla işleyin
      completer.complete(Image.memory(res));
      p.close();
    }
  });
  return completer.future;
}

void _decodeEntry(List args) async {
  final SendPort sendPort = args[0];
  final bytes = (args[1] as TransferableTypedData).materialize().asUint8List();
  final targetW = args[2] as int;
  final targetH = args[3] as int;
  // native decode veya package:image ile resize
  // package:image kullanımı örneği:
  // import 'package:image/image.dart' as img;
  // final image = img.decodeImage(bytes);
  // final resized = img.copyResize(image!, width: targetW, height: targetH);
  // final out = img.encodePng(resized);
  // sendPort.send(TransferableTypedData.fromList([Uint8List.fromList(out)]));
}

Uygulamada: flutter pub add image ile package:image ekleyin ve Isolate içinde CPU-bound decode/resize yapın. Neden TransferableTypedData: kopyalamayı azaltır, GC baskısını düşürür; benchmark için DevTools'ta allocation/sec değerini Isolate öncesi ve sonrası karşılaştırın.

Progressive/streaming decode ile bellek sınırlarını koruma

Büyük görselleri tam olarak belleğe almak yerine streaming decode yaparak peak memory'i sınırlayın. Flutter API'si üzerinden instantiateImageCodec ile hedef çözünürlük verip frame-by-frame decode yapabilirsiniz; bu yöntemde toplam retained bytes daha düşüktür.

final codec = await instantiateImageCodec(bytes, targetWidth: 800, targetHeight: 600);
final frame = await codec.getNextFrame();
final image = frame.image; // kullan
// codec.dispose() çağırarak native kaynakları bırakın

Pratik kurallar: 1) liste görünümünde thumbnail için düşük çözünürlük isteyin (targetWidth/targetHeight), 2) uzun süre gösterecekseniz Image.memory yerine Image widget'ına bitmap cache kullanın, 3) paket olarak flutter_image_compress ile sunucuda veya uygulamada (Isolate içinde) webp/avif'e çevirme değerlendirin. Bu değişiklikleri uyguladıktan sonra DevTools'ta Peak Heap ve allocation rate'i ölçün ve % olarak azaltımı raporlayın.

Image cache bütçesi ve eviction stratejileri

Flutter'ın varsayılan image cache'i uygulamaya göre çok büyük veya çok küçük olabilir; somut kontrol için uygulama başında global ayar yapın: PaintingBinding.instance.imageCache.maximumSizeBytes veya maximumSize. Aşağıdaki kod ile 20 MB limit koyabilirsiniz.

void configureImageCache() {
  // 20 MB
  PaintingBinding.instance.imageCache.maximumSizeBytes = 20 * 1024 * 1024;
  // maksimum 100 entry
  PaintingBinding.instance.imageCache.maximumSize = 100;
}

// main() içinde çağırın
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  configureImageCache();
  runApp(MyApp());
}

Monitoring: imageCache içeriğini runtime'ta incelemek için PaintingBinding.instance.imageCache.currentSizeBytes ve currentSize kullanılabilir. Profil sonrası hedef, peak cached bytes < limit olmalı; eğer GC sonrası cache tam doluyorsa eviction sıklığını arttırarak allocation/sec'i düşürebilirsiniz. Neden mekanizma çalışır: cache sınırı native ve Dart heap kullanımını sınırlar, böylece büyük resimler nedeniyle major GC tetiklenmesi azalır.

State management etkisi: rebuild ızgarası ve selective listening

Görsel yükleme/streaming kodu state management ile kötü entegre edilirse sürekli rebuild'ler heap churn'u artırır. Örneğin Riverpod veya Provider kullanıyorsanız seçici dinleme (selector) veya Consumer widget'ı ile sadece ihtiyaç duyulan widget'ı yeniden oluşturun. Örnek olarak Provider ile selector kullanan bir snippet:

// provider paketi kullanılarak selector ile selective rebuild
class ImageMeta { final String url; final int width; ImageMeta(this.url, this.width); }

final metaProvider = Provider>((ref) => []);

// Widget içinde sadece width değiştiğinde rebuild olan kısım
Selector, int>(
  selector: (_, list) => list.length,
  builder: (_, length, __) {
    return Text('Count: $length');
  },
);

Deneysel test: aynı kullanıcı senaryosunu DevTools'ta önce state management olmadan, sonra selective listening ile çalıştırın; metric olarak frame build count, widget rebuild count (DevTools -> Timeline), allocation/sec ve GC frekansını karşılaştırın. Neden: gereksiz rebuild'ler bellek tahsisleri oluşturur (kısa ömürlü widget objeleri), dolayısıyla heap churn artar ve GC tetiklenir.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

flutter eğitimi sırasında Dart heap churn nasıl ölçülür?

DevTools Memory sekmesini kullanarak allocation profile kaydı alın: flutter run --profile ile uygulamayı başlatın, DevTools'ta Memory -> Record allocations ile senaryoyu tekrarlayın, ardından allocation/sec ve retained bytes değerlerini kaydedin. Ek otomasyon için VM Service API'den getIsolateMemoryUsage() sorgusu alın.

flutter kursu alan bir geliştirici image decoding'i ana izinden nasıl güvenli şekilde çıkarır?

Decode/resize işlerini Isolate'a taşıyın; büyük Uint8List'leri TransferableTypedData ile gönderin. Örnek komut: flutter pub add image ve yukarıdaki Isolate örneğini kullanın. Bu, ana izindeki allocation/sec'i düşürür ve jank'i azaltır.

cross platform mobil uygulama geliştirme bağlamında Flutter'da bellek bütçesi nasıl konfigüre edilir?

Uygulama başlangıcında PaintingBinding.instance.imageCache.maximumSizeBytes = X ile image cache limitini belirleyin ve runtime'ta currentSizeBytes ile izleyin. Profil senaryolarında limitleri değiştirip DevTools'ta before/after ölçümü yaparak en uygun değeri belirleyin.

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