Files
imajviewer/docs/rapor-kod-inceleme-performans.md
Alhan 72c35a8a26
Some checks failed
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
fix: image_canvas correctness + RAM fixes (v1.3.0)
- _imageSize reset on image switch (wrong swap threshold on nav)
- downsample caps both dims (tall images no longer full-res decode)
- single _readImageSize instead of leaking loadingBuilder listeners
- evict old downsample provider on full-res swap
- remove imageCache.clear() that dropped live image (gallery flash)
2026-08-14 21:54:34 +03:00

6.2 KiB
Raw Permalink Blame History

Kod İnceleme + Performans Analizi

Tarih: 2026-08-14 Kapsam: Tüm lib/ (13 dosya, 1799 satır) Bağlam: Downsampling + full-res swap + RAM leak fix (v1.2.0) sonrası genel inceleme


1. Tespit Edilen Hatalar

HATA-1 (Önemli): _imageSize resim değişiminde sıfırlanmıyor

lib/widgets/image_canvas.dart

_imageSize, loadingBuilder içinde _imageSize.isEmpty koşuluyla yalnızca bir kez dolduruluyor. Ama _resetView() ve didUpdateWidget() bu alanı sıfırlamıyor.

Sonuç: ikinci resme geçince _imageSize eski resmin boyutunu tutar, yeni resmin boyutu hiç okunmaz. _checkFullResSwap() eski boyutla stretch hesabı yapar → swap yanlış zamanda tetiklenir (ya hiç, ya da gereksiz erken).

Örnek: 2000px bir resimden 12000px panoramaya geçince, stretch hesabı hâlâ 2000px üzerinden yapılır; swap ya geç tetiklenir ya da downsample 2048px'te takılı kalır (zoom'da kalite düşer). Ters yönde ise gereksiz erken full-res yükleme olur.

Düzeltme: _resetView() içine _imageSize = Size.zero; ekle.

HATA-2 (Önemli): Downsample yalnızca genişliğe uygulanıyor

_buildImage() içinde ResizeImage.resizeIfNeeded(2048, null, ...) çağrısı cacheHeight = null geçiyor. Bu, yalnızca genişlik kısıtı demek.

Sonuç: Uzun dikey resimler (ör. 800×20000 ekran görüntüsü) genişliği 2048'in altında olduğu için hiç küçültülmez, tam çözünürlükte (20000px) decode edilir. Tek resim yüzlerce MB RAM yer — downsampling'in amacı boşa çıkar. Aynı sorun panoramik 20000×800 resimlerde genişlik kısıtıyla zaten yakalanır, ama dikeyde kaçar.

Düzeltme: ResizeImage.resizeIfNeeded(2048, 2048, ...) (iki boyut da sınırla). Flutter codec aspect oranını koruyarak sığdırır (fit).

HATA-3 (Performans): loadingBuilder içinde listener sızıntısı

loadingBuilder her frame'de çağrılabilir. _imageSize.isEmpty olduğu sürece her çağrıda provider.resolve() + addListener() yapılıyor; listener hiç remove edilmiyor. _imageSize bir kez dolunca duruyor ama dolana kadar (ya da HATA-1 yüzünden hiç dolmazsa) her rebuild'de yeni listener birikir.

Ayrıca resolve() ikinci bir decode stream'i açar — Image widget'ı zaten kendi decode'ini yaparken boyut öğrenmek için ikinci kez çözmek gereksiz iş.

Düzeltme: _imageSize'i loadingBuilder yerine initState/didUpdateWidget'ta tek sefer resolve() ile oku ve ImageStreamListener'ı ImageStream.removeListener ile kaldır. Ya da Image widget'ının ImageInfo'sini imageStream üzerinden tek listener ile dinle.

HATA-4 (Küçük): Full-res swap'ta eski downsample provider evict edilmiyor

_checkFullResSwap() _cachedProvider = _cachedFileImage yapıyor ama eski ResizeImage provider'ı ImageCache'ten evict etmiyor. Downsamlı ui.Image (~16MB) cache'te gereksiz kalır. maximumSizeBytes = 256MB altında LRU temizler ama tamamen gereksiz bir tutma.

HATA-5 (Yorum yanlışlığı): imageCache.clear() canlı görseli de siler

image_manager.dart:73PaintingBinding.instance.imageCache.clear() tüm cache'i boşaltır, ekrandaki mevcut görsel de dahil. Üstündeki yorum "canlı görsel korunur" diyor ama bu doğru değil. clear() o an render edilen image'ı da atar → galeri kurulumunda kısa bir flaş/boşluk riski.

Düzeltme: Yalnızca eski klasöre ait dosyaları evict et (tek tek) ya da bu satırı kaldırıp didUpdateWidget evict mantığına bırak.


2. RAM Modeli (mevcut davranış)

  • Downsample 2048px → ~16MB/resim (RGBA 2048×2048×4)
  • Full-res swap sonrası → 100MB+ (büyük resimlerde)
  • ImageCache: maximumSize = 16 entry, maximumSizeBytes = 256MB

Kullanıcının "50 resim = 5GB" endişesi gerçekleşmez. maximumSizeBytes = 256MB LRU evict ile toplamı ~256MB1.6GB arasında sınırlar:

  • Tüm resimler downsample ise: 16 entry × ~16MB ≈ 256MB (tam limit)
  • Full-res resimler varsa: 23 tane tutulur, gerisi LRU ile atılır

Yani ölçülen "her resim ~100MB, artmıyor" davranışı doğru ve beklenen.

Kalan risk: maximumSize = 16 entry limiti, maximumSizeBytes'tan bağımsız işler. Full-res ağırlıklı kullanımda 16 × 100MB = 1.6GB'a kadar tutulabilir. maximumSizeBytes'ı düşürmek (ör. 128MB) ya da entry limitini küçültmek bu tavanı düşürür.


3. Performans İyileştirme Önerileri

ÖN-1: dir.listSync() → async/isolate

image_manager.dart:43 — klasör listSync() ile senkron taranıyor. Büyük klasörde (10k+ dosya) UI thread bloklanır, pencere donar. dir.list() (async) ya da Isolate.run ile arka planda tarama önerilir. Sonuç notifyListeners ile gelir.

ÖN-2: _printImage tam dosyayı belleğe alıyor

viewer_screen.dart:173File(path).readAsBytes() tüm dosyayı RAM'e yükler. 12000px resimde print sırasında +200MB spike. Nadir işlem ama downsample edilmiş kopya üzerinden print etmek düşünülebilir.

ÖN-3: _buildImage içindeki _imageKey + ResizeImage memo cache

HATA-1 çözülünce _imageSize doğru çalışır; ayrıca _checkFullResSwap'ın setState çağrısı scroll handler içinde — bu zaten _markRenderDirty ile çift rebuild üretebilir. _checkFullResSwap'ın setState yerine _markRenderDirty kullanması yeterli (ValueListenableBuilder zaten tick'i dinliyor).

ÖN-4: Warmup katmanı her açılışta

_startWarmup shader derlemesini her resim açılışında yapıyor; uygulama ömründe bir kez yapılması yeterli olabilir (global static flag). Küçük kazanç.


4. Doğrulama Matrisi

Madde Durum
flutter analyze 0 error (mevcut info/deprecated uyarıları hariç)
Release build
RAM stabilitesi (kullanıcı testi) ✓ "her resim ~100MB, artmıyor"
HATA-1 (_imageSize reset) düzeltildi
HATA-2 (dikey downsample) düzeltildi
HATA-3 (listener sızıntısı) düzeltildi
HATA-4 (eski provider evict) düzeltildi
HATA-5 (clear() canlı görsel) düzeltildi

5. Öncelik Sırası

  1. HATA-1 + HATA-2 — doğruluk + RAM (uzun dikey resimler hâlâ patlayabilir)
  2. HATA-3 — listener sızıntısı (uzun oturumlarda)
  3. ÖN-1 — büyük klasörde UI donması
  4. HATA-4, HATA-5 — kozmetik/ince ayar