• 30.10.2026 03:10:43
  • İbrahim Gökyar

RenderObject seviyesinde repaint ve heap churn azaltarak flutter mobil uygulama geliştirme performansını yükseltin; somut kod, DevTools profili ve state management köprü örnekleriyle uygulamalı rehber.

Flutter RenderObject ile repaint azaltma ve state management stratejileri

Neden RenderObject tabanlı optimizasyon - kısa teknik gerekçe

RenderObject katmanına inmek, widget rebuild-okunu (Dart heap ve build-phase CPU) azaltır çünkü UI ağaçtaki küçük değişiklikleri sadece paint/semantics/baseline seviyesine indirgeme imkanı verir. Teknik gerekçe: widget.build her çağrıldığında yeni Widget/Element/RenderObject ataması veya alan güncellemesi yapılır; RenderObject seviyesinde markNeedsPaint/markNeedsLayout çağrılarıyla yalnızca paint veya layout aşaması tetiklenir. Ölçüm yöntemi: önce "flutter run --profile -d " ile uygulamayı profil modunda çalıştırın, Dart DevTools -> Performance -> Record 120 frames ile baseline alın; sonra aynı senaryoyu RenderObject cache/picture reuse ile tekrar çalıştırıp p95 frame build+raster sürelerini karşılaştırın. Spesifik araçlar ve adımlar: 1) flutter run --profile -d , 2) açın Dart DevTools (http://127.0.0.1:9100), 3) Performance -> Record -> reproduce -> Stop -> Export JSON. Karşılaştırma mantığı: aynı senaryoda 120 frame toplayın, DevTools export içindeki "frame_number, UI thread, GPU thread" metriklerini p50/p95/p99 olarak kıyaslayın.

Uygulamalı örnek: Picture cache kullanan RenderBox (kod)

Aşağıdaki örnek, sıkça yeniden çizilen ancak nadiren değişen vektörel grafikleri ui.Picture ile kaydedip yeniden kullanır; bu sayede Skia yeniden-record maliyeti ve Dart tarafında gereksiz alokasyonlar azalır. Kullanım: widget ağacında bu RenderBox'i sarın veya LeafRenderObjectWidget olarak kullanın. Profil adımı: önce DevTools Performance ile 60 frame kaydedin, sonra bu değişikliği uygulayıp tekrar kaydedin; export edilen JSON'da "Rasterizer" ve "Skia" sürelerindeki düşüşü ölçün.

import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class CachedShape extends LeafRenderObjectWidget {
  final Color color;
  CachedShape({required this.color});

  @override
  _RenderCachedShape createRenderObject(BuildContext context) => _RenderCachedShape(color);

  @override
  void updateRenderObject(BuildContext context, _RenderCachedShape renderObject) {
    renderObject.color = color;
  }
}

class _RenderCachedShape extends RenderBox {
  Color _color;
  ui.Picture? _picture;

  _RenderCachedShape(this._color);

  set color(Color c) {
    if (c == _color) return;
    _color = c;
    _picture?.dispose();
    _picture = null; // force re-record only when color actually changed
    markNeedsPaint();
  }

  @override
  void performLayout() {
    size = constraints.constrain(Size(200, 200));
  }

  void _ensurePicture() {
    if (_picture != null) return;
    final recorder = ui.PictureRecorder();
    final canvas = Canvas(recorder);
    final paint = Paint()..color = _color;
    // expensive vector drawing once
    canvas.drawRRect(RRect.fromRectXY(Offset.zero & size, 12, 12), paint);
    // add more complex paths, shadows, gradients here
    _picture = recorder.endRecording();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    _ensurePicture();
    context.canvas.save();
    context.canvas.translate(offset.dx, offset.dy);
    context.canvas.drawPicture(_picture!);
    context.canvas.restore();
  }

  @override
  void dispose() {
    _picture?.dispose();
    super.dispose();
  }
}
Teknik not: ui.Picture komutları native Skia tarafında tutulur; yeniden record yerine drawPicture çağırmak GPU tarafında daha ucuz olabilir ve Dart heap'te geçici Path/Canvas nesneleri oluşturmaktan kaçınır. Profil önerisi: DevTools Memory -> Take Heap Snapshot -> filter by Class (ui.Picture, Path) ile alokasyon farkını görün.

State management ile repaint hedefleme: ValueListenable, Selector ve RenderObject köprüsü

State management katmanını doğrudan RenderObject ile bağlamak, widget rebuild'lerini minimize eder. Örnek yaklaşım: global state provider (Provider veya Riverpod) yerine, sık değişen küçük alanlar için ValueNotifier kullanın ve RenderObject içinde listener ekleyin; böylece sadece paint tetiklenir, build aşaması atlanır. Aşağıda ValueListenable'ı RenderObject'e bağlayan güvenli bir örnek (attach/detach yönetimi) var. Profil yöntemi: DevTools -> Performance -> Record, ayrıca DevTools -> Inspect -> Widget rebuild count özelliği ile rebuild sayısını karşılaştırın.

class NotifierRenderWidget extends LeafRenderObjectWidget {
  final ValueListenable colorListenable;
  NotifierRenderWidget({required this.colorListenable});

  @override
  _NotifierRenderObject createRenderObject(BuildContext context) => _NotifierRenderObject(colorListenable);

  @override
  void updateRenderObject(BuildContext context, _NotifierRenderObject renderObject) {
    renderObject.colorListenable = colorListenable;
  }
}

class _NotifierRenderObject extends RenderBox {
  ValueListenable? _listenable;

  _NotifierRenderObject(this._listenable);

  void _onChange() {
    // sadece paint gerekiyorsa
    markNeedsPaint();
  }

  set colorListenable(ValueListenable? n) {
    if (identical(n, _listenable)) return;
    _detach();
    _listenable = n;
    _attach();
    markNeedsPaint();
  }

  void _attach() { _listenable?.addListener(_onChange); }
  void _detach() { _listenable?.removeListener(_onChange); }

  @override
  void attach(PipelineOwner owner) { super.attach(owner); _attach(); }
  @override
  void detach() { _detach(); super.detach(); }

  @override
  void performLayout() { size = constraints.constrain(const Size(100, 100)); }
  @override
  void paint(PaintingContext context, Offset offset) {
    final color = _listenable?.value ?? const Color(0xFF000000);
    context.canvas.drawRect(offset & size, Paint()..color = color);
  }
}
Best practice: Provider paketinde Selector kullanarak widget rebuild'leri azaltabilirsiniz; ama daha ince kontrol istiyorsanız yukarıdaki gibi doğrudan RenderObject'e listen yapın. Neden: widget rebuild'leri Element and Widget allocation'lara yol açar, bunlar Dart heap'te kısa ömürlü nesne üretir ve GC baskısını arttırır - RenderObject listener ile sadece paint çalışır.

Heap churn azaltma: ByteBuffer pool, const ve allocation tracing

Heap churn'u azaltmak için: 1) kısa ömürlü list/string oluşturmayı azaltın (const, List.filled, reuse), 2) büyük byte buffer'ları pool'layın (örneğin resim işleme pipeline'ında), 3) dart:developer Timeline event'leri ile kritik yolları işaretleyin. Profil aracı/akış: flutter run --profile -d ile başlayın, DevTools Memory -> Start allocation recording -> reproduce senaryo -> Take snapshot. Snapshot'ta en çok instance oluşturan sınıfları (List, Uint8List, String) bulun. Aşağıdaki örnek ByteBuffer pool'u veriyor; profil sonrası ölçüm: snapshot1 (baseline) ve snapshot2 (pool aktif) karşılaştırmasında "Instances" ve "Shallow size" kolonlarındaki düşüşü kontrol edin.

class BytePool {
  final int bufferSize;
  final List _pool = [];

  BytePool(this.bufferSize);

  Uint8List rent() {
    if (_pool.isNotEmpty) return _pool.removeLast();
    return Uint8List(bufferSize); // allocate only when pool empty
  }

  void giveBack(Uint8List buf) {
    if (buf.lengthInBytes != bufferSize) return; // sanity
    _pool.add(buf);
  }
}

// kullanım örneği
final pool = BytePool(1024 * 64); // 64KB
Uint8List buf = pool.rent();
// ... dolu/okuma işlemleri
pool.giveBack(buf);
Ek tavsiye: büyük resim decode/resize işlerini isolate'a taşımak için compute veya package:isolate kullanın ve Dart DevTools -> Memory snapshot ile izleyin. Not: dart:ui tipleri çoğunlukla native bellekte yer tutar; ui.Picture reuse etmek Dart heap'te Path/Canvas geçicilerini azaltır ancak yine native bellekte kaynak tutar - bu yüzden picture.dispose() yönetimini unutmayın. Allocation tracing için ayrıca "DevTools -> Memory -> Allocation profile -> Record (short)" kullanın ve en çok hangi fonksiyonların alokasyon yaptığını görün.

Profil stratejisi: ölçümler, metrikler ve karşılaştırma adımları

Somut profil planı ve metrikler: 1) Hazırlık: uygulamayı "flutter run --profile -d " ile başlatın. 2) Baseline kaydı: DevTools Performance -> Record -> reproduce scenario (60-120 frames) -> Stop -> Export JSON. 3) Önemli metrikler: UI build time p95, Rasterizer time p95, Frame Skipped count, Dart Heap allocated bytes/sec. 4) Değişiklik: RenderObject cache veya BytePool uygulayın. 5) Tekrar kayıt: aynı senaryo ile tekrar Record. 6) Karşılaştırma: export edilen JSON içindeki frame listesinde buildTime ve rasterTime alanlarını p50/p95/p99 için hesaplayın; örnek olarak jq ile aggregate almak: "cat perf.json | jq '[.frames[].build_duration_micros] | map(. / 1000) | {p50: .[ (length*0.5)|floor ], p95: .[ (length*0.95)|floor ] }'". Bu şekilde somut sayısal fark elde eder ve optimizasyonun etkisini kanıtlayabilirsiniz.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter state management kullanırken rebuild'leri nasıl minimize ederim?

Sık değişen küçük state'ler için Provider/Riverpod üstünde Selector veya ValueListenable/ValueNotifier kullanın; daha da ince kontrol için doğrudan RenderObject'e listener ekleyin (attach/detach yönetimi). Ölçüm: DevTools -> Performance ile widget rebuild count ve build-time p95 karşılaştırması yapın.

Flutter mobil uygulama geliştirme sırasında heap churn'ı nasıl tespit ederim?

flutter run --profile -d ile uygulamayı profil modunda çalıştırın, Dart DevTools -> Memory -> Start allocation profile -> reproduce -> Take snapshot. Snapshot'ta en çok instance oluşturan sınıfları (List, String, Uint8List) ve "Retained size" farklarını inceleyin; ardından ByteBuffer pool veya const kullanımı ile tekrar alınan snapshot'ları karşılaştırın.

Dart programlama eğitimi alan bir ekip için hangi pratikleri önerirsiniz?

Ekiplere öneri: 1) performans odaklı kod incelemelerinde widget rebuild ve heap allocation inceleyin, 2) DevTools kullanımını öğretin (Performance, Memory, CPU Profiler), 3) izolasyon için compute kullanımı ve buffer pooling stratejilerini egzersiz haline getirin. Somut başlangıç görevi: uygulamada 60 frame profil alıp p95 build/raster süresini azaltacak bir RenderObject cache uygulayın ve farkı raporlayın.

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