AGP 9'da R8 Optimized Resource Shrinking: Statik Analiz Dinamik Erişimle Karşılaştığında
yazan: Nikolay Vlasov
Android Gradle Plugin (AGP) 9.0'a geçiş, kaynak küçültme (resource shrinking) yönteminde önemli bir değişiklik getirdi. Derleme türünüzde isShrinkResources = true etkinleştirildiğinde, AGP artık optimized resource shrinking (optimize edilmiş kaynak küçültme) kullanıyor. Bu mekanizma, kaynak analizini kod analiziyle sıkı bir şekilde bağlayarak yalnızca kullanılmayan kod tarafından referans verilen kaynakları kaldırır. Bu durum APK boyutunun küçülmesini sağlasa da, dinamik kaynak erişimine dayanan projelerde —önceki sürümlerde fark edilmemiş olabilecek— sorunlara yol açabilir. (Android Developers)
Dynamic Feature Module'lerde Kaynaklar Neden Kayboluyor?
Modern kaynak küçültücüler (resource shrinkers) büyük ölçüde statik analize dayanır. Bir kaynağa kodunuzda doğrudan referans veriliyorsa (örneğin, R.raw.my_resource), küçültücü bunun kullanımını kolayca takip edebilir. Ancak, kaynaklara dize adları ve çalışma zamanı aramaları (runtime lookup) kullanarak dinamik olarak eriştiğinizde, statik analiz artık kaynağın gerekli olduğunu güvenilir bir şekilde kanıtlayamaz. AGP 9 ile bu durum daha da belirginleşir çünkü optimize edilmiş kaynak küçültme artık birleşik kod ve kaynak optimizasyon boru hattının (pipeline) bir parçasıdır. (Android Developers)
Dynamic Feature Modules (DFM) kullanan projelerde, özellikle modüller Play Feature Delivery aracılığıyla ayrı olarak teslim edildiğinde, temel modül çoğunlukla özelliğin R sınıflarına derleme zamanı bağımlılığına (compile-time dependency) sahip olmaz. Yeni kaynak küçültücü bir süredir dinamik özellikleri destekliyor olsa da, dinamik aramalar nedeniyle "eksik kaynak" sorunlarının en sık yaşandığı yer tam da bu mimaridir. (Android Developers)
Örneğin, Smart Directory Agent Center projesinden bir kesit alalım. Tam metin arama dizininin (FTS index) yüklenmesi şu şekilde uygulanmıştır:
private fun getFtsId(): Int {
return dynamicContextWrapper.getContextForFts().resources.getIdentifier(
"fts",
"raw",
featuresManager.ftsModulePackage,
)
}
Kaynak küçültücü için bu çağrı bir "kara kutu"dur: Kaynak adı statik bir referans yerine dize (string) olarak iletilir. Sonuç olarak, derleme araçları kaynağın kullanılmadığını yanlış bir şekilde varsayabilir ve sürüm derlemesi (release build) sırasında kaynağı kaldırarak çalışma zamanında Resources$NotFoundException hatasına neden olabilir. (Android Developers)
AGP'de Kaynak Küçültmenin Gelişimi
AGP 9.0'ın kaynak küçültmeyi sıfırdan icat etmediğini, aksine optimize edilmiş boru hattı içinde davranışını daha katı ve öngörülebilir hale getirdiğini anlamak önemlidir. Precise resource shrinking (hassas kaynak küçültme) AGP 8.3'te zaten varsayılan olarak etkindi. AGP 9.0'da, isShrinkResources = true olduğunda optimized resource shrinking otomatik olarak tetiklenir. 8.6 ile 9.0 arasındaki sürümler için bu davranış genellikle açıkça android.r8.optimizedResourceShrinking=true bayrağının ayarlanmasını gerektiriyordu. (Android Developers)
AGP 8.9'daki regresyona (regression) dikkat edin: Sürüm notları, dinamik özellik modüllerinde kaynakların kaybolmasına neden olan bir kaynak küçültme hatasını belgeledi. Bu hata daha sonra 8.9.2 ve 8.10 sürümlerinde düzeltildi. Sürüm derlemenizde bir kilitlenme ile karşılaşırsanız, bu her zaman mimarinizdeki bir hata olmayabilir — bazen küçültücünün belirli bir sürümündeki bir hatadan kaynaklanabilir. (Android Developers)
Gerçek Çözüm: Keep Dosyaları ve Kaynakların Açıkça Korunması
Dolaylı olarak kullanılan kaynakları korumanın doğru yolu, projenizin kaynaklarında bir keep dosyası (saklama dosyası) tanımlamaktır. Belgeler, özel bir mekanizma olarak soyut bir "global keep.xml" dosyasına güvenmek yerine, res/raw/ içinde (örneğin, res/raw/my.package.keep.xml) bir XML dosyası oluşturulmasını önerir. Çok modüllü projelerde kaynak birleştirme (resource merging) sırasında çakışmaları önlemek için keep dosyası benzersiz bir ada sahip olmalıdır. (Android Developers)
Bizim durumumuzda düzeltme şu şekildeydi:
<!-- Yol: app/src/main/res/raw/com.example.app.keep.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@style/Theme.StartingScreen_*,@style/AppTheme_*,@raw/fts" />
bu yaklaşım niyetinizi açık hale getirir: @raw/fts kaynağı, yalnızca dinamik ad çözümleme yoluyla erişilebilir olsa bile kaldırılmamalıdır. Bu, Resources.getIdentifier() aracılığıyla okunan tüm kaynaklar için kritik öneme sahiptir. (Android Developers)
AGP 9 ve R8 İçin En İyi Uygulamalar
Birincisi: Dinamik olarak erişilen kaynaklar için, @raw/* gibi geniş joker karakterlere (wildcards) güvenmek yerine bunları tools:keep içinde açıkça listelemek en iyisidir. Kurallarınız ne kadar dar olursa, gereksiz kaynakları saklama riskiniz o kadar azalır ve derleme davranışınız o kadar tahmin edilebilir hale gelir. (Android Developers)
İkincisi: shrinkMode ayarını unutmayın. Varsayılan olarak, getIdentifier() aracılığıyla dinamik olarak kullanılabilecek kaynakları korumaya çalışan safe mode (güvenli mod) kullanılır. tools:shrinkMode="strict" ayarına geçerseniz, yalnızca açıkça atıfta bulunulan kaynaklar korunur, bu da keep kurallarınızın doğruluğunu son derece kritik hale getirir. (Android Developers)
Üçüncüsü: Nedeni semptomla karıştırmayın. Sorun projenizin DFM kullanması değil, bazı kaynakların yalnızca çalışma zamanı aramaları yoluyla erişilebilir olması ve statik analizci tarafından görülememesidir. DFM sadece bu senaryoyu daha olası ve optimizasyon sırasında daha belirgin hale getirir. (Android Developers)
Sonuç
AGP 9'daki "kaybolan" kaynaklar bir sihir veya rastlantısal hata değildir. Bunlar, optimize edilmiş kaynak küçültmedeki daha katı statik analizin mantıksal bir sonucudur. Mimariniz dinamik kaynak erişimine dayanıyorsa, bu kaynakları keep dosyaları aracılığıyla açıkça korumalı ve seçtiğiniz küçültücü moduna göre davranışı doğrulamalısınız. Kilitlenmeler özellikle AGP 8.9–9.0 sürümüne güncelledikten sonra ortaya çıktıysa, eklenti sürümünüzü tekrar kontrol edin; bu alandaki bilinen regresyonlar daha yeni yamalarda zaten giderilmiştir. (Android Developers)
Faydalı Kaynaklar
- 1 Enable app optimization with R8 — R8'i yapılandırmak için resmi kılavuz.
- 2 Android Gradle Plugin 7.1.0 Release Notes — Değişiklik günlüğü ve Dinamik Özellikler desteği.
- 3 Tools attributes reference —
keepveshrinkModeözniteliklerinin açıklaması. - 4 Android Gradle Plugin 8.3.0 Release Notes — Precise Resource Shrinking hakkında ayrıntılar.
- 5 Android Gradle Plugin 8.9.0 Release Notes — Kaynak küçültme hata düzeltmeleri hakkında bilgiler.
- 6 Customize which resources to keep —
keep.xmlile çalışma konusunda ayrıntılı kılavuz.