Flutter mobil uygulama geliştirme projelerinde büyük görsellerin ürettiği bellek baskısını ImageCache, doğru decode boyutu, DevTools ölçümü ve yaşam döngüsü yönetimiyle kontrol altına alın.
Flutter Mobil Uygulama Geliştirmede ImageCache ve Bellek Yönetimi
Flutter mobil uygulama geliştirmede ImageCache maliyetini modellemek
Bir feed ekranında bellek hesabını HTTP yanıt boyutundan değil, decode edilmiş piksel tamponundan yapın. PNG, JPEG veya WebP dosyası diskte 120 KB olsa bile Flutter raster aşamasında çoğunlukla RGBA biçiminde tutulur; yaklaşık maliyet genislik x yukseklik x 4 byte değeridir. Örneğin 2048 x 2048 bir görsel yaklaşık 16 MiB'dir. Ekranda aynı anda 12 görselin canlı kalması, Dart heap küçük görünse bile native grafik belleğinde yüzlerce MiB baskı oluşturabilir. Flutter DevTools içindeki Memory görünümünde native bellek eğrisini ve Performance görünümünde Image decode olaylarını, gerçek cihazda hızlı liste kaydırırken birlikte inceleyin.
flutter eğitimi veya flutter kursu içeriğinde sık atlanan ayrıntı şudur: ImageCache yalnızca URL sayısına göre değil, ImageProvider'ın ürettiği anahtara göre çalışır. İmzalı URL'nin her istekte değişen query parametresi, aynı fiziksel görsel için farklı NetworkImage anahtarları üretir. Bu durumda cache hit oranı düşer ve eski anahtarlar maximumSizeBytes sınırına kadar tutulur. İstek URL'sini doğrudan widget durumunda üretmek yerine, CDN tarafında sabit bir içerik kimliği ve sürüm parametresi kullanın; örneğin /avatars/42.webp?v=sha256-kisa-ozet biçimi, hem HTTP cache hem ImageCache için tekrar kullanılabilir bir anahtar verir.
import 'dart:ui' show PlatformDispatcher;
int decodedBytes(int width, int height) => width * height * 4;
void logImageBudget() {
final dpr = PlatformDispatcher.instance.views.first.devicePixelRatio;
final targetWidth = (160 * dpr).round();
final bytes = decodedBytes(targetWidth, targetWidth);
// 3x ekranda 160 logical px: 480 x 480 x 4 = 921600 byte
print('target=$targetWidth px, decoded=$bytes byte');
}cross platform mobil uygulama geliştirme sırasında yalnızca mantıksal piksel ile karar vermek hatalıdır. 160 logical pixel genişliğindeki kart, 3.0 DPR cihazda yaklaşık 480 fiziksel piksel decode edilmelidir; 160 piksele decode etmek bulanıklık, orijinal 3000 piksele decode etmek gereksiz native bellek üretir. Tasarım sisteminizde kart genişliği, cihaz DPR'si ve beklenen görünür öğe sayısından bir görüntü bütçesi türetin.
Decode boyutunu cacheWidth ile sınırlama ve gerçek cihazda doğrulama
Image.network için cacheWidth veya ResizeImage kullanmak, codec'in hedef çözünürlüğe yakın decode etmesini ister. Bu, görseli önce tam çözünürlükte decode edip Flutter tarafında küçültmekten farklıdır: büyük ara RGBA tamponunun oluşmasını engeller. Hedef değeri layout genişliği x devicePixelRatio olarak hesaplayın. Sadece cacheWidth vermek en-boy oranını korur; hem width hem height veriyorsanız yanlış oranlı bir hedefin decoder davranışını ve görsel kalitesini test edin.
class FeedThumbnail extends StatelessWidget {
const FeedThumbnail({super.key, required this.url});
final String url;
@override
Widget build(BuildContext context) {
final dpr = MediaQuery.devicePixelRatioOf(context);
final logicalWidth = (MediaQuery.sizeOf(context).width - 48) / 2;
final decodeWidth = (logicalWidth * dpr).round();
return AspectRatio(
aspectRatio: 1,
child: Image(
image: ResizeImage(
NetworkImage(url),
width: decodeWidth,
),
fit: BoxFit.cover,
filterQuality: FilterQuality.medium,
errorBuilder: (_, __, ___) => const ColoredBox(color: Color(0xffdddddd)),
),
);
}
}Önce-sonra karşılaştırmasını aynı cihaz, aynı veri kümesi ve aynı kaydırma senaryosuyla yapın. İlk ölçümde orijinal 2400 px görselleri cacheWidth olmadan yükleyin; ikinci ölçümde yukarıdaki hedef boyutu uygulayın. DevTools Memory ekranında kaydırma sonundaki native bellek tepesini, Performance ekranında Image decode sürelerini kaydedin. Örnek bir kabul eşiği tanımlayın: 20 kartlık iki sütunlu feed için kaydırma sonu native bellek tepesinin 180 MiB altında kalması ve 95. yüzdelik raster süresinin cihazın yenileme periyodunun altında olması. Sayıları CI cihazı ve görsel seti değiştikçe yeniden baz alın; mutlak değerleri farklı cihazlara taşımayın.
cached_network_image gibi paketler encoded dosyayı disk cache'e koyabilir, ancak Flutter'ın decode edilmiş ImageCache maliyetini ortadan kaldırmaz. Paket kullanıyor olsanız da memCacheWidth ve memCacheHeight seçeneklerini kartın gerçek fiziksel boyutuna bağlayın. Özellikle hero geçişinde küçük decode edilmiş thumbnail'i tam ekran hedefte yeniden kullanmak bulanıklık üretebilir; thumbnail ve detay ekranı için bilinçli olarak farklı ImageProvider anahtarları ve farklı decode boyutları kullanın.
ImageCache sınırları ve bellek baskısı altında temizleme
Uygulama başlangıcında ImageCache için hem adet hem byte sınırı koyun. Varsayılanlar uygulamanızın görsel boyutlarına uygun olmayabilir; 100 küçük avatar için makul olan adet tabanlı limit, 20 adet 4K fotoğraf için tehlikelidir. maximumSizeBytes değerini hedef cihaz sınıfı, görünür ekran sayısı ve decode bütçenizle belirleyin. Örneğin 80 MiB sınırı, dört adet 16 MiB tam ekran fotoğrafın cache'te kalmasına izin vermez; bu nedenle galerinizde yeniden decode maliyeti ile bellek güvenliği arasındaki tercihi ölçerek yaparsınız.
import 'package:flutter/widgets.dart';
void main() {
final cache = PaintingBinding.instance.imageCache;
cache.maximumSize = 120;
cache.maximumSizeBytes = 80 << 20; // 80 MiB
runApp(const App());
}
class MemoryPressureHandler with WidgetsBindingObserver {
void start() => WidgetsBinding.instance.addObserver(this);
@override
void didHaveMemoryPressure() {
final cache = PaintingBinding.instance.imageCache;
cache.clear();
cache.clearLiveImages();
}
void dispose() => WidgetsBinding.instance.removeObserver(this);
}Buradaki edge case, clear ile clearLiveImages farkıdır. clear(), bekleyen ve dinleyicisi olmayan keep-alive girdilerini kaldırır; aktif ImageStream dinleyicisine sahip canlı görseller kalabilir. İşletim sisteminden bellek baskısı geldiğinde ekranı küçültmek veya uygulamanın sonlandırılma riskini azaltmak için clearLiveImages da gerekebilir. Buna karşılık bunu her route değişiminde çağırmak kötü bir stratejidir: geri dönülen ekranda tekrar ağ isteği ve decode tetiklenir. Sadece didHaveMemoryPressure gibi gerçek baskı sinyallerinde veya kullanıcı oturumu değiştiğinde, kullanıcıya ait görselleri ayıran bir politika ile çağırın.
Flutter state management katmanında ImageStream sızıntılarını önleme
flutter state management katmanında UI nesnelerini saklamayın. Riverpod, Bloc veya Provider state'i URL, içerik sürümü ve yükleme durumunu tutabilir; ImageStream, ImageInfo ya da ui.Image tutmamalıdır. ImageStream'e eklenen listener kaldırılmazsa ImageCache ilgili girdiyi live kabul etmeye devam eder. Bu sızıntı, sayfadan çıkılsa bile görsellerin native bellekte kalmasına neden olabilir ve Dart heap snapshot'ında kolay görünmeyebilir.
class PaletteProbeState extends State<PaletteProbe> {
ImageStream? _stream;
ImageStreamListener? _listener;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final provider = NetworkImage(widget.url);
_stream = provider.resolve(createLocalImageConfiguration(context));
_listener = ImageStreamListener((ImageInfo info, bool syncCall) {
// Sadece gerekli renk bilgisini state katmanina yazin.
// ImageInfo veya ui.Image referansini provider state'inde saklamayin.
});
_stream!.addListener(_listener!);
}
@override
void dispose() {
_stream?.removeListener(_listener!);
super.dispose();
}
@override
Widget build(BuildContext context) => const SizedBox.shrink();
}dart programlama eğitimi kapsamında yaşam döngüsü tartışılırken bu örnek ayrıca önemlidir: didChangeDependencies birden fazla kez çağrılabilir. Yukarıdaki örnek üretim kodunda URL veya bağımlılık değişmeden önce eski listener'ı kaldıracak bir yardımcı metoda ayrılmalıdır. Aksi halde aynı State nesnesi üzerinde her bağımlılık değişiminde yeni listener eklenir. Riverpod kullanıyorsanız görsel meta verisi sağlayıcısını autoDispose yapın, fakat ağ görselinin kendisini provider state'ine taşımayın; Image widget'ın ImageProvider çözümlemesi framework cache'iyle çalışmalıdır.
Profil tabanlı regresyon testi ile görsel bellek bütçesi
Manuel DevTools oturumunu otomatikleştirilmiş kaydırma senaryosuyla tamamlayın. integration_test içinde sabit 40 URL, sabit viewport ve tekrar edilebilir drag mesafesi kullanın; profile modunda Timeline verisi derleyici optimizasyonları ve raster iş parçacığı davranışı açısından debug moddan daha temsil edicidir. Komutu gerçek cihazda çalıştırın: flutter drive --driver=test_driver/integration_test.dart --target=integration_test/feed_scroll_test.dart --profile. Test çıktısına commit SHA, cihaz modeli, DPR, görsel URL seti ve ImageCache limitlerini ekleyin; aksi halde iki ölçüm karşılaştırılamaz.
import 'dart:developer' as developer;
import 'package:flutter/widgets.dart';
void installFrameProbe() {
final rasterMicros = <int>[];
WidgetsBinding.instance.addTimingsCallback((timings) {
for (final frame in timings) {
rasterMicros.add(frame.rasterDuration.inMicroseconds);
}
if (rasterMicros.length == 120) {
rasterMicros.sort();
final p95 = rasterMicros[(rasterMicros.length * 0.95).floor() - 1];
developer.log('feed raster p95=${p95}us', name: 'image-benchmark');
rasterMicros.clear();
}
});
}Bu probe yalnızca raster süresini verir; native bellek tepesini DevTools Memory kaydıyla eşleştirin. Değişiklikten önce ve sonra aynı 120 frame penceresinde p95 değerini, cache.currentSizeBytes değerini ve kaydırma sonu bellek tepesini raporlayın. Örneğin decode boyutu değişikliği raster p95'i düşürürken aşırı küçük cache limiti nedeniyle tekrar decode sayısını artırabilir. Bu yüzden tek metriği iyileştirmek yerine, belirlediğiniz bellek tavanı altında decode olayları ve p95 raster süresi birlikte kabul kriteri olmalıdır.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter mobil uygulama geliştirmede ImageCache boyutu nasıl belirlenir?
Önce görünür ve yakın önbelleğe alınan kart sayısını belirleyin, sonra her kartın decode boyutunu genislik x yukseklik x 4 ile hesaplayın. DevTools Memory ile aynı feed kaydırmasındaki native bellek tepesini ölçün. PaintingBinding.instance.imageCache.maximumSizeBytes değerini bu ölçümden türetilen bütçeye koyun; 80 MiB gibi bir başlangıç değeri ancak hedef cihazlarda doğrulandıktan sonra kalıcı olmalıdır.
flutter state management kullanırken görsel bellek sızıntısı nasıl bulunur?
DevTools Memory kaydı açıkken görsel ekranına girip çıkın ve native bellek eğrisinin her turda kalıcı yükselip yükselmediğini kontrol edin. ImageStream.addListener kullanılan kodlarda removeListener çağrısını dispose içinde doğrulayın. Bloc, Riverpod veya Provider state'inde ImageInfo, ui.Image ve ImageStream yerine yalnızca URL, içerik sürümü ve meta veri saklayın.
flutter kursu projelerinde Image.network için cacheWidth ne olmalı?
cacheWidth değerini sabit 200 yapmak yerine render edilen logical genişliği MediaQuery.devicePixelRatioOf(context) ile çarpın. Örneğin 156 logical pixel genişliğinde, DPR 2.75 cihazda yaklaşık 429 pixel kullanın. Bu değeri gerçek kart genişliğinden türetmek, hem bulanık thumbnail riskini hem gereksiz büyük decode tamponlarını azaltır.
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.

