Mobil uygulamaların boyutu, kullanıcı deneyiminden indirme oranlarına, hatta uygulamanın marketteki sıralamasına kadar birçok şeyi doğrudan etkiliyor. Yıllardır hem kendi yan ürünlerimde hem de büyük müşteri projelerinde mobil uygulama geliştirirken, bu “uygulama boyutu” konusu hep masamda oldu. İnsanlar, özellikle mobil veriyle indirme yaparken, gereksiz büyük uygulamaları indirmek istemiyor.
Benim için uygulama boyutu optimizasyonu sadece bir performans meselesi değil, aynı zamanda bir maliyet meselesi. Daha küçük bir uygulama paketi, daha hızlı indirme, daha az veri kullanımı ve dolayısıyla daha iyi bir ilk izlenim demek. Bu yazıda, mobil uygulama geliştirirken uyguladığım ve gerçekten işe yarayan pratik yaklaşımları, karşılaştığım zorlukları ve seçtiğim trade-off’ları anlatacağım.
Statik Kaynakların Etkisi ve Optimizasyonu
Uygulama boyutunu en çok etkileyen faktörlerden biri, şüphesiz statik kaynaklar. Görseller, ses dosyaları, videolar ve diğer medya içerikleri, uygulamanın toplam boyutunun önemli bir kısmını oluşturabiliyor. Bir mobil uygulamada, onboarding ekranlarındaki yüksek çözünürlüklü görseller yüzünden uygulamanın boyutu ilk başta epey kabarıktı.
Bu durumu kabul edilemez buldum ve hemen görselleri optimize etmeye başladım. İlk adım, gereksiz yüksek çözünürlüklü görselleri tespit etmek oldu. Çoğu zaman tasarımcılar bize “en iyisini” vermek isterken, mobil cihazların ekran çözünürlükleri için gereksiz büyüklükte dosyalar sunabiliyor.
Bir projemde, tüm PNG görsellerini WebP’ye dönüştürerek sadece görsellerden kaynaklanan boyutu belirgin oranda azalttım. iOS tarafında ise Asset Catalogs kullanarak cihaz özelinde farklı çözünürlükler sunmak ve gereksiz olanları elemek mümkün. Ayrıca, ImageOptim gibi araçlarla görsellerin metadata’sını temizlemek ve kayıpsız sıkıştırma uygulamak, birkaç MB daha kazandırabiliyor. Örneğin, büyük bir PNG dosyasını TinyPNG veya Squoosh ile kabaca üçte birine kadar indirebildiğim oldu.
# WebP dönüşümü için örnek bir komut (Linux'ta cwebp ile)
for file in *.png; do
cwebp -q 75 "$file" -o "${file%.png}.webp"
done
Bu yaklaşımla, bahsettiğim uygulamanın boyutunu kabaca yarısına kadar düşürmeyi başardım. Bu sadece indirme boyutunu değil, uygulamanın yüklenme süresini de olumlu etkiledi.
Kodu Küçültme ve Ağaç Budama (Tree-Shaking)
Uygulama kodunun kendisi de şişkinliğe neden olabilir, özellikle büyük kütüphaneler ve framework’ler kullanıldığında. Kullanmadığımız kod parçaları, import ettiğimiz tüm kütüphanelerin tamamı, paketin içine dahil olabiliyor. Burada “tree-shaking” denilen kavram devreye giriyor.
Android tarafında ProGuard ve R8 gibi araçlar, derleme sırasında kullanılmayan sınıfları, metotları ve alanları otomatik olarak kaldırarak kodu küçültüyor. Bir mobil uygulamada, R8 optimizasyonlarını daha agresif hale getirince paket boyutunun belirgin ölçüde düştüğünü gördüm.
Flutter gibi framework’lerde de benzer mekanizmalar var. flutter build apk --split-debug-info=./debug_info komutu ile debug sembollerini ayrı bir dosyaya alarak APK boyutunu küçültmek mümkün. Bu, hata ayıklama sırasında sembolleri manuel olarak yüklemenize olanak tanır ancak son kullanıcı paketini hafifletir. Kendi Android uygulamamda, bu yöntemle birkaç MB’lık bir kazanç elde ettim.
# pubspec.yaml dosyasında ek yapılandırma
flutter:
# ...
# Aşağıdaki ayar, uygulamanızın boyutunu küçültmeye yardımcı olur
# Gerekli olmayan icon font'larını kaldırır
uses-material-design: true
generate: true
Ayrıca, gereksiz kütüphaneleri ve bağımlılıkları projeden çıkarmak da önemli. Her eklenen kütüphane, beraberinde kendi bağımlılıklarını ve kodlarını getirir. Bir özelliğin küçük bir parçası için tüm bir kütüphaneyi dahil etmek yerine, o özelliği manuel olarak uygulamayı veya daha hafif bir alternatif bulmayı tercih ediyorum. Özellikle build.gradle dosyasında implementation yerine api veya compileOnly gibi farklı bağımlılık tiplerini doğru kullanmak da bu konuda fark yaratır.
Native Kütüphaneler ve ABI Bölümleme
Mobil uygulamalar genellikle farklı CPU mimarilerini desteklemek için native kütüphaneler içerir: armeabi-v7a, arm64-v8a, x86, x86_64 gibi. Eğer uygulamanız tüm bu mimariler için kütüphane içeriyorsa, paketin boyutu gereksiz yere büyür çünkü bir cihaz sadece kendi mimarisine uygun kütüphaneyi kullanır.
Bu problemi çözmek için Android tarafında iki ana yaklaşım var: APK Splitting ve Android App Bundle (AAB). Ben artık AAB’yi tercih ediyorum. AAB, uygulamanızı Google Play’e yüklediğinizde, Google Play’in her cihaza özel optimize edilmiş bir APK sunmasını sağlar. Cihazın mimarisine, diline ve ekran yoğunluğuna göre sadece gerekli kaynakları içeren bir APK indirilir.
Bir müşteri projesinde, manuel APK Splitting yaparken build.gradle dosyasını şöyle yapılandırmıştım:
android {
// ...
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a' // Sadece bu mimarileri dahil et
universalApk false // Tüm mimarileri içeren evrensel bir APK oluşturma
}
}
}
Bu kod parçası, belirtilen ABI’ler için ayrı APK’lar oluşturur. Ancak, AAB kullanıyorsanız bu manuel ayara genellikle gerek kalmaz, zira AAB bu optimizasyonu otomatik olarak yapar. AAB’ye geçtikten sonra kullanıcıların indirdiği uygulamanın ortalama boyutunun gözle görülür biçimde düştüğünü gözlemledim. Bu, özellikle eski Android cihazlarda indirme ve yükleme süreleri açısından önemli bir iyileşme demekti. Ayrıca, gereksiz native kütüphaneleri projeden tamamen çıkarmak da önemli. Bazen bir bağımlılık, hiç kullanmadığımız bir native kütüphaneyi de beraberinde getiriyor.
Dil Kaynakları ve Yerelleştirme Optimizasyonu
Çok dilli uygulamalar geliştirirken, dil kaynakları da uygulama boyutunu artırabilir. Uygulamanız onlarca farklı dili destekliyorsa ve her dil için tüm string’leri, görselleri ve diğer kaynakları pakete dahil ediyorsanız, bu durum ciddi bir şişkinliğe yol açar.
Benim pratiğimde, genellikle sadece en çok kullanılan dilleri doğrudan pakete dahil etmeyi tercih ediyorum. Diğer diller için ise ya dinamik indirme mekanizmaları kullanıyorum ya da hiç dahil etmiyorum, bu tamamen iş gereksinimlerine bağlı. Örneğin, çok sayıda dil desteği gereken bir mobil uygulamada, tüm dilleri pakete dahil etmek boyutu hatırı sayılır ölçüde artırıyordu.
Android’de resConfigs veya resourceConfigurations ayarı ile sadece belirli dil kaynaklarının pakete dahil edilmesini sağlayabiliriz. Bu, özellikle az kullanılan dillerin gereksiz yere dahil edilmesini engeller.
android {
// ...
defaultConfig {
// ...
resConfigs "en", "tr", "de" // Sadece İngilizce, Türkçe ve Almanca kaynaklarını dahil et
}
}
Bu ayarı kendi yan ürünümde kullandığımda, uygulamanın toplam boyutunda küçük ama gözle görülür bir azalma gördüm. Bu küçük bir miktar gibi görünse de, birden fazla optimizasyon stratejisi bir araya geldiğinde toplamda önemli bir fark yaratabiliyor. Ayrıca, çeviri dosyalarında yinelenen veya boş string’leri temizlemek de faydalı bir adım. Bazen çevirmenler aynı metni farklı yerlerde tekrar yazabiliyor veya bazı metinleri boş bırakabiliyorlar. Bu durumlar da gereksiz veri şişkinliğine neden olur.
Build Süreçleri ve Derleyici Optimizasyonları
Uygulamanın derleme (build) süreci, nihai paketin boyutu üzerinde doğrudan etkiye sahiptir. Geliştirme (debug) ve yayın (release) build’leri arasında önemli farklar vardır ve bu farkları doğru yönetmek gerekiyor. Debug build’ler genellikle hata ayıklama bilgilerini, gereksiz log’ları ve bazen de daha az optimize edilmiş kodları içerir.
Release build’lerde ise bu bilgilerin çoğu çıkarılmalı ve derleyici tarafından mümkün olan en yüksek optimizasyonlar uygulanmalıdır. Örneğin, Android’de minifyEnabled ve shrinkResources ayarlarını true yapmak, kullanılmayan kod ve kaynakları kaldırarak boyutu önemli ölçüde küçültür.
android {
// ...
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Bir mobil uygulamayı CI/CD pipeline’ı üzerinden dağıtırken, release build’lerin doğru yapılandırıldığından emin oldum. İlk başta shrinkResources ayarını unutmuştum ve APK boyutu gereksiz yere büyüktü. Bu ayarı aktif ettikten sonra, uygulama boyutu gözle görülür ölçüde daha da küçüldü.
iOS tarafında da benzer derleyici ayarları mevcuttur. Xcode’da “Strip Debug Symbols During Copy”, “Strip Style” ve “Deployment Postprocessing” gibi seçenekler, uygulamanın nihai boyutunu etkiler. Bu ayarları her zaman release build’ler için en agresif şekilde yapılandırıyorum. Bence, bu tür ince ayarlar, uygulamanın performansını ve kullanıcıya ulaşma hızını doğrudan etkileyen kritik detaylar.
Run-Time Yükleme ve Dinamik Özellik Modülleri
Uygulama boyutunu optimize etmenin en ileri düzey yaklaşımlarından biri, uygulamanın tüm özelliklerini başlangıçta indirmek yerine, sadece ihtiyaç duyulduğunda yüklemektir. Bu, özellikle büyük ve çok özellikli uygulamalar için oyun değiştirici olabilir. Android’de Google Play Feature Delivery (dinamik özellik modülleri) ve iOS’ta On-Demand Resources (ODR) bu amaçla kullanılır.
Bir yan ürünümde, bazı gelişmiş analitik ve raporlama özelliklerini başlangıçta yüklemek yerine, kullanıcı ilgili menüye tıkladığında indirmeyi tercih ettim. Bu özellikler, uygulamanın temel akışı için kritik değildi ve çoğu kullanıcı tarafından nadiren kullanılıyordu. Bu sayede, uygulamanın ilk indirme boyutunu hatırı sayılır ölçüde azalttım.
Android tarafında bu, base modülüne ek olarak ayrı feature modülleri oluşturarak yapılır. Bu modüller, Play Store tarafından sadece istenildiğinde cihaza gönderilir. Yalnızca belirli kullanıcı rolleri tarafından kullanılan ağır özellikleri (örneğin gelişmiş bir planlama veya raporlama modülünü) dinamik özellik olarak ayırmak, temel paketi şişirmeden bu işlevleri sunmanın mantıklı bir yoludur.
<!-- build.gradle (feature module) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.myfeature">
<dist:module
dist:instant="false"
dist:onDemand="true"
dist:title="@string/feature_title">
<dist:fusing dist:include="true" />
</dist:module>
</manifest>
Bu yaklaşım, özellikle büyük veri setleri veya özel kütüphaneler gerektiren özellikler için çok mantıklı. Örneğin gelişmiş bir filtreleme modelini yalnızca onu gerçekten kullanan kullanıcıların indirmesine izin vererek, geri kalan kullanıcıların indirme paketini şişirmekten kaçınabilirsiniz. Elbette, bu durum ek bir karmaşıklık getiriyor: modüllerin yüklenme durumunu yönetmek, indirme hatalarını ele almak ve kullanıcıya uygun geri bildirim sağlamak gerekiyor. Ancak elde edilen fayda, bu karmaşıklığa değer.
Sonuç
Mobil uygulama boyutu optimizasyonu, tek bir sihirli değnekle çözülebilecek bir konu değil. Bu, sürekli dikkat, birden fazla stratejinin birleşimi ve bazen de acı veren trade-off’lar gerektiren bir süreç. Görsel optimizasyonundan kod küçültmeye, native kütüphane yönetiminden dinamik özellik modüllerine kadar birçok alanda yaptığımız her seçim, uygulamanın nihai boyutunu etkiliyor.
Benim deneyimimde, bu optimizasyonlar sadece birkaç MB’lık kazançlar gibi görünse de, bir araya geldiklerinde uygulamanın genel performansına, kullanıcı deneyimine ve hatta market başarısına doğrudan etki ediyor. Unutmayın, her MB önemlidir. Özellikle gelişmekte olan pazarlarda veya düşük bant genişliğine sahip bölgelerde, küçük bir uygulama boyutu, uygulamanızın indirilip kullanılma şansını önemli ölçüde artırır. Bu yüzden, her yeni sürümde veya büyük özellik eklemesinde bu optimizasyon adımlarını tekrar gözden geçiriyorum.
Umarım bu rehber, kendi mobil projelerinizde uygulama boyutunu optimize etme yolculuğunuzda size ışık tutar. Bir sonraki yazıda, mobil uygulamalarda performans profilini çıkarırken kullandığım araçları ve teknikleri anlatacağım.