R8 ile platform kodunu ve kaynaklarını optimize etme

Android platformu derleme sistemi (Soong), kullanılmayan kodları ve kaynakları küçültmek, optimize etmek ve kaldırmak için Android uygulamaları (android_app), testler (android_test) ve compile_dex: true içeren yüklenebilir Java kitaplıkları (services.jar gibi) dahil olmak üzere DEX bayt kodunu derleyen hedeflerde R8 derleyicisini çalıştırır. Soong, statik kitaplıklarda (android_library ve statik java_library) R8'i doğrudan çalıştırmaz. Bunun yerine, tüketici saklama kurallarını (export_proguard_flags_files: true) statik olarak bağlayan alt DEX hedeflerine eklemek ve yaymak için optimize özellik bloğunu kullanır.

Sistem görüntüleri veya tedarikçi firma paketleri oluşturan platform mühendisleri için aşırı geniş kapsamlı saklama kurallarını ortadan kaldırmak, sistem sağlığı açısından doğrudan fayda sağlar:

  • Daha küçük sistem bölümü ayak izi: R8, APK'ları ve JAR'ları /system, /system_ext, /product ve /vendor'te paketlemeden önce kullanılmayan sınıfları, yöntemleri ve kaynakları kaldırır.
  • Daha küçük derlenmiş yapılar: Daha az DEX yöntemi, derleme sırasında veya cihazda derleme sırasında dex2oat tarafından oluşturulan daha küçük .odex ve .vdex dosyaları anlamına gelir.
  • Çalışma zamanında daha az bellek baskısı: Android, .odex, .vdex ve APK kaynak tablolarını, isteğe bağlı sayfalama yapılan mmap çağrılarını kullanarak işlem belleğiyle eşlediğinden daha küçük ikili programlar, uygulama başlatma sırasında önemli sayfa hatalarını azaltır ve işlemler arasında yerleşik kod ve kaynak belleğini düşürür. Derlenmiş uygulama kodunun cihaz belleğini nasıl etkilediği hakkında daha fazla bilgi için Uygulama kodu bellektir başlıklı makaleyi inceleyin.
  • Daha etkili tüm program optimizasyonu: Dar tutma kısıtlamaları, R8'in satır içi yöntemleri kullanmasına, arayüz ve sanal çağrıları sanallaştırmasına, kullanılmayan alanları ayıklamasına ve sabitleri sınıflar arasında yaymasına olanak tanır.

Soong'da R8 optimizasyonunu yapılandırma

Android.bp dosyalarında, android_app ve yüklenebilir java_library hedeflerinde (veya tüketici saklama kurallarını dışa aktarmak için android_library ve statik java_library modüllerinde) optimize özellik bloğunu kullanarak R8 optimizasyonunu yapılandırın:

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

Aşağıdaki tabloda, Soong'daki en yaygın optimize özellikleri (build/soong/java/dex.go içinde tanımlanmıştır) özetlenmiştir:

Özellik Açıklama
enabled R8'in hedefte çalışıp çalışmayacağını kontrol eder. Tüm android_app hedefleri için varsayılan olarak true değerine ayarlanır.
shrink Erişilemeyen sınıfları, alanları ve yöntemleri kaldırmak için ağaç sallamayı kontrol eder. android_app hedefleri için varsayılan olarak true değerine ayarlanır (bağımsız java_library ve test modülleri için false değerine ayarlanır. Bu modüller, açıkça ayarlanmadığı sürece -dontshrink değerini iletir).
optimize Yöntem satır içi ekleme, sınıf birleştirme, sabit yayma ve kullanılmayan dal kaldırma gibi bayt kodu optimizasyonlarını kontrol eder. android_app hedefleri için varsayılan olarak true değerine ayarlanır (RELEASE_R8_OPTIMIZE_BY_DEFAULT yayınlanan derleme işareti tarafından kontrol edilir).
obfuscate Tanımlayıcı sadeleştirmesini ve yeniden adlandırmasını kontrol eder. Geçmiş uyumluluk için varsayılan olarak false değerine ayarlanır ancak DEX boyutunu küçültmek ve daha kapsamlı optimizasyonları etkinleştirmek için mümkün olduğunda true değerine ayarlayın (Mümkün olduğunda kod karartmayı etkinleştirme bölümüne bakın).
shrink_resources Kod daraltma işleminden sonra paketlenmiş APK'dan kullanılmayan kaynakları (res/ giriş) kaldırır. Varsayılan değer: false.
optimized_shrink_resources Entegre R8 kodunu ve kaynak küçültücü ardışık düzenini çalıştırır. Böylece R8, kod ve kaynak referanslarını tek bir geçişte birlikte izler. shrink_resources: true ayarlandığında varsayılan olarak RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (standart platform derlemelerinde true) değerine ayarlanır.
proguard_flags_files Özel saklama kuralları içeren modüle özgü .flags veya .pro dosyalarını listeler. Standart ek açıklamalar veya varsayılanlar yeterli olduğunda özel dosyalar eklemekten kaçının.
export_proguard_flags_files Bu kitaplığın proguard_flags_files öğesini, statik olarak bağlı olan alt modüllere yayar.
trace_references_from Bayt kodu bu hedefe referans veren doğrudan DEX derlemeye bağımlı hedefleri (instrumentation_for veya libs aracılığıyla bağlı) listeler. R8, bu hedefleri otomatik olarak izler ve saklar (ayrıca override_android_app ve override_android_test varyantları arasında yayılır ve bir .impl bölmesiyle eşlendiğinde bağımlı olmayan tamamlayıcı Java kitaplıkları için desteklenir).
allow_traced_references Doğrudan DEX derlemeye bağlı hedeflerin (instrumentation_for veya libs aracılığıyla bağlanan, trace_references_from içinde listelenemeyen çapraz depo ya da tedarikçi testleri gibi), trace_references: true ayarını yaparak bayt kodu referanslarını bu hedefte izlemeyi etkinleştirmesine olanak tanır.
trace_references allow_traced_references: true içeren bir hedefin doğrudan DEX derleme bağımlılığında true olarak ayarlandığında bu modülde R8 tracereferences çalıştırılır ve oluşturulan saklama kuralları hedef modüle iletilir.
protect_api_surface --protect-api-surface değerini R8'e iletir. java_sdk_library üzerinde ayarlandığında, etkinleştirilen tüm API kapsamlarında (herkese açık, sistem, modül kitaplığı, sistem sunucusu ve test) Metalava API saklama kurallarını otomatik olarak oluşturur ve uygulama kitaplığına ekler.

Statik kitaplıklar (android_library, java_library ve AAR içe aktarmaları), R8 küçültme ve bayt kodu optimizasyonunu etkinleştirmek için bunları statik olarak (doğrudan veya geçişli olarak) bağlayan tüm alt DEX derleme hedeflerini zorunlu kılmak üzere Android.bp içinde required_downstream_optimization: { enabled: true, optimize: true } değerini de ayarlayabilir. Soong, bunu androidx.compose.runtime_runtime üzerinde otomatik olarak etkinleştirir. Böylece Jetpack Compose'u bağlayan hedefler R8 ile optimize edilir.

Mümkün olan her yerde kod karartmayı etkinleştirme

Soong'da obfuscate, geçmiş uyumluluk için varsayılan olarak false değerine ayarlanır ancak mümkün olduğunda android_app ve bağımsız DEX hedeflerinde obfuscate: true değerini açıkça ayarlamanız gerekir:

  • Daha küçük DEX ve .vdex ayak izi: Paketleri, sınıfları, alanları ve yöntemleri kısa tanımlayıcılar (a, b) olarak yeniden adlandırmak, DEX dize havuzunu (string_ids ve string_data_item) ve tür tanımlayıcılarını küçültür. Android, .vdex ve .odex dosyalarını işlem belleğiyle eşlediğinden daha küçük sembol tabloları, hem sistem bölümü boyutunu hem de çalışma zamanı bellekte kaplanan yeri doğrudan azaltır.
  • Daha kapsamlı program optimizasyonları: R8'in tanımlayıcıları yeniden adlandırmasına izin vermek, ad çakışmalarını önlemek için R8'in atlaması gereken paketlerde sınıf birleştirme, paket düzleştirme ve sentetik sınıf tekilleştirme işlemlerini etkinleştirir.
  • Tam yığın izleme simgeselleştirmesi: Soong'da kod karartmayı etkinleştirmek, hata ayıklama özelliğini azaltmaz. Soong, R8 ile derlenen her hedef için modülün ara dizininde bir proguard_dictionary eşleme dosyası oluşturur, tüm modül sözlüklerini derlemenin proguard-dict.zip yapısında birleştirir, DEX başlığına bir eşleme karması (--map-id-template) yerleştirir ve retrace'in yığın izlemelerini otomatik olarak sembolize edebilmesi için sınıf SourceFile özelliklerini (--source-file-template) yeniden yazar.

obfuscate: false (veya herkese açık API adlarını açıkça koru) yalnızca şunlar için geçerli olsun:

  • bootclasspath veya system_server sınıf yolundaki paylaşılan kitaplıklar: Çalışma zamanında diğer modüller tarafından dinamik olarak bağlanan harici bir API yüzeyini (ör. framework.jar, services.jar veya <uses-library> hedefleri) gösteren modüller (java_library veya java_sdk_library), obfuscate: false değerini korumalı veya protect_api_surface: true, @KeepForApi ya da -keep kurallarını kullanarak API yüzeylerini açıkça korumalıdır. Böylece, taslaklarına göre derlenen çağrıcılar çalışma zamanında sınıf ve üye adlarını çözümleyebilir. java_sdk_library modüllerinde protect_api_surface: true ayarı, tüm etkin API kapsamlarında (herkese açık, sistem, modül kitaplığı, sistem sunucusu ve test) Metalava ProGuard saklama kurallarını otomatik olarak oluşturur ve uygulama kitaplığına ekler. Güncellenemeyen bootclasspath hedeflerinde (framework-minus-apex, ext ve framework-location) genelleştirilmiş R8 optimizasyonu etkinleştirildiğinde, etkin boot/hiddenapi/*.txt dosyalarında listelenen üyeleri korumak için framework-hiddenapi-proguard-rules (hiddenapi_to_proguard tarafından oluşturulur) de dahil edilir (hiddenapi-max-target-o.txt hariç tutulur).

Bir uygulamada obfuscate: true özelliğini etkinleştirmeden önce aşağıdaki taşıma ön koşullarını doğrulayın:

  • Harici test APK bağımlılıkları: Harici bir android_test APK'sı instrumentation_for: "MyApp" ayarlar ve MyApp'nin dahili sınıflarını veya yöntemlerini doğrudan çağırırsa bu sembollerin yeniden adlandırılması, test çalıştırma zamanında NoSuchMethodError veya NoClassDefFoundError hatasına neden olur. trace_references_from (veya trace_references: true ile allow_traced_references) yapılandırın, testi statik olarak MyApp.impl bağlayan kendi kendini izleyen bir APK olarak yapılandırın veya test kancalarına @VisibleForTesting ile ek açıklama ekleyin (2. Adım: Test odaklı saklama kurallarını taşıyın bölümüne bakın). Böylece, uygulamanın geri kalanı gizlenirken testler için gereken dahili semboller korunur.
  • Dize tabanlı yansıma ve JNI: Bir modül; sınıfları, yöntemleri veya alanları, saklama kuralları ya da ek açıklamalar olmadan dize adlarıyla (Class.forName, getDeclaredMethod veya JNI FindClass ve GetMethodID) ararsa kod karartma bu hedefleri yeniden adlandırır ve çalışma zamanı aramalarını bozar. (Ek açıklama eklenmemiş yansıtmanın shrink: true ve optimize: true altında da başarısız olduğunu unutmayın.) R8'in adlarını koruması ve modülün geri kalanını gizlemesi için bu giriş noktalarına Ek Açıklamaları Saklama Kılavuzu (@UsesReflection, @UsedByReflection veya @UsedByNative) ile ek açıklama ekleyin.

Sıfır özel işaret ilkesine uyun

İdeal Android platform modülünde özel proguard.flags veya keep.xml dosyaları bulunmaz. Android platformu derlemesinde, özel saklama kurallarının büyük çoğunluğu gereksizdir:

  • Platform temelleri ve AAPT2: Çoğu giriş noktası, global platform temelleri (ör. @Keep, JNI native yöntemleri ve @VisibleForTesting) tarafından otomatik olarak korunur veya AAPT2 tarafından AndroidManifest.xml ve düzen kaynaklarından oluşturulur (Varsayılan platform saklama kurallarını anlama bölümüne bakın).
  • Hedeflenen alternatifler: Giriş noktalarının korunması gereken durumlarda, ayrılmış .flags dosyaları yerine bildirim sitesi ek açıklamalarını (keepanno, @VisibleForTesting), dışa aktarılan kitaplık kurallarını (export_proguard_flags_files: true) veya statik test bağlantısını tercih edin (Mevcut saklama kurallarını denetleme ve taşıma bölümüne bakın).

Özel bir proguard.flags veya keep.xml dosyası eklemeden ya da saklamadan önce, kuralın varsayılan referanslar tarafından zaten işlenip işlenmediğini veya kod ek açıklamalarına taşınıp taşınamayacağını doğrulayın.

Varsayılan platform saklama kurallarını anlama

Soong, aşağıdaki global temel kuralları build/soong/java/dex.go içinde yapılandırılmış DEX derleme hedeflerindeki (android_app, android_test ve yüklenebilir java_library modülleri) her R8 çağrısına otomatik olarak iletir:

  • build/make/core/proguard.flags: Tüm paketlerde @com.android.internal.annotations.VisibleForTesting ile ve android.**, com.android.** ve com.google.android.** paketlerinde @VisibleForTesting (androidx.annotation.VisibleForTesting veya com.google.common.annotations.VisibleForTesting) ile ek açıklama eklenmiş sınıfları ve üyeleri korur. Ayrıca @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback ve @dalvik.annotation.optimization.** dillerini de korur.
  • build/make/core/proguard_basic_keeps.flags: Yığın izlemeleri (stack trace) için SourceFile özelliklerini, çalışma zamanı görünürlük ek açıklamalarını (RuntimeVisible*Annotations), Exceptions ve AnnotationDefault özelliklerini, native yöntemlerini, Serializable üyelerini, @JavascriptInterface yöntemlerini, Throwable(String) kurucularını, Parcelable$CREATOR alanlarını ve protobuf MessageLite alanlarını korur.
  • build/make/core/proguard/kotlin.flags: Belirli Kotlin meta ek açıklamaları (kotlin.Metadata ve kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) için zararsız uyarıları sessize alır ve yayınlanan derlemelerdeki Kotlin DebugMetadata ek açıklamalarını kaldırır.
  • build/make/core/proguard/checknotnull.flags: Yaygın null kontrolü yardımcı çağrılarını (com.google.common.base.Preconditions.checkNotNull ve dagger.internal.Preconditions.checkNotNull*) kısa bayt kodu null kontrolleriyle değiştirir. Bu dosya, çerçeve API sınırları genelinde açık istisna mesajlarını korumak için kasıtlı olarak Objects.requireNonNull öğesini atlar.
  • build/make/core/proguard/enumvalues.flags: Derleme yapılandırması tarafından devre dışı bırakılmadığı sürece enum türlerinde values ve valueOf yöntemlerini korur.
  • AAPT2 tarafından otomatik olarak oluşturulan kurallar: AAPT2, kayıtlı her Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, özel View alt sınıfı, Preference alt sınıfı ve XML android:onClick yöntemi için tam saklama kuralları oluşturmak üzere birleştirilmiş AndroidManifest.xml, düzen XML ve tercih XML dosyalarını inceler.

Mevcut saklama kurallarını denetleme ve taşıma

Bir platform deposundaki mevcut proguard.flags veya keep.xml dosyalarını denetlerken her kuralı aşağıdaki dört adımlı hiyerarşiye göre değerlendirin:

1. adım: Gereksiz veya eski kuralları silin

Genel temel çizgiler, AAPT2 manifest ve düzen kuralları veya R8 varsayılanları tarafından zaten kapsanan kuralları ve artık mevcut olmayan sınıflara veya paketlere referans veren kuralları silin.

Bir .flags dosyasındaki tüm kurallar gereksizse dosyayı silin ve Android.bp dosyasından proguard_flags_files satırını kaldırın. Kalan optimize bloğu yalnızca varsayılan ayarları yeniden belirtiyorsa gereksiz bloğu kaldırın ve derleme dosyasını bpfmt -w Android.bp ile biçimlendirin. Bir paketi temizlerken alt dizinlerdeki tamamlayıcı modüllerin (ör. Kotlin hedef varyantları) aynı işaret dosyasına referans verip vermediğini kontrol edin ve bunları birlikte güncelleyin.

2. adım: Test odaklı saklama kurallarını taşıyın

Test odaklı saklama kurallarını, aşağıdaki kalıplardan birini kullanarak özel üretim .flags dosyalarından çıkarın:

  • Test veya tamamlayıcı hedeflerden (trace_references_from veya allow_traced_references) referansları izleme: Hem hedef hem de doğrudan bağımlı bir modül kendi DEX'lerini (android_app, android_test veya dexed java_library) derlediğinde ve instrumentation_for ya da libs aracılığıyla bağlandığında Soong, bağımlı modülde R8 tracereferences çalıştırabilir ve oluşturulan saklama kurallarını, bağımlılık döngüsü oluşturmadan veya manuel .impl kitaplık bölme işlemi gerektirmeden hedefin R8 derlemesine besleyebilir:

    • Aynı depodaki testler (trace_references_from): Hedef modüle doğrudan instrumentation_for veya libs bağımlı olanları listeleyin (Bu, override_android_app ve override_android_test varyantları arasında da otomatik olarak yayılır ve .impl bölmesiyle eşlendiğinde bağımlı olmayan tamamlayıcı Java kitaplıklarını desteklemeye devam eder):

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              obfuscate: true,
              shrink_resources: true,
              trace_references_from: ["MyAppTests"],
          },
      }
      
      android_test {
          name: "MyAppTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
      }
      
    • Depolar arası veya tedarikçi testleri (allow_traced_references ve trace_references): Hedef modül, bağımlı modülü doğrudan trace_references_from içinde listeleyemediğinde (başka bir depodaki veya tedarikçi ağacındaki testler gibi) hedef modülde allow_traced_references: true değerini ayarlayın ve trace_references: true ile her bağımlı modülden etkinleştirin:

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              allow_traced_references: true,
          },
      }
      
      android_test {
          name: "MyAppVendorTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
          optimize: {
              trace_references: true,
          },
      }
      
    • Testlerde dize tabanlı yansıma: tracereferences, bayt kodu sabit havuz referanslarını inceler ve testlerde dize tabanlı yansımayı (Class.forName, getDeclaredField, getDeclaredMethod veya yansımalı alan yerleştirme yardımcıları) algılayamaz. Yansıtmalı olarak erişilen üretim alanlarına ve yöntemlerine @VisibleForTesting (veya @UsedByReflection) ile ek açıklama ekleyin.

  • Uygulama kitaplığını testlere bağlama (static_libs): Üretim APK'sında yalnızca test amaçlı giriş noktası kalmamasını ve test değişikliklerinin üretim hedefinde R8'i yeniden tetiklememesini istiyorsanız uygulama kaynak dosyalarını bir android_library (MyApp.impl) içine yerleştirin ve MyApp.impl öğesini hem MyApp hem de MyAppTests öğesine statik olarak bağlayın:

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    Bu 3 modüllü kalıp, MyAppTests modülünün dahili sınıflara tam erişimle kendi kendini izleyen bir enstrümantasyon testi olarak çalışmasına olanak tanır. Aynı zamanda MyApp modülünün yalnızca canlı sürüm giriş noktalarına göre küçültülüp kodunun karartılmasına izin verir.

  • Test kancalarına @VisibleForTesting ile ek açıklama ekleyin: Harici bir test APK'sı, bir android_app hedefinde yalnızca birkaç dahili yöntem veya kurucu çağırıyorsa bu bildirimlere @VisibleForTesting ile ek açıklama ekleyebilirsiniz. Genel build/make/core/proguard.flags temel çizgisi, özel .flags dosyaları olmadan android.**, com.android.** ve com.google.android.** paketlerindeki @VisibleForTesting öğeyi otomatik olarak saklar. (Bu ad alanlarının dışındaki tedarikçi modülleri için trace_references_from, @UsedByReflection veya dışa aktarılan kitaplık kurallarını kullanın.) trace_references_from veya trace_references yerine @VisibleForTesting ile manuel olarak ek açıklama eklerken Mockito mock(...) veya spy(...)'ya iletilen sınıflara da ek açıklama ekleyin (yalnızca sarmalanmış yöntemlere değil). Bunun nedeni, cihaz üzerinde mockito-target (dexmaker-mockito) çalışma zamanı alt sınıfları oluşturması ve R8'in alt sınıfı olmayan sınıfları final (MockitoException: Can't mock/spy class ... - final class) olarak işaretleyebilmesidir.

  • Çakışan statik kitaplıklardan kaçının veya bunları yeniden paketleyin (jarjar_rules): Hem android_app hem de instrumentation_for test APK'sı aynı kitaplığı statik olarak bağladığında (doğrudan, örneğin guava veya geçişli olarak), tracereferences testte tanımlanan bu sınıfları görür ve uygulama için saklama kuralı yayınlamaz. Çalışma zamanında ise uygulamanın üst sınıf yükleyicisi, R8 ile küçültülmüş sınıflarıyla testin kopyasını gölgeler ve IllegalAccessError veya NoSuchMethodError hatasına neden olur. Testin kendi paketlenmiş kopyasına ihtiyacı yoksa çakışmayı önlemek için kopyayı testin static_libs öğesinden kaldırın (veya libs aracılığıyla bağlayın). Aksi takdirde, uygulamanın çakışan statik kitaplık paketlerini jarjar_rules kullanarak modüle özgü bir ön ek altında yeniden paketleyin.

3. adım: Kuralları, sahibi olan kitaplıktan dışa aktarın

Paylaşılan bir java_library veya android_library, kendi dahili yansıma ya da JNI geri çağırmaları için saklama kuralları gerektiriyorsa kuralları kitaplık hedefinde tanımlayın ve export_proguard_flags_files: true değerini ayarlayın:

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

my-shared-library öğesini statik olarak bağlayan tüm alt android_app ve kitaplık hedefleri, bu kuralları otomatik olarak devralır. Bu nedenle, alt uygulamaların bu kuralları kopyalaması gerekmez. Farklı dizinlerdeki birden fazla modülün tek bir kod kitaplığına bağlı olmayan bir kural kümesini paylaşması gerektiğinde, modüllerin paket sınırları genelinde :my-shared-flags'e temiz bir şekilde referans verebilmesi için .flags dosyasını Android.bp içinde açık bir filegroup aracılığıyla yayınlayın. Alt uygulama optimizasyonunu kısıtlamadan kitaplık tüketici saklama kuralları yazma hakkında genel bilgi için Kitaplık yazarları için optimizasyon başlıklı makaleyi inceleyin.

4. adım: Kaynak kodundaki bildirimlere ek açıklama ekleme

Bir sınıfa, yönteme, kurucuya veya alana yansıma ya da JNI aracılığıyla erişildiğinde ve AAPT2 veya genel referanslar tarafından kapsanmadığında, .flags dosyalarındaki ayrılmış -keep kurallarını Keep Ek Açıklamaları Kılavuzu'ndaki kaynak ek açıklamalarıyla değiştirin (keepanno Javadoc referansına bakın):

  • @UsesReflection: Yansımayı gerçekleştiren kodu kontrol ederken bu ek açıklamayı tercih edin. Dinamik olarak erişilen hedef sınıfları, yöntemleri veya alanları bildirmek için bu ek açıklamayı yansıma çağrısı sitesine yerleştirin. @UsesReflection, ek açıklamalı çağrı sitesinin erişilebilir olduğuna dair bir ön koşulu otomatik olarak kodladığından, çağrı sitesi kullanılmıyorsa R8 hem arayanı hem de yansıtıcı hedefi budar.
  • @UsedByReflection: Harici kod veya kitaplıklar tarafından yansıtmalı olarak örneklendirilen ya da çağrılan sınıflara, yöntemlere, alanlara veya oluşturuculara yerleştirin. Örneğin, Bundle veya Settings anahtarlarından Class.forName ile yüklenen sınıflar ya da dinamik sınıf yükleyiciler üzerinden yüklenen eklenti sınıfları. R8'in yalnızca tam yansıtıcı sözleşmeyi tutması ve sınıfın kullanılmayan üyelerini optimize etmeye veya ayıklamaya devam edebilmesi için kind (ör. KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition) ve parametre kısıtlamalarını belirtin. AAPT2, JobService veya BroadcastReceiver gibi manifest dosyasına kayıtlı bileşenleri zaten sakladığı için bunlara @UsedByReflection eklemeyin.
  • @UsedByNative: GetMethodID, GetStaticMethodID veya GetFieldID kullanılarak C ya da C++ JNI kodundan erişilen yöntemlere veya alanlara yerleştirin.
  • @KeepForApi: Kitaplık API sınıflarına veya üyelerine yerleştirilir. Kitaplık dağıtımdan önce küçültüldüğünde bu sınıfların veya üyelerin bozulmadan kalması gerekir.
  • @Keep (androidx.annotation.Keep): keepanno geçerli olmadığında yedek olarak kullanın. Bir sınıfa @Keep uygulandığında sınıf ve tüm üyeleri koşulsuz olarak saklanır. Bir yönteme veya alana @Keep uygulamak, sınıf hiçbir zaman örneklendirilmese bile üyeyi ve onu içeren sınıfı koruyan koşulsuz bir giriş noktası görevi görür. keepanno ise koşullu erişilebilirliği ifade eder.

Soong modülünde R8 keepanno ek açıklamalarını kullanmak için:

  1. Android.bp içinde libs adlı gruba "keepanno-annotations" adlı kullanıcıyı ekleyin:

    libs: [
        "keepanno-annotations",
    ],
    
  2. com.android.tools.r8.keepanno.annotations.* öğesini içe aktarın ve Java veya Kotlin kaynak kodunda bildirime ya da çağrı sitesine ek açıklama ekleyin:

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8, keepanno ek açıklamalarını doğrudan kendi dahili saklama kuralı modeline çevirir ve ek açıklamaları nihai DEX çıkışından çıkararak çalışma zamanı bayt kodu ek yükünü sıfıra indirir.

Yaygın hatalardan kaçınma

Aşağıdaki bölümlerde, platform kodunda sık karşılaşılan proguard.flags ve keep.xml sorunları ve bunların nasıl düzeltileceği açıklanmaktadır.

Geniş bileşen saklama kuralları

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • Performansı neden olumsuz etkiler? Bu kural, AAPT2 ile tamamen gereksizdir. Bileşenin nihai birleştirilmiş AndroidManifest.xml içinde kayıtlı olup olmadığını kontrol etmeden joker karakter kullandığı için R8'i, kullanılmayan kitaplık bileşenleri ve devre dışı bırakılmış hata ayıklama etkinlikleri de dahil olmak üzere sınıf yolunun herhangi bir yerinde bulunan her Activity, Service veya BroadcastReceiver alt sınıfını korumaya zorlar.
  • Önerilen düzeltme: Kuralı silin. AAPT2, birleştirilmiş manifesti inceler ve kayıtlı bileşenler için tam -keep kuralları oluşturur.

Geniş XML tıklama işleyici saklama kuralları

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Performansı neden olumsuz etkiler? Modüldeki her sınıfta her public void *(View) yöntemini korumak, R8'in tek bir View parametresi kabul eden yöntemleri kaldırmasını veya satır içine almasını engeller.
  • Önerilen düzeltme: Kuralı silin. AAPT2, düzen XML dosyalarını tarar ve android:onClick özellikleri tarafından referans verilen yöntemler için hedeflenen saklama kuralları oluşturur.

Broad View alıcı ve ayarlayıcı saklama kuralları

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • Performansı olumsuz etkileme nedeni: Bu kural, R8'i uygulamadaki her View alt sınıfında ve tüm bağlı kitaplıklarda (AndroidX ve Material kitaplıkları dahil) her alıcı ve ayarlayıcıyı korumaya zorlar. Bu da ölü yöntem kaldırma ve kullanıcı arayüzü kodunda satır içi yerleştirmeyi engeller.
  • Önerilen düzeltme: Kuralı silin. AAPT2, düzen XML dosyalarından şişirilen özel View sınıfları için yapıcıları zaten korur. Kod, ObjectAnimator.ofFloat(view, "translationZ", ...) gibi yansıtıcı dize adları kullanarak bir görünüm özelliğine animasyon uyguluyorsa yansımayı tamamen önlemek için dize adını yazılı bir özellik referansıyla (View.TRANSLATION_Z veya özel bir FloatProperty ya da IntProperty uygulaması) değiştirin. Yansıtmalı özellik erişiminden kaçınılamıyorsa belirli alıcı veya ayarlayıcıya @UsedByReflection ile ek açıklama ekleyin.

Genel optimizasyon veya gizleme devre dışı bırakma

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Performansı neden olumsuz etkiler? .flags dosyasına -dontoptimize veya -dontshrink eklemek, modülün Android.bp ayarlarını sessizce geçersiz kılar ve tüm hedef genelinde optimizasyon geçişlerini devre dışı bırakır. Daha da kötüsü, bir kitaplık -dontoptimize içeren bir .flags dosyası dışa aktarırsa kitaplığı bağlayan her alt android_app için R8 optimizasyonunu devre dışı bırakır.
  • Önerilen düzeltme: .flags dosyadan -dontoptimize, -dontshrink ve -dontobfuscate erişimini kaldırın. Android.bp içinde optimizasyon davranışını açıkça kontrol etmek için yaprak hedefte optimize bloğunu (shrink, optimize, obfuscate) kullanın.

Onaylanmış kurallardaki teşhis işaretleri

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • Derlemeyi neden olumsuz etkiler? Teşhis işaretleri, korumalı alana alınmış Soong derlemeleri sırasında derleme günlüklerini doldurur veya çıkış dosyalarını yerel yollara yazmaya çalışır.
  • Önerilen düzeltme: Bu işaretleri, kaydedilen .flags dosyalardan silin. Soong, proguard_dictionary ve proguard_usage.zip gibi R8 eşleme ve kullanım çıktılarını out/soong/.intermediates/ altındaki modülün ara dizinine otomatik olarak yazar.

Test kodu için üretimde saklama kuralları

  • Performansı neden olumsuz etkiler? Sırf test erişimi için özel -keep kuralları eklemek, bu kodun gizlenmeden kalmasına ve üretim derlemelerinde tutulmasına neden olur. trace_references_from (veya trace_references: true ile allow_traced_references) yapılandırılırken ya da yöntemlere @VisibleForTesting ile ek açıklama eklenirken manuel -keep kurallarının sürdürülmesinden kaçınılır ve yalnızca testlerin referans verdiği öğeler korunur. Ancak bu semboller, üretim ikili programında gönderilmeye devam eder.
  • Önerilen düzeltme: trace_references_from (veya allow_traced_references ve trace_references: true) seçeneğini etkinleştirerek özel test -keep kurallarını kaldırın ya da uygulamayı, 2. adım: Test odaklı saklama kurallarını taşıma bölümünde açıklandığı gibi, yalnızca test amaçlı giriş noktalarını üretim APK'sının tamamen dışında tutmak için static_libs kullanarak kendi kendini izleyen bir test APK'sına bağlı bir .impl kitaplığı halinde yapılandırın.

keep.xml dosyasında geniş kapsamlı kaynak joker karakterleri

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • Performansı neden olumsuz etkiler? Genel joker karakterler, modül ve bağımlılıkları genelinde eşleşen türdeki her kaynağı korur. Bu durum, kaynak küçültmeyi (shrink_resources: true) engeller ve APK ile eşlenen resources.arsc tablosunu şişirir.
  • Önerilen düzeltme:
    • keep.xml yerine statik kaynak referanslarını tercih edin:Numaralandırılmış deneme dizeleri veya temalı çizilebilir öğeler gibi sınırlı bir kaynak grubunu dinamik olarak aramak için Resources.getIdentifier yöntemini kullanmaktan kaçının. Bunun yerine, derleme zamanı switch ifadesi kullanın veya statik R.id, R.string ya da R.drawable sabitleri üzerinde eşleme yapın. Statik referanslar, R8 ve AAPT2'nin tam olarak canlı kaynakları izlemesine, çalışma zamanı dize arama ek yükünü ortadan kaldırmasına ve keep.xml ihtiyacını tamamen ortadan kaldırmasına olanak tanır.
    • Belirli kaynak adlarını listeleyin: Üçüncü taraf lisans okuyucu gibi dinamik arama gerekiyorsa tam kaynak tanımlayıcılarını tools:keep içinde listeleyin (örneğin, tools:keep="@raw/third_party_licenses").
    • Gereksiz keep.xml dosyalarını silin: Listelenen kaynaklara kodda (R.raw.foo) veya XML'de (@raw/foo) statik olarak referans veriliyorsa küçültücü bunları otomatik olarak saklar. Bu nedenle res/raw/keep.xml dosyasını silebilirsiniz.

Kural değişikliklerini doğrulama ve denetleme

proguard.flags veya keep.xml dosyasındaki saklama kurallarını kaldırdığınızda ya da daralttığınızda modülün sorunsuz bir şekilde derlendiğini, birim ve enstrümantasyon testlerini geçtiğini ve gerekli tüm giriş noktalarını koruduğunu doğrulayın.

Modül oluşturma ve test etme

  1. R8 ve AAPT2'nin eksik referans uyarıları olmadan tamamlandığını doğrulamak için modülü temiz bir şekilde derleyin:

    m <MODULE_NAME>
    
  2. atest kullanarak modül için birim ve enstrümantasyon testleri çalıştırın:

    atest <TEST_MODULE_NAME>
    
  3. Sistem uygulamaları, ayrıcalıklı hizmetler veya donanıma bağlı modüller için hedef fiziksel cihazlarda ya da cihaz test laboratuvarınızda araç ve kullanıcı arayüzü testleri çalıştırarak çalışma zamanı yansıma yollarını, IPC bağlamalarını ve kaynak şişirmeyi test edin.

DEX ve kaynak farklarını inceleme

R8'in beklenen giriş noktalarını kaldırmadan ölü kodları ve kaynakları kaldırdığını doğrulamak için kural değişikliklerinden önce ve sonra derlenen APK veya JAR'ı karşılaştırın:

  • Çıkış APK veya DEX dosyalarında tutulan sınıfları, yöntemleri ve alanları incelemek için apkanalyzer veya dexdump kullanın:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • keep.xml politikasını değiştirirken veya shrink_resources politikasını etkinleştirirken derlemenin kullanılmayan kaynakları kaldırdığını ve gerekli kaynakları koruduğunu doğrulamak için aapt2 dump resources aracını kullanın:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Tam bir platform derlemesi olmadan işaret değişikliklerini çevrimdışı doğrulamak için bir modülün R8 öncesi classes.jar dosyasında doğrudan prebuilts/r8/r8.jar çalıştırırken hem modülün --lib sınıf yolu JAR'lerini (android.jar, framework.jar ve paylaşılan libs) hem de AAPT2'nin AndroidManifest.xml ve düzen XML'lerinden oluşturulan proguard.options kurallarını iletin. Bu girişler olmadan R8, çerçeve üst türlerini (Activity, Service, Phone, Handler) çözemez ve hiyerarşi yöntemlerini eksik tutar.

R8 ile saklama yarıçapını ve kural kapsamını analiz etme

Açık kaynak R8 derleyicisi, derleme sırasında her saklama kuralının tam etkisini ölçen bir Keep Radius analiz aracı (Android Studio ve Gradle'da R8 Yapılandırma Analiz Aracı olarak da gösterilir) içerir. Soong, bu analiz aracını doğrudan Android platformu derlemesine entegre eder (build/soong/java/dex.go içinde yapılandırılır).

Bir platform modülü veya derlemenin tamamı için saklama kurallarını analiz etmek istiyorsanız:

  1. Derlemeyi R8_DUMP_KEEP_RADIUS=true ortam değişkeniyle çalıştırın:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    R8_DUMP_KEEP_RADIUS=true ayarlandığında Soong, R8'e out/soong/.intermediates/ altındaki her derlenmiş modül için saklama kuralı metriklerini ara bir r8keepradius.pb dosyasına kaydetmesini söyler. R8, her saklama kuralı ve keepanno ek açıklaması için şunları kaydeder:

    • Doğrudan koruma kapsamı: Kural tarafından korunan sınıflar, alanlar ve yöntemler ile küçültme, optimizasyon veya kod karartmaya karşı uyguladığı belirli kısıtlamalar.
    • Kural kapsamı: Aynı öğeleri zaten saklayan diğer saklama kuralları veya ek açıklamalar. Özel bir kural, AAPT2 kuralı veya genel temel kural tarafından tamamen kapsanıyorsa bu kuralı güvenle silebilirsiniz.
    • Paket genelinde ve global kurallar: Paket genelinde geniş joker karakterler kullanan veya global yapılandırma yönergeleri uygulayan kurallar.
  2. KeepRadiusHtmlReportGenerator (prebuilts/r8/r8.jar içinde paketlenmiş) kullanarak r8keepradius.pb çıkışını etkileşimli bir HTML raporuna dönüştürün:

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    Bir dizin verildiğinde KeepRadiusHtmlReportGenerator tüm *keepradius*.pb dosyalarını tarar, her modül için bir HTML raporu oluşturur ve derleme genelinde etkin öğe sayılarını, saklanan öğe sayılarını ve en yüksek yarıçaplı saklama kurallarını özetleyen out/keep_radius_reports/keepradius.html dosyasını oluşturur. Oluşturulan rapordaki küçültme, optimizasyon ve kod karartma puanlarını yorumlama hakkında daha fazla bilgi için R8 Yapılandırma Analiz Aracı'nı kullanma başlıklı makaleyi inceleyin.

Soong, build/soong/java/dex.go içinde ek R8 teşhis ve iç döngü ortam değişkenlerini de destekler:

  • R8_DUMP_INPUT=true: Bağımsız R8 yeniden üretimi için tüm giriş JAR'larını, kitaplık JAR'larını ve birleştirilmiş ProGuard yapılandırmalarını içeren modülün ara dizinine r8inputs.zip yazar.
  • R8_DUMP_PERFETTO_TRACE=true: Perfetto'da R8 derleme geçişlerini incelemek için modülün ara dizinine r8trace.ptrace yazar.
  • SOONG_SKIP_TRACE_REFERENCES=true: Test kodunda iterasyon yaparken yerel iç döngü derlemelerini hızlandırmak için trace_references_from veya trace_references tarafından yapılandırılan tracereferences kurallarının çalıştırılmasını atlar.

Kitaplık tüketici kurallarını doğrulama

Bir java_library veya android_library, kuralları alt tüketicilere (export_proguard_flags_files: true) aktardığında bu kurallar, tüketen uygulama için optimizasyonu değiştiren veya devre dışı bırakan genel işaretler içermemelidir.

R8, Android platformu derlemesinde process-keep-rules ana makine aracı olarak sunulan (prebuilts/r8/Android.bp içinde tanımlanır) açık kaynaklı bir saklama kuralı analiz aracı (ProcessKeepRules) sağlar:

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

process-keep-rules aracı, her yapılandırma dosyasını ayrıştırır ve kitaplık tüketici kurallarında izin verilmeyen yönergelerle karşılaşırsa dosya ve satır teşhisiyle başarısız olur. İzin verilmeyen yönergeler arasında genel optimizasyon, küçültme veya gizleme devre dışı bırakma (ör. -dontoptimize veya -dontshrink), paketi yeniden paketleme ve erişim değiştirme işaretleri, teşhis veya eşleme işaretleri ve uygulama düzeyinde -keepattributes yer alır.

pgaudit.py ile kaynak ağaçlarını denetleme

R8'in derleme zamanı analiz araçlarının yanı sıra derlenmemiş kaynak dizinlerini denetlemek için Android platform ağacı, build/make/core/proguard/tools/ altında pgaudit.py komut dosyasını içerir. Bu statik analiz aracı, bir depodaki Android.bp, proguard.flags ve keep.xml dosyalarını tarayarak AAPT2 veya global platform temelleri, geniş joker karakterler, -dontoptimize geçersiz kılmaları ve yalnızca test amaçlı saklama kuralları tarafından zaten kapsanan kuralları işaretler:

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

Platform ve OEM ekipleri, kaynak ağacını hızlıca önceliklendirmek için pgaudit.py, dışa aktarılan kitaplık kurallarını doğrulamak için process-keep-rules ve bir derlemedeki her kuralın tam sınıf, alan ve yöntem saklama yarıçapını ölçmek için R8_DUMP_KEEP_RADIUS=true ile KeepRadiusHtmlReportGenerator komutlarını birleştirebilir.