Mengoptimalkan kode dan resource platform dengan R8

Sistem build platform Android (Soong) menjalankan compiler R8 pada target yang mengompilasi bytecode DEX, termasuk aplikasi Android (android_app), pengujian (android_test), dan library Java yang dapat diinstal dengan compile_dex: true (seperti services.jar), untuk menyingkat, mengoptimalkan, dan menghapus kode dan resource yang tidak digunakan. Pada library statis (android_library dan java_library statis), Soong tidak menjalankan R8 secara langsung, tetapi menggunakan blok properti optimize untuk melampirkan dan menerapkan aturan keep konsumen (export_proguard_flags_files: true) ke target DEX downstream yang menautkannya secara statis.

Bagi engineer platform yang membangun image sistem atau paket vendor, menghilangkan aturan keep yang terlalu luas akan memberikan manfaat langsung bagi kesehatan sistem:

  • Jejak partisi sistem yang lebih kecil: R8 menghapus class, metode, dan resource yang tidak aktif sebelum mengemas APK dan JAR ke /system, /system_ext, /product, dan /vendor.
  • Artefak yang dikompilasi lebih kecil: Metode DEX yang lebih sedikit berarti file .odex dan .vdex yang dihasilkan oleh dex2oat selama waktu build atau kompilasi di perangkat akan lebih kecil.
  • Tekanan memori runtime yang lebih rendah: Karena Android memetakan .odex, .vdex, dan tabel resource APK ke dalam memori proses menggunakan panggilan mmap dengan halaman sesuai permintaan, biner yang lebih kecil mengurangi kesalahan halaman utama selama startup aplikasi dan menurunkan memori kode dan resource residen di seluruh proses. Untuk mengetahui informasi selengkapnya tentang pengaruh kode aplikasi yang dikompilasi terhadap memori perangkat, lihat Kode aplikasi adalah memori.
  • Pengoptimalan seluruh program yang lebih efektif: Batasan keep yang sempit memungkinkan R8 menyisipkan metode, melakukan devirtualisasi antarmuka dan panggilan virtual, memangkas kolom yang tidak digunakan, dan menyebarkan konstanta di seluruh class.

Mengonfigurasi pengoptimalan R8 di Soong

Dalam file Android.bp, konfigurasikan pengoptimalan R8 menggunakan blok properti optimize pada target android_app dan java_library yang dapat diinstal (atau pada modul android_library dan java_library statis untuk mengekspor aturan keep konsumen):

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

Tabel berikut merangkum properti optimize yang paling umum di Soong (ditentukan dalam build/soong/java/dex.go):

Properti Deskripsi
enabled Mengontrol apakah R8 berjalan pada target. Default-nya adalah true untuk semua target android_app.
shrink Mengontrol tree shaking untuk menghapus class, kolom, dan metode yang tidak dapat dijangkau. Secara default ditetapkan ke true untuk target android_app (false untuk java_library mandiri dan modul pengujian, yang meneruskan -dontshrink kecuali jika ditetapkan secara eksplisit).
optimize Mengontrol pengoptimalan bytecode seperti inlining metode, penggabungan class, penyebaran konstanta, dan penghapusan cabang mati. Nilai defaultnya adalah true untuk target android_app (dikontrol oleh flag build rilis RELEASE_R8_OPTIMIZE_BY_DEFAULT).
obfuscate Mengontrol minifikasi dan penggantian nama ID. Defaultnya adalah false untuk kompatibilitas historis, tetapi tetapkan ke true jika memungkinkan untuk mengurangi ukuran DEX dan membuka pengoptimalan yang lebih mendalam (lihat Aktifkan obfuscation jika memungkinkan).
shrink_resources Menghapus resource yang tidak digunakan (entri res/) dari APK yang dipaketkan setelah penyingkatan kode. Nilai defaultnya adalah false.
optimized_shrink_resources Menjalankan pipeline penyingkat kode dan resource R8 terintegrasi sehingga R8 melacak referensi kode dan resource secara bersamaan dalam satu penerusan. Jika shrink_resources: true ditetapkan, setelan defaultnya adalah RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true dalam build platform standar).
proguard_flags_files Mencantumkan file .flags atau .pro khusus modul yang berisi aturan keep kustom. Hindari menambahkan file kustom jika anotasi standar atau default sudah cukup.
export_proguard_flags_files Menyebarkan proguard_flags_files library ini ke modul downstream yang bergantung padanya secara statis.
trace_references_from Mencantumkan target dependen kompilasi DEX langsung (terhubung melalui instrumentation_for atau libs) yang referensi bytecode-nya ke target ini akan otomatis dilacak dan dipertahankan oleh R8 (juga disebarkan di seluruh varian override_android_app dan override_android_test, serta didukung untuk library Java pendamping non-dependen jika dipasangkan dengan pemisahan .impl).
allow_traced_references Memungkinkan target dependen kompilasi DEX langsung (terhubung melalui instrumentation_for atau libs, seperti pengujian lintas repositori atau vendor yang tidak dapat dicantumkan dalam trace_references_from) untuk memilih ikut serta dalam melacak referensi bytecode-nya ke target ini dengan menetapkan trace_references: true.
trace_references Jika ditetapkan ke true pada dependensi kompilasi DEX langsung dari target dengan allow_traced_references: true, menjalankan R8 tracereferences pada modul ini dan meneruskan aturan keep yang dihasilkan ke modul target.
protect_api_surface Meneruskan --protect-api-surface ke R8. Jika ditetapkan pada java_sdk_library, juga otomatis membuat dan melampirkan aturan keep Metalava API di semua cakupan API yang diaktifkan (publik, sistem, module-lib, server sistem, dan pengujian) ke library implementasi.

Library statis (android_library, java_library, dan impor AAR) juga dapat menetapkan required_downstream_optimization: { enabled: true, optimize: true } di Android.bp untuk mewajibkan target kompilasi DEX downstream yang menautkannya secara statis (langsung atau transitif) untuk mengaktifkan penyingkatan R8 dan pengoptimalan bytecode. Soong mengaktifkan ini secara otomatis di androidx.compose.runtime_runtime sehingga target yang menautkan Jetpack Compose dioptimalkan dengan R8.

Mengaktifkan obfuscation jika memungkinkan

Di Soong, obfuscate ditetapkan secara default ke false untuk kompatibilitas historis, tetapi Anda harus secara eksplisit menetapkan obfuscate: true pada android_app dan target DEX mandiri jika memungkinkan:

  • Jejak DEX dan .vdex yang lebih kecil: Mengganti nama paket, class, kolom, dan metode menjadi ID singkat (a, b) akan memperkecil string pool DEX (string_ids dan string_data_item) dan deskriptor jenis. Karena Android memetakan file .vdex dan .odex ke dalam memori proses, tabel simbol yang lebih kecil secara langsung mengurangi ukuran partisi sistem dan jejak memori runtime.
  • Pengoptimalan seluruh program yang lebih mendalam: Membiarkan R8 mengganti nama ID akan memungkinkan penggabungan class, perataan paket, dan penghapusan duplikat class sintetis di seluruh paket yang harus dilewati R8 untuk menghindari konflik nama.
  • Simbolisasi pelacakan tumpukan penuh: Mengaktifkan obfuscation di Soong tidak mengurangi kemampuan debug. Untuk setiap target yang dikompilasi R8, Soong memunculkan file pemetaan proguard_dictionary di direktori perantara modul, menggabungkan semua kamus modul ke dalam artefak proguard-dict.zip build, menyematkan hash pemetaan (--map-id-template) ke dalam header DEX, dan menulis ulang atribut SourceFile class (--source-file-template) sehingga retrace dapat menyimbolkan pelacakan tumpukan secara otomatis.

Pertahankan obfuscate: false (atau pertahankan nama API publik secara eksplisit) hanya untuk:

  • Library bersama di bootclasspath atau classpath system_server: Modul (java_library atau java_sdk_library) yang mengekspos platform API eksternal yang ditautkan secara dinamis oleh modul lain pada waktu proses (seperti target framework.jar, services.jar, atau <uses-library>) harus mempertahankan obfuscate: false atau secara eksplisit mempertahankan platform API-nya menggunakan aturan protect_api_surface: true, @KeepForApi, atau -keep sehingga pemanggil yang dikompilasi terhadap stub-nya dapat me-resolve nama class dan anggota pada waktu proses. Pada modul java_sdk_library, menyetel protect_api_surface: true akan otomatis membuat dan melampirkan aturan keep ProGuard Metalava di semua cakupan API yang diaktifkan (publik, sistem, module-lib, system-server, dan pengujian) ke library implementasi. Jika pengoptimalan R8 umum diaktifkan pada target bootclasspath yang tidak dapat diupdate (framework-minus-apex, ext, dan framework-location), target tersebut juga menyertakan framework-hiddenapi-proguard-rules (yang dihasilkan oleh hiddenapi_to_proguard) untuk mempertahankan anggota yang tercantum dalam file boot/hiddenapi/*.txt aktif (menghilangkan hiddenapi-max-target-o.txt).

Sebelum mengaktifkan obfuscate: true pada aplikasi, verifikasi prasyarat migrasi berikut:

  • Dependensi APK pengujian eksternal: Jika APK android_test eksternal menetapkan instrumentation_for: "MyApp" dan langsung memanggil class atau metode internal MyApp, penggantian nama simbol tersebut akan menyebabkan NoSuchMethodError atau NoClassDefFoundError pada waktu pengujian. Konfigurasikan trace_references_from (atau allow_traced_references dengan trace_references: true), susun pengujian sebagai APK self-instrumenting yang menautkan MyApp.impl secara statis, atau beri anotasi pada hook pengujian dengan @VisibleForTesting (lihat Langkah 2: Migrasikan aturan keep berbasis pengujian) sehingga simbol internal yang diperlukan oleh pengujian dipertahankan saat meng-obfuscate bagian aplikasi lainnya.
  • Refleksi berbasis string dan JNI: Jika modul mencari class, metode, atau kolom berdasarkan nama string literal (Class.forName, getDeclaredMethod, atau JNI FindClass dan GetMethodID) tanpa aturan keep atau anotasi, obfuscation akan mengganti nama target tersebut dan merusak pencarian runtime. (Perhatikan bahwa refleksi yang tidak dianotasi juga gagal di shrink: true dan optimize: true.) Beri anotasi pada titik entri tersebut dengan Panduan untuk Mempertahankan Anotasi (@UsesReflection, @UsedByReflection, atau @UsedByNative) sehingga R8 mempertahankan namanya dan meng-obfuscate sisa modul.

Mengikuti prinsip tanpa flag kustom

Modul platform Android yang ideal tidak memiliki file proguard.flags atau keep.xml kustom. Dalam build platform Android, sebagian besar aturan keep kustom bersifat redundan:

  • Baseline platform dan AAPT2: Sebagian besar titik entri sudah dipertahankan secara otomatis oleh baseline platform global (seperti @Keep, metode JNI native, dan @VisibleForTesting) atau dibuat oleh AAPT2 dari AndroidManifest.xml dan resource tata letak (lihat Memahami aturan keep platform default).
  • Alternatif yang ditargetkan: Jika titik entri harus dipertahankan, pilih anotasi situs deklarasi (keepanno, @VisibleForTesting), aturan library yang diekspor (export_proguard_flags_files: true), atau penautan pengujian statis daripada file .flags yang terpisah (lihat Mengaudit dan memigrasikan aturan keep yang ada).

Sebelum menambahkan atau mempertahankan file proguard.flags atau keep.xml kustom, pastikan apakah aturan tersebut sudah ditangani oleh baseline default atau dapat dimigrasikan ke anotasi kode.

Memahami aturan penyimpanan platform default

Soong otomatis meneruskan aturan dasar pengukuran global berikut ke setiap pemanggilan R8 pada target kompilasi DEX (android_app, android_test, dan modul java_library yang dapat diinstal), yang dikonfigurasi di build/soong/java/dex.go:

  • build/make/core/proguard.flags: Mempertahankan class dan anggota yang dianotasi dengan @com.android.internal.annotations.VisibleForTesting di semua paket, dan dengan @VisibleForTesting (androidx.annotation.VisibleForTesting atau com.google.common.annotations.VisibleForTesting) dalam paket android.**, com.android.**, dan com.google.android.**. Juga mempertahankan @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback, dan @dalvik.annotation.optimization.**.
  • build/make/core/proguard_basic_keeps.flags: Mempertahankan atribut SourceFile untuk stack trace, anotasi visibilitas runtime (RuntimeVisible*Annotations), atribut Exceptions dan AnnotationDefault, metode native, anggota Serializable, metode @JavascriptInterface, konstruktor Throwable(String), kolom Parcelable$CREATOR, dan kolom MessageLite protobuf.
  • build/make/core/proguard/kotlin.flags: Menonaktifkan peringatan yang tidak berbahaya untuk meta-anotasi Kotlin tertentu (kotlin.Metadata dan kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) dan menghapus anotasi DebugMetadata Kotlin dalam build rilis.
  • build/make/core/proguard/checknotnull.flags: Mengganti panggilan helper pemeriksaan null umum (com.google.common.base.Preconditions.checkNotNull dan dagger.internal.Preconditions.checkNotNull*) dengan pemeriksaan null bytecode yang ringkas. File ini sengaja menghilangkan Objects.requireNonNull untuk mempertahankan pesan pengecualian eksplisit di seluruh batas API framework.
  • build/make/core/proguard/enumvalues.flags: Mempertahankan metode values dan valueOf pada jenis enum kecuali jika dinonaktifkan oleh konfigurasi build.
  • Aturan yang dibuat otomatis oleh AAPT2: AAPT2 memeriksa AndroidManifest.xml, file XML tata letak, dan file XML preferensi yang digabungkan untuk menghasilkan aturan keep yang tepat untuk setiap Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, subclass View kustom, subclass Preference, dan metode android:onClick XML yang terdaftar.

Mengaudit dan memigrasikan aturan penyimpanan yang ada

Saat mengaudit file proguard.flags atau keep.xml yang ada di repositori platform, evaluasi setiap aturan secara berurutan berdasarkan hierarki empat langkah berikut:

Langkah 1: Hapus aturan yang redundan atau usang

Hapus aturan yang sudah tercakup dalam dasar pengukuran global, aturan manifes dan tata letak AAPT2, atau default R8, serta aturan yang mereferensikan class atau paket yang sudah tidak ada.

Jika semua aturan dalam file .flags bersifat redundan, hapus file tersebut dan hapus proguard_flags_files dari Android.bp. Jika blok optimize yang tersisa hanya menyatakan ulang setelan default, hapus blok yang redundan dan format file build dengan bpfmt -w Android.bp. Saat membersihkan paket, periksa apakah modul pendamping di subdirektori (seperti varian target Kotlin) mereferensikan file flag yang sama dan update bersama-sama.

Langkah 2: Migrasikan aturan keep berbasis pengujian

Pindahkan aturan keep berbasis pengujian dari file .flags produksi kustom menggunakan salah satu pola berikut:

  • Lacak referensi dari target pengujian atau pendamping (trace_references_from atau allow_traced_references): Jika target dan modul dependen langsung mengompilasi DEX-nya sendiri (android_app, android_test, atau java_library yang di-dex) dan terhubung melalui instrumentation_for atau libs, Soong dapat menjalankan R8 tracereferences pada modul dependen dan memasukkan aturan keep yang dihasilkan ke dalam kompilasi R8 target tanpa membuat siklus dependensi atau memerlukan pemisahan library .impl manual:

    • Pengujian repositori yang sama (trace_references_from): Mencantumkan dependensi instrumentation_for atau libs langsung pada modul target (ini juga otomatis diterapkan di seluruh varian override_android_app dan override_android_test, serta terus mendukung library Java pendamping yang tidak bergantung saat dipasangkan dengan pemisahan .impl):

      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"],
      }
      
    • Pengujian lintas repositori atau vendor (allow_traced_references dan trace_references): Jika modul target tidak dapat mencantumkan dependensi secara langsung di trace_references_from (seperti pengujian di repositori atau hierarki vendor lain), tetapkan allow_traced_references: true pada modul target dan ikut serta dari setiap dependensi dengan trace_references: true:

      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,
          },
      }
      
    • Refleksi berbasis string dalam pengujian: tracereferences memeriksa referensi kumpulan konstanta bytecode dan tidak dapat mendeteksi refleksi berbasis string dalam pengujian (Class.forName, getDeclaredField, getDeclaredMethod, atau helper injeksi kolom reflektif). Anotasikan kolom dan metode produksi yang diakses secara reflektif dengan @VisibleForTesting (atau @UsedByReflection).

  • Menautkan library implementasi ke pengujian (static_libs): Jika Anda tidak ingin titik entri khusus pengujian dipertahankan di APK produksi dan tidak ingin perubahan pengujian memicu ulang R8 pada target produksi, tempatkan file sumber aplikasi di android_library (MyApp.impl) dan tautkan MyApp.impl secara statis ke MyApp dan MyAppTests:

    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"],
    }
    

    Pola 3 modul ini memungkinkan MyAppTests berjalan sebagai uji instrumentasi mandiri dengan akses penuh ke class internal sekaligus memungkinkan MyApp menyusutkan dan meng-obfuscate murni terhadap titik entri produksi.

  • Beri anotasi pada hook pengujian dengan @VisibleForTesting: Jika APK pengujian eksternal hanya memanggil beberapa metode atau konstruktor internal pada target android_app, Anda dapat memberi anotasi pada deklarasi tersebut dengan @VisibleForTesting. Baseline build/make/core/proguard.flags global mempertahankan @VisibleForTesting item dalam paket android.**, com.android.**, dan com.google.android.** secara otomatis tanpa file .flags kustom. (Untuk modul vendor di luar namespace ini, gunakan trace_references_from, @UsedByReflection, atau aturan library yang diekspor.) Saat menganotasi secara manual dengan @VisibleForTesting, bukan menggunakan trace_references_from atau trace_references, anotasikan juga class yang diteruskan ke Mockito mock(...) atau spy(...) (bukan hanya metode stub), karena mockito-target (dexmaker-mockito) di perangkat menghasilkan subclass runtime dan R8 dapat menandai class yang tidak di-subclass final (MockitoException: Can't mock/spy class ... - final class).

  • Hindari atau kemas ulang library statis yang tumpang-tindih (jarjar_rules): Jika android_app dan APK pengujian instrumentation_for-nya secara statis menautkan library yang sama (secara langsung, seperti guava, atau secara transitif), tracereferences akan melihat class tersebut ditentukan dalam pengujian dan tidak memunculkan aturan keep untuk aplikasi, dan pada waktu proses, classloader induk aplikasi akan menyamarkan salinan pengujian dengan class yang disusutkan R8, sehingga menyebabkan IllegalAccessError atau NoSuchMethodError. Jika pengujian tidak memerlukan salinan paketnya sendiri, hapus duplikat dari static_libs pengujian (atau tautkan melalui libs) untuk menghindari konflik; jika tidak, kemas ulang paket library statis aplikasi yang tumpang-tindih di bawah awalan khusus modul menggunakan jarjar_rules.

Langkah 3: Ekspor aturan dari koleksi pemilik

Jika java_library atau android_library bersama memerlukan aturan keep untuk refleksi internal atau callback JNI-nya sendiri, tentukan aturan pada target library dan tetapkan export_proguard_flags_files: true:

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

Semua target library dan android_app downstream yang menautkan my-shared-library secara statis akan mewarisi aturan tersebut secara otomatis, sehingga aplikasi downstream tidak perlu menduplikasinya. Jika beberapa modul di berbagai direktori harus berbagi set aturan yang tidak terkait dengan satu library kode, publikasikan file .flags melalui filegroup eksplisit di Android.bp sehingga modul dapat mereferensikan :my-shared-flags dengan jelas di seluruh batas paket. Untuk panduan umum tentang cara menulis aturan keep konsumen library tanpa membatasi pengoptimalan aplikasi downstream, lihat Pengoptimalan untuk penulis library.

Langkah 4: Beri anotasi pada deklarasi dalam kode sumber

Jika class, metode, konstruktor, atau kolom diakses melalui refleksi atau JNI dan tidak tercakup oleh AAPT2 atau dasar pengukuran global, ganti aturan -keep yang terpisah dalam file .flags dengan anotasi sumber dari Panduan untuk Mempertahankan Anotasi (lihat referensi Javadoc keepanno):

  • @UsesReflection: Pilih anotasi ini saat Anda mengontrol kode yang melakukan refleksi. Tempatkan anotasi ini di situs panggilan refleksi untuk mendeklarasikan class, metode, atau kolom target mana yang diakses secara dinamis. Karena @UsesReflection otomatis mengenkode prasyarat bahwa situs panggilan yang dianotasi itu sendiri dapat dijangkau, R8 akan memangkas pemanggil dan target reflektif jika situs panggilan tidak digunakan.
  • @UsedByReflection: Tempatkan pada class, metode, kolom, atau konstruktor yang dibuat instance-nya atau dipanggil secara reflektif oleh kode atau library eksternal, seperti class yang dimuat dengan Class.forName dari kunci Bundle atau Settings, atau class plugin yang dimuat di seluruh classloader dinamis. Tentukan kind (seperti KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition), dan batasan parameter sehingga R8 hanya mempertahankan kontrak reflektif yang tepat dan masih dapat mengoptimalkan atau memangkas anggota class yang tidak digunakan. Jangan tambahkan @UsedByReflection ke komponen yang terdaftar dalam manifes seperti JobService atau BroadcastReceiver, karena AAPT2 sudah menyimpannya.
  • @UsedByNative: Tempatkan pada metode atau kolom yang diakses dari kode JNI C atau C++ menggunakan GetMethodID, GetStaticMethodID, atau GetFieldID.
  • @KeepForApi: Tempatkan pada class atau anggota API library yang harus tetap utuh saat library itu sendiri diciutkan sebelum didistribusikan.
  • @Keep (androidx.annotation.Keep): Gunakan sebagai pengganti jika keepanno tidak berlaku. Menerapkan @Keep ke suatu class akan mempertahankan class tersebut dan semua elemennya tanpa syarat. Menerapkan @Keep ke metode atau kolom akan bertindak sebagai titik entri tanpa syarat yang mempertahankan anggota dan class yang memuatnya meskipun class tersebut tidak pernah dibuat instance-nya, sedangkan keepanno mengekspresikan keterjangkauan bersyarat.

Untuk menggunakan anotasi keepanno R8 dalam modul Soong:

  1. Tambahkan "keepanno-annotations" ke libs di Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. Impor com.android.tools.r8.keepanno.annotations.* dan anotasikan deklarasi atau situs panggilan dalam kode sumber Java atau Kotlin:

    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 menerjemahkan anotasi keepanno langsung ke model aturan keep internalnya dan menghapus anotasi dari output DEX akhir, sehingga tidak menambahkan overhead bytecode runtime.

Menghindari kesalahan umum

Bagian berikut menjelaskan kesalahan umum proguard.flags dan keep.xml dalam kode platform dan cara memperbaikinya.

Aturan keep komponen yang luas

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • Alasan hal ini mengganggu performa: Aturan ini sepenuhnya redundan dengan AAPT2. Karena menggunakan karakter pengganti tanpa memeriksa apakah komponen terdaftar dalam AndroidManifest.xml gabungan akhir, aturan ini memaksa R8 untuk mempertahankan setiap subclass Activity, Service, atau BroadcastReceiver yang ditemukan di mana saja pada classpath, termasuk komponen library yang tidak digunakan dan aktivitas debug yang dinonaktifkan.
  • Perbaikan yang disarankan: Hapus aturan. AAPT2 memeriksa manifes gabungan dan membuat aturan -keep yang tepat untuk komponen terdaftar.

Aturan keep pengendali klik XML yang luas

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Alasan hal ini mengganggu performa: Mempertahankan setiap metode public void *(View) di setiap class dalam modul akan mencegah R8 menghapus atau menyisipkan metode apa pun yang kebetulan menerima satu parameter View.
  • Perbaikan yang disarankan: Hapus aturan. AAPT2 memindai file XML tata letak dan membuat aturan keep yang ditargetkan untuk metode yang direferensikan oleh atribut android:onClick.

Aturan keep getter dan setter View yang luas

# 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*();
}
  • Alasan hal ini menurunkan performa: Aturan ini memaksa R8 untuk mempertahankan setiap pengambil dan penyetel di setiap subclass View dalam aplikasi dan semua library tertaut (termasuk library AndroidX dan Material), sehingga memblokir penghapusan metode mati dan inlining di seluruh kode UI.
  • Perbaikan yang disarankan: Hapus aturan. AAPT2 sudah mempertahankan konstruktor untuk class View kustom yang di-inflate dari file XML tata letak. Jika kode menganimasikan properti tampilan menggunakan nama string reflektif seperti ObjectAnimator.ofFloat(view, "translationZ", ...), ganti nama string dengan referensi properti yang diketik (View.TRANSLATION_Z atau implementasi FloatProperty atau IntProperty kustom) untuk menghindari refleksi sama sekali. Jika akses properti reflektif tidak dapat dihindari, anotasikan pengambil atau penyetel tertentu dengan @UsedByReflection.

Pengoptimalan atau obfuscation menyeluruh menonaktifkan

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Alasan hal ini menurunkan performa: Menempatkan -dontoptimize atau -dontshrink di dalam file .flags akan mengganti setelan Android.bp modul secara otomatis dan menonaktifkan penerusan pengoptimalan di seluruh target. Lebih buruk lagi, jika library mengekspor file .flags yang berisi -dontoptimize, library tersebut akan menonaktifkan pengoptimalan R8 untuk setiap android_app downstream yang menautkan library.
  • Perbaikan yang direkomendasikan: Hapus -dontoptimize, -dontshrink, dan -dontobfuscate dari file .flags. Kontrol perilaku pengoptimalan secara eksplisit di Android.bp menggunakan blok optimize (shrink, optimize, obfuscate) pada target leaf.

Flag diagnostik dalam aturan yang di-commit

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • Alasan hal ini mengganggu build: Flag diagnostik membanjiri log build atau mencoba menulis file output ke jalur lokal selama build Soong dengan sandbox.
  • Perbaikan yang direkomendasikan: Hapus flag ini dari file .flags yang di-commit. Soong secara otomatis menulis output penggunaan dan pemetaan R8, seperti proguard_dictionary dan proguard_usage.zip, ke direktori perantara modul di bagian out/soong/.intermediates/.

Aturan keep produksi untuk kode pengujian

  • Alasan hal ini mengganggu performa: Menambahkan aturan -keep kustom hanya untuk akses pengujian akan memaksa kode tersebut tetap tidak di-obfuscate dan dipertahankan dalam build produksi. Meskipun mengonfigurasi trace_references_from (atau allow_traced_references dengan trace_references: true) atau menganotasi metode dengan @VisibleForTesting menghindari pemeliharaan aturan -keep manual dan hanya mempertahankan apa yang direferensikan pengujian, simbol tersebut masih dikirim dalam biner produksi.
  • Perbaikan yang direkomendasikan: Hilangkan aturan -keep pengujian kustom dengan mengaktifkan trace_references_from (atau allow_traced_references dan trace_references: true), atau susun aplikasi ke dalam library .impl yang ditautkan ke APK pengujian yang melakukan instrumentasi sendiri menggunakan static_libs untuk menjaga titik entri khusus pengujian sepenuhnya keluar dari APK produksi, seperti yang dijelaskan dalam Langkah 2: Migrasikan aturan keep berbasis pengujian.

Karakter pengganti resource menyeluruh di keep.xml

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • Alasan hal ini menurunkan performa: Karakter pengganti menyeluruh mempertahankan setiap resource dalam jenis yang cocok di seluruh modul dan dependensinya, sehingga menggagalkan penyingkatan resource (shrink_resources: true) serta memperbesar APK dan tabel resources.arsc yang dipetakan.
  • Perbaikan yang direkomendasikan:
    • Lebih pilih referensi resource statis daripada keep.xml: Hindari penggunaan metode Resources.getIdentifier untuk mencari kumpulan resource yang dibatasi secara dinamis, seperti string eksperimen bernomor atau drawable bertema. Sebagai gantinya, gunakan pernyataan switch waktu kompilasi atau petakan konstanta R.id, R.string, atau R.drawable statis. Referensi statis memungkinkan R8 dan AAPT2 melacak resource aktif yang tepat, menghilangkan overhead pencarian string runtime, dan sepenuhnya menghilangkan kebutuhan akan keep.xml.
    • Cantumkan nama resource tertentu: Jika pencarian dinamis diperlukan, misalnya oleh pembaca lisensi pihak ketiga, cantumkan ID resource yang tepat di tools:keep (misalnya, tools:keep="@raw/third_party_licenses").
    • Hapus file keep.xml yang redundan: Jika resource yang tercantum sudah direferensikan secara statis dalam kode (R.raw.foo) atau XML (@raw/foo), penyingkat akan mempertahankannya secara otomatis, sehingga Anda dapat menghapus res/raw/keep.xml.

Memverifikasi dan mengaudit perubahan aturan

Setiap kali Anda menghapus atau mempersempit aturan keep di proguard.flags atau keep.xml, pastikan modul di-build dengan baik, lulus uji unit dan instrumentasi, serta mempertahankan semua titik entri yang diperlukan.

Modul build dan pengujian

  1. Bangun modul dengan bersih untuk memverifikasi bahwa R8 dan AAPT2 selesai tanpa peringatan referensi yang hilang:

    m <MODULE_NAME>
    
  2. Jalankan pengujian unit dan instrumentasi untuk modul menggunakan atest:

    atest <TEST_MODULE_NAME>
    
  3. Untuk aplikasi sistem, layanan dengan hak istimewa, atau modul yang bergantung pada hardware, jalankan pengujian instrumentasi dan UI pada perangkat fisik target atau di lab pengujian perangkat Anda untuk menjalankan jalur refleksi runtime, binding IPC, dan inflasi resource.

Memeriksa perbedaan DEX dan resource

Bandingkan APK atau JAR yang dikompilasi sebelum dan sesudah perubahan aturan untuk mengonfirmasi bahwa R8 menghapus kode dan resource yang tidak digunakan tanpa menghapus titik entri yang diharapkan:

  • Gunakan apkanalyzer atau dexdump untuk memeriksa class, metode, dan kolom yang dipertahankan dalam file APK atau DEX output:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Saat mengubah keep.xml atau mengaktifkan shrink_resources, gunakan aapt2 dump resources untuk memverifikasi bahwa build menghapus resource yang tidak digunakan dan mempertahankan resource yang diperlukan:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • Saat menjalankan prebuilts/r8/r8.jar langsung pada classes.jar pra-R8 modul untuk memverifikasi perubahan flag secara offline tanpa build platform penuh, teruskan JAR classpath --lib modul (android.jar, framework.jar, dan libs bersama) dan aturan proguard.options yang dihasilkan AAPT2 dari AndroidManifest.xml dan XML tata letak. Tanpa input tersebut, R8 tidak dapat me-resolve superjenis framework (Activity, Service, Phone, Handler) dan kurang mempertahankan metode hierarki.

Menganalisis radius keep dan subsumsi aturan dengan R8

Compiler R8 open source menyertakan penganalisis Keep Radius (juga diekspos di Android Studio dan Gradle sebagai R8 Configuration Analyzer) yang mengukur dampak pasti dari setiap aturan keep selama kompilasi. Soong mengintegrasikan penganalisis ini langsung ke build platform Android (dikonfigurasi di build/soong/java/dex.go).

Untuk menganalisis aturan keep untuk modul platform atau di seluruh build:

  1. Jalankan build dengan variabel lingkungan R8_DUMP_KEEP_RADIUS=true:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    Jika R8_DUMP_KEEP_RADIUS=true ditetapkan, Soong akan menginstruksikan R8 untuk mencatat metrik aturan keep dalam file r8keepradius.pb perantara untuk setiap modul yang dikompilasi di bagian out/soong/.intermediates/. Untuk setiap aturan keep dan anotasi keepanno, R8 mencatat:

    • Radius keep langsung: Class, kolom, dan metode yang tepat yang dipertahankan oleh aturan, beserta batasan spesifik yang diterapkan terhadap penyingkatan, pengoptimalan, atau obfuscation.
    • Subsumsi aturan: Aturan atau anotasi keep lain yang sudah mempertahankan item yang sama. Jika aturan kustom sepenuhnya tercakup dalam aturan AAPT2 atau aturan dasar pengukuran global, Anda dapat menghapusnya dengan aman.
    • Aturan global dan seluruh paket: Aturan yang menggunakan karakter pengganti seluruh paket yang luas atau menerapkan perintah konfigurasi global.
  2. Konversikan output r8keepradius.pb menjadi laporan HTML interaktif menggunakan KeepRadiusHtmlReportGenerator (dipaketkan dalam prebuilts/r8/r8.jar):

    # 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
    

    Saat diberi direktori, KeepRadiusHtmlReportGenerator akan menelusuri semua file *keepradius*.pb, membuat laporan HTML untuk setiap modul, dan membuat out/keep_radius_reports/keepradius.html yang meringkas jumlah item aktif, jumlah item yang dipertahankan, dan aturan keep radius tertinggi di seluruh build. Untuk mengetahui informasi selengkapnya tentang cara menafsirkan skor penyingkatan, pengoptimalan, dan obfuscation dalam laporan yang dihasilkan, lihat Menggunakan R8 Configuration Analyzer.

Soong juga mendukung variabel lingkungan loop dalam dan diagnostik R8 tambahan di build/soong/java/dex.go:

  • R8_DUMP_INPUT=true: Menulis r8inputs.zip ke direktori perantara modul yang berisi semua JAR input, JAR library, dan konfigurasi ProGuard gabungan untuk reproduksi R8 mandiri.
  • R8_DUMP_PERFETTO_TRACE=true: Menulis r8trace.ptrace ke direktori perantara modul untuk memeriksa penerusan kompilasi R8 di Perfetto.
  • SOONG_SKIP_TRACE_REFERENCES=true: Melewati eksekusi aturan tracereferences yang dikonfigurasi oleh trace_references_from atau trace_references untuk mempercepat build loop dalam lokal saat melakukan iterasi pada kode pengujian.

Memvalidasi aturan konsumen library

Saat java_library atau android_library mengekspor aturan keep ke aplikasi pelanggan downstream (export_proguard_flags_files: true), aturan tersebut tidak boleh menyertakan flag global yang mengubah atau menonaktifkan pengoptimalan untuk aplikasi pelanggan.

R8 menyediakan penganalisis aturan keep open source (ProcessKeepRules), yang diekspos dalam build platform Android sebagai alat host process-keep-rules (ditentukan dalam prebuilts/r8/Android.bp):

# 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>

Alat process-keep-rules mengurai setiap file konfigurasi dan gagal dengan diagnostik file dan baris jika menemukan perintah yang tidak diizinkan dalam aturan konsumen library. Perintah yang tidak diizinkan mencakup pengoptimalan global, penyingkatan, atau penonaktifan obfuscation (seperti -dontoptimize atau -dontshrink), pengemasan ulang paket dan flag modifikasi akses, flag diagnostik atau pemetaan, dan -keepattributes level aplikasi.

Mengaudit hierarki sumber dengan pgaudit.py

Untuk mengaudit direktori sumber yang belum di-build bersama penganalisis waktu build R8, hierarki platform Android menyertakan skrip pgaudit.py di bagian build/make/core/proguard/tools/. Alat analisis statis ini memindai file Android.bp, proguard.flags, dan keep.xml di seluruh repositori untuk menandai aturan yang sudah dicakup oleh AAPT2 atau dasar pengukuran platform global, karakter pengganti yang luas, penggantian -dontoptimize, dan aturan keep khusus pengujian:

# 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

Tim platform dan OEM dapat menggabungkan pgaudit.py untuk triase hierarki sumber yang cepat, process-keep-rules untuk memvalidasi aturan library yang diekspor, dan R8_DUMP_KEEP_RADIUS=true dengan KeepRadiusHtmlReportGenerator untuk mengukur radius retensi class, kolom, dan metode yang tepat dari setiap aturan dalam build.