Yemek siparişi uygulaması olan bir müşterimizin mağaza puanı üç ayda 4,6'dan 3,8'e düştü. Yorumları döktük: "kasıyor", "açılmıyor", "menü takılıyor". Ekip yeni özellik yetiştirmekten performansa bakamamış; biz ölçtüğümüzde tablo netti — orta segment bir Android'de soğuk başlangıç 6,5 saniye, menü listesinde kaydırma sırasında gözle görülür takılmalar. Üç haftalık düzeltme turunun sonunda açılış 1,9 saniyeye indi; puanın toparlanması iki ayını aldı ama toparladı.
Bu hikâyedeki en önemli detay şu: yapılan hiçbir düzeltme egzotik değildi. Mağaza yorumlarındaki "kasıyor" şikâyeti, neredeyse her seferinde düzeltilebilir üç-beş teknik hatanın toplamıdır. Araştırmaların ortak bulgusu da acımasız: açılışı üç saniyeyi geçen uygulamalarda hatırı sayılır bir kullanıcı dilimi, daha ilk ekranı görmeden vazgeçiyor.
Açılışta gerçekten ne yükleniyor?
Soğuk başlangıç sorununu teşhis etmenin ilk adımı basit bir soru: uygulama, ilk ekranı gösterebilmek için gerçekte neyi bekliyor? O yemek uygulamasında cevap ibretlikti: açılışta senkron bir ağ isteği (kampanya listesi — ilk ekranda görünmüyordu bile), ana thread üzerinde çalışan bir veritabanı migration'ı, dört bin piksellik bir karşılama görselinin decode edilmesi ve dokuz ayrı üçüncü parti SDK'nın sırayla init edilmesi. Bunların hiçbiri kullanıcının ilk saniyede ihtiyaç duyduğu şeyler değildi.
Reçetemiz her projede aşağı yukarı aynı: ilk ekran, veri gelmeden iskelet (skeleton) hâliyle anında çizilir; veri arkadan gelir ve yerine oturur. SDK init'leri gecikmeli yapılır — analitik aracının açılışın üçüncü milisaniyesinde ayakta olmasına gerek yok. Migration'lar ve ağır okumalar ana thread'den çıkar. Başlangıç rotası bilinçli olarak hafif tutulur; oraya eklenen her bağımlılık, her kullanıcının her açılışta ödediği bir vergidir.
Ölçümün de adabı var. "Açılış süresi" derken neyi kastettiğinizi netleştirin: bizim referansımız kullanıcının etkileşime geçebildiği an — logo animasyonunun bittiği an değil. Lokal ölçüm için platform araçları yetiyor; ama asıl hazine, sahadan gelen gerçek kullanıcı metrikleri. Firebase Performance gibi araçlarla üretimdeki dağılıma bakın ve medyana değil yavaş dilime odaklanın: kullanıcıların en yavaş yüzde onunun yaşadığı süre, mağaza yorumlarınızı yazan kitlenin deneyimidir.
Karede on altı milisaniye
Akıcılık hissinin matematiği katıdır: ekran saniyede altmış kare çiziyorsa, her kare için bütçeniz yaklaşık on altı milisaniyedir. Ana thread o pencerede çizimden başka bir işle — büyükçe bir JSON'ı parse etmek, görsel işlemek, uzun bir listeyi hesaplamak — meşgulse kare düşer ve kullanıcının "takıldı" dediği o küçük donma, jank, doğar. Tek tük kare kaybını göz affeder; kaydırma sırasında üst üste gelen kayıplar ise uygulamanın kalitesiz hissettirmesi için yeterlidir.
Çare, ağır işi ana thread'den uzak tutmak. Flutter dünyasında bunun adı isolate; büyük parse işlerini, sıkıştırma ve şifrelemeyi oraya taşıyoruz. Listelerde tembel kurulum (lazy/recycler yapılar) pazarlık konusu bile değil: bin elemanlı menüyü tek seferde kurmak, hem belleği hem kareleri yakar. Ekranda ne görünüyorsa o kurulur, gerisi sırasını bekler.
Bir de gölge maliyetler var: her kare yeniden hesaplanan layout'lar, gereksiz widget yeniden kurulumları, kaydırma sırasında çalışan animasyonlu gölgeler. Bunlar profiler açılmadan görünmez — ki zaten bu yüzden ölçüm şart.
Flutter özelinde iki ucuz kazanç daha: değişmeyen widget'ları const yapmak ve setState kapsamını daraltmak. Ekranın tepesindeki bir sayaç değişti diye bütün sayfayı yeniden kuran kod, sahada en sık gördüğümüz jank kaynaklarından. Widget ağacını "değişen ne kadar küçükse, yeniden kurulan o kadar küçük olmalı" ilkesiyle bölmek, çoğu zaman tek başına kaydırma akıcılığını kurtarıyor. (DevTools'taki rebuild sayacı, bu israfı çıplak gözle gösterir; açıp bakmak beş dakika.)
Görseller: en büyük ve en kolay kazanç
Performans turlarımızda en yüksek getiriyi neredeyse her zaman görseller verir. Klasik vaka: sunucu, ürün fotoğrafını orijinal boyutuyla gönderiyor; telefon, dört bin piksellik görseli indirip belleğinde küçültüyor. Bu, hem bant genişliği hem bellek hem CPU açısından cinayettir — ve çözümü sunucudan ekran boyutuna uygun görsel istemek kadar basittir. WebP formatına geçmek dosya boyutlarını belirgin düşürür; önbelleğe makul bir sınır koymak bellek baskısını azaltır; liste görsellerinde placeholder artı yumuşak geçiş, algılanan hızı gerçek hızdan bile fazla iyileştirir. (Kullanıcı algısı da bir performans metriğidir; belki de en önemlisi.)
O yemek uygulamasında yalnızca görsel boru hattını düzeltmek, kaydırma jank'inin yarısını tek başına çözdü.
Görsel boru hattının sunucu ayağını da ihmal etmeyin: CDN'lerin çoğu URL parametresiyle anlık boyutlandırma yapabiliyor. Liste küçük resmi için ayrı boyut, detay sayfası için ayrı boyut ister; telefon asla ekranda göstereceğinden büyüğünü indirmez. Bu kurgu bir kez kurulunca tasarımcı da geliştirici de düşünmek zorunda kalmıyor — doğru davranış, altyapının varsayılanı hâline geliyor.
Emülatör yalan söyler
Son ders en genel olanı: ölçmeden hiçbir iyileştirmeye inanmayın, ve doğru yerde ölçün. Geliştiricinin masasındaki son model telefon ve emülatör, sahadaki gerçekliği temsil etmez; kullanıcı kitlenizin elindeki üç-dört yaşında orta segment cihaz eder. Bizim test rafımızda tam da bu yüzden eskimiş telefonlar durur — yenisi değil, kasıtlı olarak eskisi.
Gerçekçi test, cihazla da bitmiyor. Zayıf ağ simülasyonu (kısıtlı bant, yüksek gecikme), düşük güç modu ve dolu depolama, uygulamanızın sahadaki ortalama gününü temsil eder. Arka plana atılan uygulamanın sistem tarafından öldürülüp geri açılması — process death — ayrı bir sınavdır: kullanıcı kaldığı ekrana mı döner, yoksa formda yazdıklarını kaybedip baştan mı başlar? Bu senaryoyu test etmeyen ekip, mağaza yorumlarında "her şey siliniyor" cümlesiyle tanışır.
Disiplini kalıcılaştırmanın yolu da performans bütçesi: açılış iki saniyenin altında, liste kaydırma jank'siz, bellek belirlenen eşiğin altında. Bu bütçeyi CI hattında otomatik kontrol eden ekipler, regresyonu mağaza yorumundan değil pipeline'dan öğrenir. Aradaki fark, üç ayda bir puan kaybetmekle hiç kaybetmemek arasındaki farktır.
Bir kalem daha, çoğu performans sohbetinde unutulur: uygulama boyutu. İndirme ekranında bekleyen kullanıcı da bir performans kurbanıdır ve zayıf ağda 150 megabaytlık paket, kurulumdan vazgeçme sebebidir. Kullanılmayan kaynakları ayıklamak, görselleri paket yerine ihtiyaç anında indirmek ve ölü kod tespiti, açılış süresi kadar bu metriği de iyileştirir. Boyut da bütçeye dahil; her sürümde ölçülür.
Uygulamanızın yorumlarında "kasıyor" kelimesi belirmeye başladıysa bekletmeyin; bize yazın, bir günlük ölçüm turu çoğu zaman sorunların yarısını isimleriyle ortaya çıkarıyor.