Flutter mobil uygulama geliştirmede liste kaydırma takılmalarını, görsel decode maliyetini ve bellek önbelleğini Flutter DevTools ile ölçerek cacheWidth, disk cache ve kontrollü prefetch ile yönetin.
Flutter Mobil Uygulama Geliştirmede Görsel Decode ve Önbellek Bütçesi
Flutter Mobil Uygulama Geliştirmede Görsel Darboğazını Ölçmek
Görsel ağırlıklı bir akışta sorun, ağ isteğinin yavaş olması değil, büyük bir JPEG veya WebP dosyasının UI ve raster iş parçacıklarının kare bütçesi içinde decode edilmesi olabilir. flutter mobil uygulama geliştirme ekiplerinde ilk adım tahmin değil, profile build ile ölçümdür. Uygulamayı fiziksel cihazda flutter run --profile ile başlatın, Flutter DevTools Performance görünümünde hızlı bir liste kaydırması kaydedin ve raster thread üzerindeki ImageDecode, ImageUpload ve uzun frame olaylarını inceleyin. Jank sayısını yalnızca ortalama FPS ile değerlendirmeyin: 3 aynı kaydırma senaryosunda frame zamanlarının P95 değerini karşılaştırın. Örneğin değişiklik öncesi ve sonrası için toplam frame sayısı, 16.7 ms üzerindeki frame sayısı ve raster P95 değerini aynı cihazda kaydedin.
Kare sürelerini uygulama loguna da yazmak, CI üzerinde regresyon eşiği koymayı kolaylaştırır. Aşağıdaki ölçüm sadece profile veya debug ortamında anlamlıdır; release modunda loglama kapatılmalıdır. Burada build ve raster süreleri ayrı tutulur, çünkü gereksiz widget üretimi ile görsel decode farklı darboğazlardır.
import 'dart:ui';
import 'package:flutter/scheduler.dart';
void installFrameProbe() {
SchedulerBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
for (final timing in timings) {
final buildMs = timing.buildDuration.inMicroseconds / 1000;
final rasterMs = timing.rasterDuration.inMicroseconds / 1000;
if (buildMs > 8 || rasterMs > 8) {
debugPrint(
'slow-frame build=${buildMs.toStringAsFixed(1)}ms '
'raster=${rasterMs.toStringAsFixed(1)}ms',
);
}
}
});
}Bir flutter eğitimi veya flutter kursu kapsamında sık görülen hata, debug modundaki sonucu cihazın gerçek davranışı saymaktır. Debug build'de JIT, assertion'lar ve geliştirme servisleri frame sürelerini değiştirir. Ölçüm senaryosunu da sabitleyin: aynı ağ koşulu, aynı hesap verisi, aynı 30 kartlık kaydırma mesafesi ve önceden temizlenmiş uygulama durumu kullanın. Disk önbelleğin etkisini ayırmak için ilk geçişi soğuk, ikinci geçişi sıcak önbellek olarak ayrı kaydedin.
Decode Boyutunu cacheWidth ile Ekran Pikseline İndirmek
Bir kart ekranda 180 logical pixel genişliğindeyken cihaz piksel oranı 3 ise, yaklaşık 540 fiziksel piksel genişliğinde decode hedeflenir. Sunucudan 2400 px genişliğinde bir kaynak indirip tam çözünürlükte decode etmek, ekranda küçültülse bile decoded RGBA bellekte yaklaşık 2400 * yükseklik * 4 byte alan açar. cacheWidth, decoder'a hedef örnekleme boyutunu ileterek bu maliyeti düşürür. Değer logical pixel değil, fiziksel piksel olmalıdır.
Kart genişliği sabit değilse MediaQuery değerini körlemesine kullanmayın. Grid padding'i, iki sütun düzeni ve yatay boşluklar gerçek constraint'i değiştirir. LayoutBuilder ile kartın gerçek genişliğini alıp device pixel ratio ile çarpın; widget'ın kullandığı ResizeImage anahtarı ile prefetch tarafındaki anahtarın da aynı olması gerekir.
class FeedImage extends StatelessWidget {
const FeedImage({super.key, required this.url});
final String url;
@override
Widget build(BuildContext context) {
final dpr = MediaQuery.devicePixelRatioOf(context);
return LayoutBuilder(
builder: (context, constraints) {
final targetWidth = (constraints.maxWidth * dpr).round();
final provider = ResizeImage(
NetworkImage(url),
width: targetWidth,
);
return Image(
image: provider,
fit: BoxFit.cover,
filterQuality: FilterQuality.low,
errorBuilder: (_, error, __) => const ColoredBox(
color: Color(0xffeeeeee),
child: Center(child: Icon(Icons.broken_image)),
),
);
},
);
}
}Edge case: cacheWidth kalite parametresi değildir. Görsel daha sonra tam ekran açılacaksa liste için 540 px, detay sayfası için ayrı ve daha büyük bir provider üretin. Aynı düşük çözünürlüklü provider'ı detay ekranında yeniden kullanmak bulanıklık üretir. Buna karşılık liste kartında FilterQuality.high seçmek, zaten yetersiz kaynak çözünürlüğünü geri getirmez; GPU örnekleme maliyetini artırabilir. DevTools Memory görünümünde değişiklik öncesi ve sonrası ImageCache kullanımını, Performance görünümünde de raster P95 değerini birlikte doğrulayın.
Flutter State Management İçinde Görsel Kimlik ve Cache Sınırları
flutter state management katmanı decoded ui.Image, ImageProvider veya widget saklamamalıdır. State yalnızca görselin iş kimliğini, sürümünü ve URL'sini taşımalıdır. Bunun nedeni, provider nesnesinin state değişiminden sonra yaşamaya devam eden ImageCache girdileriyle farklı yaşam döngülerine sahip olmasıdır. Ayrıca imzalı URL'ye her yenilemede farklı query parametresi eklenirse Flutter bunu farklı kaynak sayar ve bellek cache isabeti düşer.
Aşağıdaki model, veri revizyonunu state içinde açıkça tutar. CDN URL'si değişse bile arayüz hangi içerik revizyonunu gösterdiğini bilir; ancak erişim yetkisi kullanıcıya özel ise disk cache anahtarını kullanıcılar arasında ortaklaştırmayın. Aksi halde önceki kullanıcının cihazdaki görseli, yeni kullanıcı oturumunda yanlış bağlamda görünebilir.
class FeedItem {
const FeedItem({
required this.id,
required this.imageUrl,
required this.imageRevision,
});
final String id;
final Uri imageUrl;
final int imageRevision;
String get imageIdentity => '$id:$imageRevision';
}
class FeedState {
const FeedState(this.items);
final List<FeedItem> items;
}Bellek cache sınırını ürününüzün görsel boyutlarına göre açıkça koyun. Varsayılan değerler uygulamanın kart geometrisine göre fazla veya az kalabilir. Örneğin aynı anda görünür 12 kart, her biri yaklaşık 600 x 400 fiziksel piksel ise yalnızca decoded RGBA verisi kart başına yaklaşık 0.9 MB'dir. 40 MB hedefi başlangıç noktası olabilir, fakat gerçek değeri DevTools Memory snapshot ile doğrulayın.
void configureImageCache() {
final cache = PaintingBinding.instance.imageCache;
cache.maximumSize = 120;
cache.maximumSizeBytes = 40 << 20; // 40 MiB
}Bu ayarı uygulama başlangıcında, ilk görsel resolve edilmeden önce çalıştırın. Düşük sınır her zaman doğru değildir: kullanıcı ileri geri kaydırırken sürekli eviction ve yeniden decode oluşursa raster P95 yükselir. Önce mevcut ImageCache büyüklüğünü ve GC sıklığını kaydedin, sonra sınırı değiştirip aynı kaydırma testini tekrarlayın. Amaç en küçük cache değil, bellek zirvesi ile yeniden decode maliyeti arasında ölçülmüş dengedir.
Disk Önbelleği, CDN Başlıkları ve Kontrollü Prefetch
Flutter'ın ImageCache'i decoded görüntüleri RAM'de tutar; disk önbelleği ise indirilmiş byte'ları tutar. Bu iki katmanı birbirine karıştırmak yaygın bir teşhis hatasıdır. flutter_cache_manager paketi ile disk katmanını sınırlayabilir, CDN tarafında da Cache-Control, ETag ve imzalı URL yaşam süresini ürün gereksinimine göre düzenleyebilirsiniz. Kullanıcıya özel veya silinmesi gereken görsellerde 7 günlük genel bir stale period seçmek yerine oturum kapanışında ilgili cache anahtarlarını temizleyecek bir politika tanımlayın.
Aşağıdaki manager, akış görsellerini diğer dosya tiplerinden ayırır. maxNrOfCacheObjects disk girdisi sayısını sınırlar; bu sayı decoded RAM kullanımını sınırlamaz. Paket sürümünüzün platform adaptörleri ve cache veritabanı davranışı için entegrasyon testini Android ve iOS üzerinde ayrı çalıştırın.
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class FeedCacheManager extends CacheManager {
FeedCacheManager()
: super(
Config(
'feed-images-v1',
stalePeriod: const Duration(days: 3),
maxNrOfCacheObjects: 250,
),
);
}Prefetch'i tüm sayfaya uygulamak yerine görünür alanın hemen sonrasındaki sınırlı pencereye uygulayın. Aşağıdaki örnek aynı decode genişliğini kullanır ve istekleri seri çalıştırır. Paralel 30 adet precacheImage çağrısı, ağ tamamlanınca çok sayıda decode işlemini aynı anda tetikleyip raster thread'de kısa süreli yığılma oluşturabilir.
Future<void> prefetchNextImages(
BuildContext context,
List<FeedItem> items,
int startIndex,
double cardWidth,
) async {
final dpr = MediaQuery.devicePixelRatioOf(context);
final width = (cardWidth * dpr).round();
final end = (startIndex + 6).clamp(0, items.length);
for (var i = startIndex; i < end; i++) {
final provider = ResizeImage(
NetworkImage(items[i].imageUrl.toString()),
width: width,
);
await precacheImage(provider, context);
}
}Cross Platform Mobil Uygulama Geliştirme İçin Regresyon Testi
cross platform mobil uygulama geliştirme sürecinde aynı görsel kaynak Android ve iOS'ta aynı decode süresini vermeyebilir; cihazın CPU'su, codec implementasyonu, GPU sürücüsü ve bellek baskısı sonucu değiştirir. Bu nedenle test matrisinde en az bir düşük veya orta segment Android cihaz ve desteklenen bir iOS cihaz bulunmalıdır. Her cihazda profile modda aynı test hesabı, aynı 30 görsel URL'si ve aynı scroll komut dosyasıyla ölçüm alın.
En pratik kabul kriteri bir benchmark kaydıdır: değişiklik öncesi raster P95, 16.7 ms üzerindeki frame yüzdesi, maksimum ImageCache byte değeri ve soğuk-sıcak cache geçiş süreleri ayrı sütunlarda tutulur. Örneğin cacheWidth değişikliği raster P95'i düşürürken bulanık görsel oranını artırıyorsa, liste ve detay görünümü için iki ayrı görüntü boyutu gerekir. dart programlama eğitimi sırasında yalnızca widget API'lerini öğrenmek yerine, bu tür ölçüm dosyalarını kod incelemesinin parçası yapmak kalıcı bir mühendislik alışkanlığıdır.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter mobil uygulama geliştirmede cacheWidth değeri nasıl hesaplanır?
Widget'ın LayoutBuilder içindeki gerçek logical genişliğini MediaQuery.devicePixelRatioOf(context) ile çarpıp tam sayıya yuvarlayın. Örneğin 172 logical px ve 3.0 DPR için 516 kullanın. Grid kartı ile tam ekran detay görseli aynı genişliği kullanmamalıdır; her görünüm kendi hedef decode boyutunu üretmelidir.
Flutter state management içinde ImageProvider veya ui.Image saklamak doğru mu?
Genellikle hayır. State içinde URL, içerik kimliği ve revizyon saklayın; ImageProvider çözümlemesini widget katmanında yapın. Provider veya ui.Image saklamak, state yaşam döngüsü ile ImageCache yaşam döngüsünü bağlar ve imzalı URL değişimlerinde gereksiz cache miss veya eski içerik gösterimi teşhisini zorlaştırır.
Flutter kursu projelerinde görsel kaydırma performansı nasıl ölçülmeli?
Fiziksel cihazda flutter run --profile ile çalışın, DevTools Performance kaydında raster thread'i inceleyin ve aynı scroll senaryosunu en az 3 kez çalıştırın. Değişiklik öncesi-sonrası için raster P95, 16.7 ms üzerindeki frame sayısı ve DevTools Memory görünümündeki ImageCache zirvesini karşılaştırın. Debug moddaki FPS sayısını kabul kriteri yapmayın.
Dart programlama eğitimi sonrası flutter_cache_manager kullanmak RAM tüketimini azaltır mı?
Tek başına garanti etmez. flutter_cache_manager indirilmiş dosya byte'larını diskte yönetir; decoded görseller Flutter ImageCache içinde RAM kullanmaya devam eder. RAM zirvesini azaltmak için cacheWidth veya ResizeImage ile decode boyutunu düşürün ve PaintingBinding.instance.imageCache için ölçülmüş maximumSizeBytes sınırı koyun.
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.



