Pengoptimalan untuk penulis library

Sebagai penulis library, Anda harus memastikan bahwa developer aplikasi dapat dengan mudah menggabungkan library Anda ke dalam aplikasi mereka sekaligus mempertahankan pengalaman pengguna akhir yang berkualitas tinggi. Artinya, library Anda harus kompatibel dengan pengoptimalan Android (R8) tanpa memerlukan penyiapan tambahan dari developer—atau mendokumentasikan bahwa library mungkin tidak sesuai untuk digunakan di Android. Pustaka yang ditujukan untuk digunakan di Android tidak boleh mencegah pengoptimalan aplikasi penting dan harus mematuhi persyaratan pengoptimalan tambahan.

Dokumentasi ini ditujukan untuk developer library yang dipublikasikan, tetapi mungkin juga berguna bagi developer modul library internal dalam aplikasi besar yang dimodulasi.

Jika Anda adalah developer aplikasi dan ingin mempelajari cara mengoptimalkan aplikasi Android, lihat Mengaktifkan pengoptimalan aplikasi. Untuk mempelajari library mana yang tepat untuk digunakan, lihat Pilih library dengan bijak.

Memahami jenis aturan simpan

Ada dua jenis aturan simpan yang berbeda yang dapat Anda miliki di library:

  • Aturan keep konsumen harus menentukan aturan yang mempertahankan apa pun yang direfleksikan oleh library. Jika library menggunakan refleksi atau JNI untuk memanggil kodenya, atau kode yang ditentukan oleh aplikasi klien, aturan ini perlu menjelaskan kode apa yang perlu dipertahankan. Library harus memaketkan aturan keep konsumen, yang menggunakan format yang sama dengan aturan keep aplikasi. Aturan ini dibundel ke dalam artefak library (AAR atau JAR) dan digunakan secara otomatis selama pengoptimalan aplikasi Android saat library digunakan. Aturan ini dikelola dalam file yang ditentukan dengan properti consumerProguardFiles dalam file build.gradle.kts (atau build.gradle). Untuk mempelajari lebih lanjut, lihat Menulis aturan penyimpanan konsumen.
  • Aturan keep build library diterapkan saat library Anda di-build. Alat ini hanya diperlukan jika Anda memutuskan untuk mengoptimalkan library secara parsial pada waktu build. Mereka harus mencegah API publik library dihapus, jika tidak, API publik tidak akan ada dalam distribusi library, yang berarti developer aplikasi tidak dapat menggunakan library tersebut. Aturan ini dikelola dalam file yang ditentukan dengan properti proguardFiles dalam file build.gradle.kts (atau build.gradle). Untuk mempelajari lebih lanjut, lihat Mengoptimalkan build library AAR.

Persyaratan dan panduan pengoptimalan

Konfigurasi R8 di library memiliki dampak global pada ukuran dan performa biner akhir aplikasi yang menggunakannya. Selain praktik terbaik aturan keep umum, penulis library harus mematuhi persyaratan tertentu, dan mempertimbangkan panduan tambahan.

Mematuhi persyaratan pengoptimalan

Inefisiensi dalam library adalah kontributor utama bloatware aplikasi, pemborosan memori, startup yang lambat, dan ANR (error Aplikasi Tidak Merespons). Library harus menghindari pelanggaran persyaratan berikut untuk menghindari penurunan kualitas aplikasi dan pengalaman pengguna secara signifikan.

  • Tidak ada aturan penyimpanan luas atau di seluruh paket: Library Anda tidak boleh menyertakan aturan penyimpanan luas yang menyimpan sebagian besar kode di library Anda, atau di library lain. Aturan keep yang luas mungkin dapat mengatasi error dalam jangka pendek, tetapi akan membuat ukuran semua aplikasi yang menggunakan library Anda menjadi besar.

    Jangan sertakan aturan keep seluruh paket (seperti -keep class com.mylibrary.** {*; }) untuk paket di library Anda atau library lain yang direferensikan. Aturan tersebut membatasi pengoptimalan untuk paket ini di semua aplikasi yang menggunakan library Anda.

  • Tidak ada aturan global yang tidak sesuai: Jangan pernah menggunakan opsi global seperti -dontobfuscate atau -allowaccessmodification.

  • Penggunaan codegen daripada refleksi jika memungkinkan: Jika memungkinkan, gunakan pembuatan kode (codegen) daripada refleksi. Codegen dan refleksi adalah pendekatan umum untuk menghindari kode boilerplate saat memprogram, tetapi codegen lebih kompatibel dengan pengoptimal aplikasi seperti R8.

    Dengan codegen, kode dianalisis dan diubah selama proses build. Karena tidak ada modifikasi besar setelah waktu kompilasi, pengoptimal mengetahui kode yang pada akhirnya diperlukan dan kode yang dapat dihapus dengan aman.

    Dengan refleksi, kode dianalisis dan dimanipulasi saat runtime. Karena kode tidak benar-benar diselesaikan hingga dieksekusi, pengoptimal tidak tahu kode mana yang dapat dihapus dengan aman. Proses ini kemungkinan akan menghapus kode yang digunakan secara dinamis melalui refleksi selama runtime, yang menyebabkan error aplikasi bagi pengguna.

    Banyak library modern menggunakan codegen, bukan refleksi. Lihat KSP untuk titik entri umum, yang digunakan oleh Room, Hilt, dan banyak lainnya.

  • Mendukung mode penuh R8: Library Anda tidak boleh error saat mode penuh R8 diaktifkan. Mode penuh R8 adalah mode yang direkomendasikan untuk menggunakan R8, dan merupakan mode default sejak AGP 8.0, yang dibuat stabil pada tahun 2023. Jika library Anda mengalami error di R8, solusinya adalah mengidentifikasi titik entri refleksi atau JNI tertentu dan menambahkan aturan yang ditargetkan, bukan mempertahankan seluruh paket.

Rekomendasi tambahan

Selain persyaratan pengoptimalan, berikut adalah rekomendasi tambahan.

  • Jangan gunakan -repackageclasses dalam file aturan keep konsumen library Anda. Namun, untuk mengoptimalkan build library, Anda dapat menggunakan -repackageclasses dengan nama paket internal, seperti <your.library.package>.internal, dalam file aturan keep build library Anda. Hal ini dapat meningkatkan efisiensi library Anda di aplikasi yang tidak dioptimalkan. Namun, hal ini umumnya tidak diperlukan, karena aplikasi juga harus dioptimalkan.
  • Deklarasikan atribut apa pun yang Anda perlukan agar library berfungsi dalam file aturan keep library, meskipun mungkin ada tumpang-tindih dengan atribut yang ditentukan dalam proguard-android-optimize.txt.
  • Jika Anda memerlukan atribut berikut dalam distribusi library, pertahankan atribut tersebut dalam file aturan keep build library, dan bukan dalam file aturan keep konsumen library:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Pembuat library harus menyimpan atribut RuntimeVisibleAnnotations dalam aturan keep konsumen jika anotasi digunakan saat runtime.
  • Pembuat library tidak boleh menggunakan opsi global berikut dalam aturan keep konsumen mereka:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

Kapan refleksi diperbolehkan

Jika harus menggunakan refleksi, Anda hanya boleh merefleksikan ke salah satu dari berikut:

  • Jenis target tertentu (implementer antarmuka atau subclass tertentu)
  • Kode yang menggunakan anotasi runtime tertentu

Menggunakan refleksi dengan cara ini akan membatasi biaya runtime, dan memungkinkan penulisan aturan keep konsumen yang ditargetkan.

Bentuk refleksi yang spesifik dan terarah ini adalah pola yang dapat Anda lihat di framework Android (misalnya, saat meng-inflate aktivitas, tampilan, dan drawable) dan library AndroidX (misalnya saat membuat WorkManager ListenableWorkers, atau RoomDatabases). Sebaliknya, refleksi terbuka Gson tidak sesuai untuk digunakan di aplikasi Android.

Kesalahpahaman umum

Beberapa kesalahpahaman umum mungkin menyebabkan Anda salah mengonfigurasi R8. Contoh ini meliputi:

  • Pemahaman yang salah tentang pengoptimalan R8: Berbeda dengan pemahaman umum, pengoptimalan R8 tidak terbatas pada obfuscation saja, tetapi juga mencakup penyingkatan kode dan pengoptimalan logis dengan teknik inlining metode dan penggabungan class. Untuk mengetahui informasi selengkapnya, lihat Ringkasan pengoptimalan R8.

  • Melewati pengoptimalan library yang di-obfuscate: Kesalahan umum adalah menghilangkan library dari pengoptimalan, karena library dioptimalkan atau di-obfuscate saat dikompilasi ke AAR (Android Archive) atau JAR (Java Archive). Pengoptimalan selama waktu build library terbatas, dan aplikasi Anda tidak boleh menonaktifkan pengoptimalan library dengan menyertakannya dalam aturan keep. Untuk mengetahui informasi selengkapnya, lihat Mengoptimalkan build library AAR.

  • Pemahaman yang salah tentang opsi -keep Aturan -keep mencegah R8 menjalankan pass pengoptimalan. Untuk mengetahui informasi selengkapnya, lihat Memilih opsi simpan yang tepat.

Memvalidasi kualitas aturan simpan konsumen

Untuk memvalidasi bahwa library Anda tidak terlalu banyak mencegah pengoptimalan kode saat disematkan dalam aplikasi, gunakan contoh library atau aplikasi pengujian integrasi. Aktifkan R8, dan periksa kualitas konfigurasi R8 aplikasi secara keseluruhan dengan R8 Configuration Analyzer.

Jika aturan konsumen Anda terbukti membatasi pengoptimalan pada sebagian besar kode library, ini menandakan bahwa aturan konsumen Anda perlu disempurnakan. Persempit aturan konsumen dan uji aspek library yang terpengaruh dalam aplikasi dengan R8 diaktifkan.

Mengonfigurasi pengemasan aturan

Untuk memastikan aturan keep konsumen diterapkan dengan benar, Anda harus mengemasnya dengan tepat, bergantung pada format library Anda.

Jika Anda menggunakan Jetpack Keep untuk mendeklarasikan penggunaan refleksi dan JNI library, plugin Gradle-nya dapat mengemas aturan yang dihasilkan sebagai aturan keep konsumen untuk Anda.

Library AAR

Untuk menambahkan aturan konsumen untuk library AAR, gunakan opsi consumerProguardFiles di skrip build modul library Android. Untuk mengetahui informasi selengkapnya, lihat panduan kami tentang cara membuat modul library.

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

Library JAR

Untuk memaketkan aturan dengan library Kotlin atau Java yang dikirim sebagai JAR, tempatkan file aturan Anda di direktori META-INF/proguard/ JAR akhir, dengan nama file apa pun. Misalnya, jika kode Anda ada di <libraryroot>/src/main/kotlin, letakkan file aturan konsumen di <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro dan aturan akan dibundel di lokasi yang benar dalam JAR output Anda.

Pastikan bahwa aturan paket JAR akhir sudah benar dengan memeriksa bahwa aturan tersebut ada di direktori META-INF/proguard.

Mengoptimalkan build library AAR (lanjutan)

Umumnya, Anda tidak perlu mengoptimalkan build library secara langsung karena kemungkinan pengoptimalan pada waktu build library sangat terbatas. Sebagai developer library, Anda perlu mempertimbangkan beberapa tahap pengoptimalan dan mempertahankan perilaku, baik pada waktu build library maupun aplikasi, sebelum mengoptimalkan library tersebut.

Jika Anda masih ingin mengoptimalkan library pada waktu build, hal ini didukung oleh Plugin Android Gradle.

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

Groovy

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

Perhatikan bahwa perilaku proguardFiles sangat berbeda dengan consumerProguardFiles:

  • proguardFiles digunakan pada waktu build, sering kali bersama dengan getDefaultProguardFile("proguard-android-optimize.txt"), untuk menentukan bagian library yang harus dipertahankan selama build library. Setidaknya, ini adalah API publik Anda.
  • Sebaliknya, consumerProguardFiles dipaketkan ke dalam library untuk memengaruhi pengoptimalan yang terjadi nanti, selama build aplikasi yang menggunakan library Anda.

Misalnya, jika library Anda menggunakan refleksi untuk membuat class internal, Anda mungkin perlu menentukan aturan keep di proguardFiles dan consumerProguardFiles.

Jika Anda menggunakan -repackageclasses dalam build library, kemas ulang class ke sub-paket di dalam paket library Anda. Misalnya, gunakan -repackageclasses 'com.example.mylibrary.internal', bukan -repackageclasses 'internal'.

Mendukung berbagai versi R8 (lanjutan)

Anda dapat menyesuaikan aturan untuk menargetkan versi R8 tertentu. Hal ini memungkinkan library Anda berfungsi secara optimal dalam project yang menggunakan versi R8 yang lebih baru, sekaligus memungkinkan aturan yang ada untuk terus digunakan dalam project dengan versi R8 yang lebih lama.

Untuk menentukan aturan R8 yang ditargetkan, Anda harus menyertakannya di direktori META-INF/com.android.tools dalam classes.jar AAR atau di direktori META-INF/com.android.tools JAR.

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

Dalam direktori META-INF/com.android.tools, dapat ada beberapa subdirektori dengan nama dalam bentuk r8-from-<X>-upto-<Y> untuk menunjukkan versi R8 yang menjadi tujuan penulisan aturan. Setiap subdirektori dapat memiliki satu atau beberapa file yang berisi aturan R8, dengan nama dan ekstensi file apa pun.

Perhatikan bahwa bagian -from-<X> dan -upto-<Y> bersifat opsional, versi <Y> bersifat eksklusif, dan rentang versi biasanya berkelanjutan, tetapi juga dapat tumpang-tindih.

Misalnya, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0, dan r8-from-8.2.0 adalah nama direktori yang merepresentasikan sekumpulan aturan R8 yang ditargetkan. Aturan di direktori r8 dapat digunakan oleh semua versi R8. Aturan dalam direktori r8-from-8.0.0-upto-8.2.0 dapat digunakan oleh R8 dari versi 8.0.0 hingga tidak termasuk versi 8.2.0.

Plugin Android Gradle menggunakan informasi tersebut untuk memilih semua aturan yang dapat digunakan oleh versi R8 saat ini. Jika library tidak menentukan aturan R8 yang ditargetkan, plugin Android Gradle akan memilih aturan dari lokasi lama (proguard.txt untuk AAR atau META-INF/proguard/<ProGuard-rule-files> untuk JAR).