Menyusutkan, meng-obfuscate, dan mengoptimalkan aplikasi Anda

Untuk membuat aplikasi sekecil dan secepat mungkin, Anda harus mengoptimalkan dan meminifikasi build rilis dengan isMinifyEnabled = true.

Tindakan ini memungkinkan penyingkatan, yang menghapus kode yang tidak digunakan; obfuscation, yang mempersingkat nama class dan anggota aplikasi; serta pengoptimalan, yang menerapkan strategi pengoptimalan kode yang ditingkatkan untuk semakin mengurangi ukuran dan meningkatkan performa aplikasi. Halaman ini menjelaskan cara R8 melakukan tugas-tugas waktu kompilasi tersebut untuk project Anda dan cara menyesuaikannya.

Saat Anda membuat project menggunakan plugin Android Gradle 3.4.0 atau yang lebih baru, plugin ini tidak lagi menggunakan ProGuard untuk melakukan pengoptimalan kode waktu kompilasi. Sebagai gantinya, plugin ini berfungsi dengan compiler R8 untuk menangani tugas-tugas waktu kompilasi berikut:

  • Penyingkatan kode (atau tree-shaking): mendeteksi dan menghapus dengan aman class, kolom, metode, dan atribut yang tidak digunakan dari aplikasi Anda dan dependensi library-nya (menjadikannya alat yang berharga untuk mengatasi batasan referensi 64k). Misalnya, jika Anda hanya menggunakan beberapa API dari dependensi library, penyingkatan dapat mengidentifikasi kode library yang tidak digunakan aplikasi dan hanya menghapus kode tersebut dari aplikasi Anda. Untuk mempelajari lebih lanjut, buka bagian tentang cara menyingkat kode.
  • Penyingkatan resource: menghapus resource yang tidak digunakan dari aplikasi yang dipaketkan, termasuk resource yang tidak digunakan dalam dependensi library aplikasi Anda. Ini berfungsi bersamaan dengan penyingkatan kode sehingga setelah kode yang tidak digunakan dihapus, semua resource yang tidak lagi direferensikan juga dapat dihapus dengan aman. Untuk mempelajari lebih lanjut, baca bagian tentang cara menyingkat resource.
  • Pengoptimalan: memeriksa dan menulis ulang kode Anda untuk meningkatkan performa runtime dan semakin mengurangi ukuran file DEX aplikasi Anda. Hal ini meningkatkan performa runtime kode hingga 30%, yang secara drastis meningkatkan waktu startup dan render frame. Misalnya, jika R8 mendeteksi bahwa cabang else {} untuk pernyataan if/else tertentu tidak pernah digunakan, R8 akan menghapus kode untuk cabang else {}. Untuk mempelajari lebih lanjut, buka bagian tentang pengoptimalan kode.
  • Obfuscation (atau minifikasi ID): mempersingkat nama class dan anggota, sehingga menghasilkan ukuran file DEX yang lebih kecil. Untuk mempelajari lebih lanjut, baca bagian tentang cara meng-obfuscate kode.

Saat mem-build versi rilis aplikasi, R8 dapat dikonfigurasi untuk menjalankan tugas waktu kompilasi yang dijelaskan di atas untuk Anda. Anda juga dapat menonaktifkan tugas tertentu atau menyesuaikan perilaku R8 melalui file aturan ProGuard. Faktanya, R8 berfungsi dengan semua file aturan ProGuard yang ada, jadi mengupdate plugin Android Gradle agar menggunakan R8 tidak akan mengharuskan Anda untuk mengubah aturan yang ada.

Mengaktifkan penyingkatan, obfuscation, dan pengoptimalan

Saat Anda menggunakan Android Studio 3.4 atau plugin Android Gradle 3.4.0 dan yang lebih baru, R8 adalah compiler default yang mengubah bytecode Java project Anda menjadi format DEX yang berjalan pada platform Android. Namun, saat Anda membuat project baru menggunakan Android Studio, penyingkatan, obfuscation, dan pengoptimalan kode tidak diaktifkan secara default. Hal itu karena pengoptimalan waktu kompilasi ini meningkatkan waktu build project dan dapat memunculkan bug jika Anda tidak menyesuaikan kode yang perlu dipertahankan secara memadai.

Jadi, sebaiknya aktifkan tugas-tugas waktu kompilasi ini saat membuat versi final aplikasi yang Anda uji sebelum dipublikasikan. Untuk mengaktifkan penyingkatan, obfuscation, dan pengoptimalan, sertakan yang berikut dalam skrip build level project Anda.

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `isDebuggable=false`.
            isMinifyEnabled = true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            isShrinkResources = true

            proguardFiles(
                // Includes the default ProGuard rules files that are packaged with
                // the Android Gradle plugin. To learn more, go to the section about
                // R8 configuration files.
                getDefaultProguardFile("proguard-android-optimize.txt"),

                // Includes a local, custom Proguard rules file
                "proguard-rules.pro"
            )
        }
    }
    ...
}

Groovy

android {
    buildTypes {
        release {
            // Enables code shrinking, obfuscation, and optimization for only
            // your project's release build type. Make sure to use a build
            // variant with `debuggable false`.
            minifyEnabled true

            // Enables resource shrinking, which is performed by the
            // Android Gradle plugin.
            shrinkResources true

            // Includes the default ProGuard rules files that are packaged with
            // the Android Gradle plugin. To learn more, go to the section about
            // R8 configuration files.
            proguardFiles getDefaultProguardFile(
                    'proguard-android-optimize.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}

File konfigurasi R8

R8 menggunakan file aturan ProGuard untuk mengubah perilaku defaultnya dan memahami struktur aplikasi Anda dengan lebih baik, seperti class yang berfungsi sebagai titik masuk ke dalam kode aplikasi Anda. Meskipun Anda dapat mengubah beberapa file aturan ini, beberapa aturan dapat otomatis dibuat oleh fitur waktu kompilasi, seperti AAPT2, atau diwarisi dari dependensi library aplikasi Anda. Tabel di bawah ini menjelaskan sumber file aturan ProGuard yang digunakan R8.

Sumber Lokasi Deskripsi
Android Studio <module-dir>/proguard-rules.pro Saat Anda membuat modul baru menggunakan Android Studio, IDE akan membuat file proguard-rules.pro di direktori utama dari modul tersebut.

Secara default, file ini tidak menerapkan aturan apa pun. Jadi, sertakan aturan ProGuard Anda sendiri di sini, seperti aturan keep kustom.

Plugin Android Gradle Dihasilkan oleh plugin Android Gradle pada waktu kompilasi. Plugin Android Gradle menghasilkan proguard-android-optimize.txt, yang menyertakan aturan yang berguna untuk sebagian besar project Android dan memungkinkan anotasi @Keep*.

Secara default, saat membuat modul baru menggunakan Android Studio, skrip build level modul akan menyertakan file aturan ini dalam build rilis untuk Anda.

Catatan: Plugin Android Gradle menyertakan file aturan ProGuard tambahan yang telah ditetapkan sebelumnya, tetapi sebaiknya Anda menggunakan proguard-android-optimize.txt.

Dependensi library

Di library AAR:
proguard.txt

Dalam library JAR:
META-INF/proguard/<ProGuard-rules-file>

Selain lokasi ini, plugin Android Gradle 3.6 atau yang lebih tinggi juga mendukung aturan penyingkatan yang ditargetkan.

Jika library AAR atau JAR dipublikasikan dengan file aturannya sendiri, dan Anda menyertakan library tersebut sebagai dependensi waktu kompilasi, R8 akan otomatis menerapkan aturan tersebut saat mengompilasi project Anda.

Selain aturan ProGuard konvensional, plugin Android Gradle 3.6 atau yang lebih tinggi juga mendukung aturan penyusutan yang ditargetkan. Ini adalah aturan yang menargetkan penyingkat tertentu (R8 atau ProGuard), serta versi penyingkat tertentu.

Penggunaan file aturan yang dikemas dengan library akan berguna jika aturan tertentu diperlukan agar library berfungsi dengan baik—yaitu, library yang langkah-langkah pemecahan masalahnya sudah diselesaikan oleh developer untuk Anda.

Namun, Anda harus memahami bahwa, karena aturan bersifat aditif, aturan tertentu yang disertakan dependensi library tidak dapat dihapus dan dapat memengaruhi kompilasi bagian lain dari aplikasi Anda. Misalnya, jika library menyertakan aturan untuk menonaktifkan pengoptimalan kode, aturan tersebut akan menonaktifkan pengoptimalan untuk seluruh project Anda.

Android Asset Package Tool 2 (AAPT2) Setelah membuat project dengan minifyEnabled true: <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt AAPT2 akan membuat aturan keep berdasarkan referensi ke class dalam manifes aplikasi, tata letak, dan resource aplikasi Anda lainnya. Misalnya, AAPT2 menyertakan aturan keep untuk setiap Aktivitas yang Anda daftarkan dalam manifes aplikasi Anda sebagai titik entri.
File konfigurasi kustom Secara default, saat membuat modul baru menggunakan Android Studio, IDE akan membuat <module-dir>/proguard-rules.pro agar Anda dapat menambahkan aturan Anda sendiri. Anda dapat menyertakan konfigurasi tambahan, dan R8 akan menerapkannya pada waktu kompilasi.

Saat Anda menetapkan properti minifyEnabled ke true, R8 akan menggabungkan aturan dari semua sumber yang tersedia yang tercantum di atas. Hal ini penting diingat saat Anda memecahkan masalah dengan R8, karena dependensi waktu kompilasi lainnya, seperti dependensi library, dapat memunculkan perubahan pada perilaku R8 yang tidak Anda ketahui.

Untuk meng-output laporan lengkap dari semua aturan yang diterapkan R8 saat membuat project Anda, sertakan baris berikut dalam file proguard-rules.pro modul Anda:

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Aturan penyingkatan yang ditargetkan

Plugin Android Gradle 3.6 atau yang lebih tinggi mendukung aturan library yang menargetkan penyingkat tertentu (R8 atau ProGuard), serta versi penyingkat tertentu. Hal ini memungkinkan developer library menyesuaikan aturan mereka agar dapat bekerja secara optimal dalam project yang menggunakan versi penyingkat baru, sekaligus mengizinkan aturan yang sudah ada untuk terus digunakan dalam project dengan versi penyingkat yang lebih lama.

Untuk menentukan aturan penyusutan yang ditargetkan, developer library harus menyertakannya di lokasi tertentu dalam library AAR atau JAR, seperti yang dijelaskan di bawah.

In an AAR library:
    proguard.txt (legacy location)
    classes.jar
    └── META-INF
        └── com.android.tools (targeted shrink rules location)
            ├── r8-from-<X>-upto-<Y>/<R8-rules-file>
            └── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rules-file> (legacy location)
    └── com.android.tools (targeted shrink rules location)
        ├── r8-from-<X>-upto-<Y>/<R8-rules-file>
        └── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>

Artinya, aturan penyingkatan yang ditargetkan disimpan di direktori META-INF/com.android.tools JAR atau di direktori META-INF/com.android.tools di dalam classes.jar AAR.

Di direktori tersebut, dapat ada beberapa direktori dengan nama dalam bentuk r8-from-<X>-upto-<Y> atau proguard-from-<X>-upto-<Y> untuk menunjukkan versi penyingkat yang aturannya ditulis di dalam direktori. Perhatikan bahwa bagian -from-<X> dan -upto-<Y> bersifat opsional, versi <Y> eksklusif, dan rentang versi harus berkelanjutan.

Misalnya, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0, dan r8-from-8.2.0 membentuk kumpulan aturan penyusutan yang ditargetkan yang valid. Aturan di direktori r8-from-8.0.0-upto-8.2.0 akan digunakan oleh R8 dari versi 8.0.0 hingga tetapi tidak termasuk versi 8.2.0.

Dengan informasi tersebut, plugin Android Gradle 3.6 atau yang lebih tinggi akan memilih aturan dari direktori R8 yang cocok. Jika library tidak menentukan aturan pengerutan yang ditargetkan, plugin Android Gradle akan memilih aturan dari lokasi lama (proguard.txt untuk AAR atau META-INF/proguard/<ProGuard-rules-file> untuk JAR).

Developer library dapat memilih untuk menyertakan aturan penyingkatan yang ditargetkan atau aturan ProGuard lama di library mereka, atau kedua jenis tersebut jika mereka ingin mempertahankan kompatibilitas dengan plugin Android Gradle yang lebih lama dari 3.6 atau alat lainnya.

Menyertakan konfigurasi tambahan

Saat Anda membuat project atau modul baru menggunakan Android Studio, IDE akan membuat file <module-dir>/proguard-rules.pro agar Anda dapat menyertakan aturan Anda sendiri. Anda juga dapat menyertakan aturan tambahan dari file lain dengan menambahkannya ke properti proguardFiles dalam skrip build modul Anda.

Misalnya, Anda dapat menambahkan aturan yang berlaku khusus untuk setiap varian build dengan menambahkan properti proguardFiles lain di blok productFlavor yang terkait. File Gradle berikut menambahkan flavor2-rules.pro ke ragam produk flavor2. Sekarang, flavor2 menggunakan ketiga aturan ProGuard karena aturan dari blok release juga diterapkan.

Selain itu, Anda dapat menambahkan properti testProguardFiles, yang menentukan daftar file ProGuard yang hanya disertakan dalam APK pengujian:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                "proguard-rules.pro"
            )
            testProguardFiles(
                // The proguard files listed here are included in the
                // test APK only.
                "test-proguard-rules.pro"
            )
        }
    }
    flavorDimensions.add("version")
    productFlavors {
        create("flavor1") {
            ...
        }
        create("flavor2") {
            proguardFile("flavor2-rules.pro")
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                'proguard-rules.pro'
            testProguardFiles
                // The proguard files listed here are included in the
                // test APK only.
                'test-proguard-rules.pro'
        }
    }
    flavorDimensions "version"
    productFlavors {
        flavor1 {
            ...
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

Menyingkat kode

Penyingkatan kode dengan R8 diaktifkan secara default saat Anda menetapkan properti minifyEnabled ke true.

Penyingkatan kode (disebut juga tree shaking) adalah proses menghapus kode yang menurut R8 tidak diperlukan selama runtime. Proses ini dapat mengurangi ukuran aplikasi Anda secara signifikan jika, misalnya, aplikasi Anda menyertakan banyak dependensi library tetapi hanya menggunakan sebagian kecil fungsinya.

Untuk menyingkat kode aplikasi Anda, pertama-tama R8 akan menentukan semua titik entri ke kode aplikasi Anda berdasarkan set file konfigurasi gabungan. Titik entri ini menyertakan semua class yang dapat digunakan platform Android untuk membuka Aktivitas atau layanan aplikasi Anda. Mulai dari setiap titik entri, R8 akan memeriksa kode aplikasi Anda untuk membuat grafik dari semua metode, variabel anggota, dan class lain yang mungkin diakses aplikasi Anda saat runtime. Kode yang tidak terhubung ke grafik tersebut dianggap tidak dapat diakses dan dapat dihapus dari aplikasi.

Gambar 1 menunjukkan aplikasi dengan dependensi library runtime. Selagi memeriksa kode aplikasi, R8 menyimpulkan bahwa metode foo(), faz(), dan bar() dapat diakses dari titik entri MainActivity.class. Namun, class OkayApi.class atau metode baz()-nya tidak pernah digunakan oleh aplikasi Anda pada runtime, dan R8 akan menghapus kode itu saat menyusutkan aplikasi Anda.

Gambar 1. Pada waktu kompilasi, R8 membuat grafik berdasarkan gabungan aturan keep project Anda untuk menentukan kode yang tidak dapat diakses.

R8 menentukan titik entri melalui aturan -keep dalam file konfigurasi R8 project. Artinya, aturan keep menentukan class yang tidak boleh dihapus oleh R8 saat menyusutkan aplikasi Anda, dan R8 menganggap class tersebut sebagai titik entri ke dalam aplikasi Anda. Plugin Android Gradle dan AAPT2 otomatis menghasilkan aturan keep yang diperlukan oleh sebagian besar project aplikasi, seperti aktivitas, tampilan, dan layanan aplikasi Anda. Namun, jika Anda perlu menyesuaikan perilaku default ini dengan aturan keep tambahan, baca bagian tentang cara menyesuaikan kode yang perlu dipertahankan.

Jika Anda hanya tertarik untuk mengurangi ukuran resource aplikasi, langsung baca cara menyingkat resource.

Perhatikan bahwa jika project library diciutkan, aplikasi yang bergantung pada library tersebut akan menyertakan class library yang diciutkan. Anda mungkin perlu menyesuaikan aturan keep library jika ada class yang hilang di APK library. Jika Anda mem-build dan memublikasikan library dalam format AAR, file JAR lokal yang menjadi dependensi library Anda tidak diperkecil dalam file AAR.

Menyesuaikan kode yang perlu dipertahankan

Dalam sebagian besar situasi, file aturan ProGuard default (proguard-android-optimize.txt) sudah cukup bagi R8 untuk hanya menghapus kode yang tidak digunakan. Namun, beberapa situasi sulit dianalisis dengan benar oleh R8 dan akibatnya R8 mungkin menghapus kode yang sebenarnya diperlukan aplikasi Anda. Beberapa contoh di mana R8 mungkin salah menghapus kode antara lain:

  • Jika aplikasi Anda memanggil metode dari Java Native Interface (JNI)
  • Jika aplikasi Anda mencari kode saat runtime (seperti dengan refleksi)

Pengujian aplikasi akan menunjukkan error yang disebabkan oleh kode yang salah dihapus, tetapi Anda juga dapat memeriksa kode apa yang telah dihapus dengan membuat laporan kode yang dihapus.

Untuk memperbaiki error dan memaksa R8 agar mempertahankan kode tertentu, tambahkan baris -keep dalam file aturan ProGuard. Contoh:

-keep public class MyClass

Atau, Anda dapat menambahkan anotasi @Keep ke kode yang ingin dipertahankan. Menambahkan @Keep pada suatu class akan mempertahankan seluruh class tersebut apa adanya. Menambahkannya pada suatu metode atau kolom akan mempertahankan metode/kolom tersebut (dan namanya) serta nama class-nya tetap utuh. Perhatikan bahwa anotasi ini hanya tersedia saat menggunakan AndroidX Annotations Library dan saat Anda menyertakan file aturan ProGuard yang dikemas dengan plugin Android Gradle, seperti dijelaskan di bagian tentang cara mengaktifkan penyingkatan.

Ada banyak pertimbangan yang harus Anda buat saat menggunakan opsi -keep; untuk informasi selengkapnya tentang menyesuaikan file aturan, baca Panduan ProGuard. Bagian Pemecahan Masalah dalam panduan tersebut menjelaskan masalah umum lain yang mungkin Anda alami jika kode dihapus.

Menghapus library native

Secara default, library kode native dihilangkan di build rilis aplikasi Anda. Pembersihan ini terdiri dari penghapusan tabel simbol dan informasi proses debug yang dimuat dalam library native yang digunakan oleh aplikasi Anda. Pembersihan library kode native menghasilkan penghematan ukuran yang signifikan; tetapi, Anda tidak dapat mendiagnosis error pada Konsol Google Play karena ada informasi yang tidak ada (seperti nama class dan fungsi).

Dukungan untuk masalah pada native code

Konsol Google Play melaporkan masalah pada native code di Android vitals. Dengan beberapa langkah, Anda dapat membuat dan mengupload file simbol debug native untuk aplikasi Anda. File ini memungkinkan pelacakan tumpukan error native tersimbolkan (yang mencakup nama fungsi dan class) di Android vitals untuk membantu Anda men-debug aplikasi dalam produksi. Langkah-langkah ini bervariasi bergantung pada versi plugin Android Gradle yang digunakan dalam project Anda dan output build project Anda.

Versi Plugin Android Gradle: 4.1 atau yang lebih baru

Jika project mem-build Android App Bundle, Anda dapat otomatis menyertakan file simbol debug native di dalamnya. Untuk menyertakan file ini dalam build rilis, tambahkan hal berikut ke file build.gradle.kts aplikasi Anda:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Pilih level simbol debug berikut ini:

  • Gunakan SYMBOL_TABLE untuk mendapatkan nama fungsi di pelacakan tumpukan yang disimbolkan Konsol Play. Level ini mendukung tombstone.
  • Gunakan FULL untuk mendapatkan nama fungsi, file, dan nomor baris dalam pelacakan tumpukan yang disimbolkan Konsol Play.

Jika project Anda mem-build APK, gunakan setelan build build.gradle.kts yang ditampilkan di awal untuk membuat file simbol debug native secara terpisah. Mengupload file simbol debug native secara manual ke Konsol Google Play. Sebagai bagian dari proses build, plugin Android Gradle akan menampilkan file ini pada lokasi project berikut:

app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip

Plugin Android Gradle versi 4.0 atau yang lebih lama (dan sistem build lainnya)

Sebagai bagian dari proses build, plugin Android Gradle menyimpan salinan library simbolik dalam direktori project. Struktur direktori ini mirip dengan yang berikut:

app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── arm64-v8a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── x86/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
└── x86_64/
    ├── libgameengine.so
    ├── libothercode.so
    └── libvideocodec.so
  1. Gunakan konten direktori ini:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Upload file symbols.zip secara manual ke Konsol Google Play.

Menyingkat resource

Penyingkatan resource hanya dapat dilakukan bersama dengan penyingkatan kode. Setelah penyingkat kode menghapus semua kode yang tidak terpakai, penyingkat resource dapat mengidentifikasi resource mana saja yang masih digunakan aplikasi. Hal ini terjadi terutama saat Anda menambahkan library kode yang menyertakan resource—Anda harus menghapus kode library yang tidak digunakan sehingga resource library tersebut menjadi tidak dirujuk dan, dengan demikian, dapat dihapus oleh penyingkat resource.

Untuk mengaktifkan penyingkatan resource, tetapkan properti shrinkResources ke true dalam skrip build Anda (beserta minifyEnabled untuk penyingkatan kode). Contoh:

Kotlin

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Groovy

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android.txt'),
                'proguard-rules.pro'
        }
    }
}

Jika Anda belum membuat aplikasi menggunakan minifyEnabled untuk penyingkatan kode, cobalah itu sebelum mengaktifkan shrinkResources, karena Anda mungkin perlu mengedit file proguard-rules.pro untuk mempertahankan class atau metode yang dibuat atau dipanggil secara dinamis sebelum Anda mulai menghapus resource.

Menyesuaikan resource yang perlu dipertahankan

Jika ada resource tertentu yang ingin dipertahankan atau dihapus, buatlah file XML dalam project dengan tag <resources>, lalu tentukan setiap resource yang ingin dipertahankan dalam atribut tools:keep dan setiap resource yang ingin dihapus dalam atribut tools:discard. Kedua atribut ini menerima daftar nama resource yang dipisahkan koma. Anda dapat menggunakan karakter tanda bintang sebagai karakter pengganti.

Contoh:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

Simpan file ini dalam resource project, misalnya, di res/raw/my.package.keep.xml. Build tidak memaketkan file ini ke dalam aplikasi Anda.

Catatan: Pastikan untuk menggunakan nama unik untuk file keep. Jika library yang berbeda ditautkan bersama, aturan simpannya akan bertentangan dengan aturan yang diabaikan atau resource simpan yang tidak diperlukan.

Menentukan resource yang akan dihapus mungkin tampak aneh karena sebenarnya Anda bisa langsung menghapusnya, tetapi cara ini berguna saat Anda menggunakan varian build. Misalnya, Anda dapat menempatkan semua resource ke dalam direktori project umum, lalu membuat file my.package.build.variant.keep.xml yang berbeda untuk setiap varian build saat Anda mengetahui bahwa resource tertentu tampaknya digunakan dalam kode (dan karenanya tidak dihapus oleh penyingkat), tetapi Anda tahu bahwa resource tersebut sebenarnya tidak akan digunakan untuk varian build tertentu. Mungkin juga alat build salah mengidentifikasi resource sesuai kebutuhan, yang mungkin terjadi karena compiler menambahkan ID resource secara inline, lalu penganalisis resource mungkin tidak mengetahui perbedaan antara resource yang benar-benar direferensikan dengan nilai bilangan bulat dalam kode yang kebetulan memiliki nilai yang sama.

Mengaktifkan pemeriksaan referensi yang ketat

Biasanya, penyingkat resource dapat mengetahui secara akurat saat sebuah resource digunakan. Akan tetapi, jika kode Anda membuat panggilan ke Resources.getIdentifier() (atau jika salah satu library Anda melakukannya—library AppCompat biasanya melakukan panggilan ini), hal itu berarti kode Anda menelusuri nama resource berdasarkan string yang dihasilkan secara dinamis. Saat Anda melakukan ini, secara default penyingkat resource akan berperilaku defensif dan menandai semua resource dengan format nama yang cocok sebagai berpotensi digunakan dan tidak boleh dihapus.

Misalnya, kode berikut menyebabkan semua resource dengan awalan img_ ditandai sebagai digunakan.

Kotlin

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Java

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

Penyingkat resource juga memeriksa semua konstanta string dalam kode Anda, serta berbagai resource res/raw/, untuk menemukan URL resource dalam format yang mirip dengan file:///android_res/drawable//ic_plus_anim_016.png. Jika menemukan string seperti ini atau string lain yang tampaknya dapat digunakan untuk membuat URL seperti ini, maka penyingkat tidak akan menghapusnya.

Berikut ini contoh dari mode penyingkatan aman yang diaktifkan secara default. Namun, Anda dapat menonaktifkan penanganan "lebih baik mencegah daripada mengobati" ini, dan menentukan bahwa penyingkat resource hanya mempertahankan resource yang benar-benar digunakan. Caranya, tetapkan shrinkMode ke strict dalam file keep.xml, seperti berikut:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

Jika mengaktifkan mode penyingkatan ketat dan kode Anda juga merujuk resource dengan string yang dihasilkan secara dinamis, seperti yang ditampilkan di atas, maka Anda harus secara manual mempertahankan resource menggunakan atribut tools:keep.

Menghapus resource alternatif yang tidak digunakan

Penyingkat resource Gradle hanya akan menghapus resource yang tidak direferensikan oleh kode aplikasi Anda, yang berarti penyingkat tidak akan menghapus resource alternatif untuk konfigurasi perangkat yang berbeda. Jika perlu, Anda dapat menggunakan properti resConfigs plugin Android Gradle untuk menghapus file resource alternatif yang tidak diperlukan aplikasi.

Misalnya, jika Anda menggunakan library yang menyertakan resource bahasa (seperti AppCompat atau Layanan Google Play), maka aplikasi Anda akan menyertakan semua string bahasa yang diterjemahkan untuk pesan di library tersebut, tanpa melihat apakah bagian lain aplikasi Anda diterjemahkan ke bahasa yang sama atau tidak. Jika hanya ingin mempertahankan bahasa yang resmi didukung aplikasi, Anda dapat menentukannya menggunakan properti resConfig. Setiap resource bahasa yang tidak ditentukan akan dihapus.

Cuplikan berikut menampilkan cara membatasi resource bahasa hanya ke bahasa Inggris dan Prancis:

Kotlin

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

Groovy

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

Saat merilis aplikasi menggunakan format Android App Bundle, secara default hanya bahasa yang dikonfigurasi pada perangkat pengguna yang akan didownload saat menginstal aplikasi. Demikian pula, hanya resource yang cocok dengan kepadatan layar perangkat, dan library native yang cocok dengan ABI perangkat, yang disertakan dalam download. Untuk mengetahui informasi selengkapnya, lihat Konfigurasi Android App Bundle.

Untuk aplikasi lama yang dirilis dengan APK (dibuat sebelum Agustus 2021), Anda dapat menyesuaikan kepadatan layar atau resource ABI yang akan disertakan dalam APK dengan mem-build beberapa APK yang masing-masing menargetkan konfigurasi perangkat yang berbeda.

Menggabungkan resource duplikat

Secara default, Gradle juga menggabungkan resource bernama identik, seperti drawable dengan nama sama yang mungkin berada dalam folder resource berbeda. Perilaku ini tidak dikendalikan oleh properti shrinkResources dan tidak dapat dinonaktifkan, karena diperlukan untuk mencegah error saat terdapat beberapa resource yang namanya sama dengan nama kode yang Anda cari.

Penggabungan resource hanya terjadi saat dua atau beberapa file menggunakan nama, jenis, dan penentu resource yang sama. Gradle memilih file mana yang dianggap sebagai pilihan terbaik di antara duplikat tersebut (berdasarkan urutan prioritas yang dijelaskan di bawah) dan meneruskan hanya satu resource ke AAPT untuk didistribusikan dalam artefak final.

Gradle mencari resource duplikat di lokasi berikut:

  • Resource utama, yang terkait dengan set sumber utama, biasanya terletak di src/main/res/.
  • Overlay varian, dari jenis build dan ragam build.
  • Dependensi project library.

Gradle menggabungkan resource duplikat dalam urutan prioritas berikut:

Dependensi → Utama → Ragam build → Jenis build

Misalnya, jika sebuah resource duplikat muncul di resource utama dan ragam build, maka Gradle akan memilih resource yang berada di ragam build.

Jika resource identik muncul dalam set sumber yang sama, Gradle tidak dapat menggabungkannya dan akan mengeluarkan error penggabungan resource. Hal ini dapat terjadi jika Anda menentukan beberapa set sumber di properti sourceSet dari file build.gradle.kts—misalnya jika src/main/res/ dan src/main/res2/ berisi resource yang sama.

Meng-obfuscate kode

Tujuan obfuscation adalah mengurangi ukuran aplikasi dengan mempersingkat nama class, metode, dan kolom aplikasi Anda. Berikut ini adalah contoh obfuscation menggunakan R8:

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
    android.content.Context mContext -> a
    int mListItemLayout -> O
    int mViewSpacingRight -> l
    android.widget.Button mButtonNeutral -> w
    int mMultiChoiceItemLayout -> M
    boolean mShowTitle -> P
    int mViewSpacingLeft -> j
    int mButtonPanelSideLayout -> K

Meskipun obfuscation tidak menghapus kode dari aplikasi Anda, penghematan ukuran yang signifikan dapat dicapai dalam aplikasi dengan file DEX yang mengindeks banyak class, metode, dan kolom. Namun, karena obfuscation mengubah nama berbagai bagian kode, tugas tertentu, seperti inspeksi pelacakan tumpukan, memerlukan fitur tambahan. Untuk memahami pelacakan tumpukan Anda setelah obfuscation, bacalah bagian tentang cara mendekode pelacakan tumpukan yang di-obfuscate.

Selain itu, jika kode Anda mengandalkan penamaan yang mudah diprediksi untuk metode dan class aplikasi Anda—saat menggunakan refleksi, misalnya, Anda harus memperlakukan tanda tangan tersebut sebagai titik entri dan menetapkan aturan keep untuknya, seperti dijelaskan di bagian cara menyesuaikan kode yang perlu dipertahankan. Aturan keep tersebut memberi tahu R8 untuk tidak hanya mempertahankan kode itu dalam DEX akhir aplikasi Anda, tetapi juga mempertahankan penamaan aslinya.

Mendekode pelacakan tumpukan yang di-obfuscate

Setelah R8 meng-obfuscate kode Anda, pelacakan tumpukan akan sulit (jika tidak mustahil) dipahami karena nama-nama class dan metode mungkin telah berubah. Untuk mendapatkan pelacakan tumpukan asli, Anda harus melakukan retrace pelacakan tumpukan.

Pengoptimalan kode

Untuk mengoptimalkan aplikasi Anda lebih lanjut, R8 akan memeriksa kode Anda di tingkat yang lebih dalam untuk menghapus kode yang tidak terpakai atau, jika mungkin, menulis ulang kode Anda agar tidak terlalu panjang. Berikut ini beberapa contoh pengoptimalan tersebut:

  • Jika kode Anda tidak pernah mengambil cabang else {} untuk pernyataan if/else tertentu, R8 mungkin akan menghapus kode untuk cabang else {}.
  • Jika kode Anda memanggil metode hanya di beberapa tempat, R8 mungkin akan menghapus metode itu dan menyisipkannya secara inline di beberapa situs panggilan.
  • Jika R8 menentukan bahwa sebuah class hanya memiliki satu subclass unik, dan class itu sendiri tidak dipakai (misalnya, class dasar abstrak hanya digunakan oleh satu class implementasi konkret), maka R8 dapat menggabungkan dua class dan menghapus class dari aplikasi.
  • Untuk mempelajari lebih lanjut, baca postingan blog pengoptimalan R8 oleh Jake Wharton.

R8 tidak memungkinkan Anda menonaktifkan atau mengaktifkan pengoptimalan terpisah, atau mengubah perilaku pengoptimalan. Bahkan, R8 mengabaikan semua aturan ProGuard yang mencoba mengubah pengoptimalan default, seperti -optimizations dan -optimizationpasses. Pembatasan ini penting karena, seiring berkembangnya R8, mempertahankan perilaku standar untuk pengoptimalan akan membantu tim Android Studio memecahkan masalah dan menyelesaikan masalah apa pun yang mungkin Anda temui dengan mudah.

Perhatikan bahwa mengaktifkan pengoptimalan akan mengubah pelacakan tumpukan untuk aplikasi. Misalnya, inline akan menghapus frame stack. Lihat bagian tentang retrace untuk mempelajari cara mendapatkan pelacakan tumpukan asli.

Dampak terhadap performa runtime

Jika penyingkatan, obfuscation, dan pengoptimalan diaktifkan, R8 akan meningkatkan performa runtime kode (termasuk waktu startup dan frame pada UI thread) hingga 30%. Menonaktifkan salah satu dari hal ini akan sangat membatasi kumpulan pengoptimalan yang digunakan R8.

Jika R8 diaktifkan, Anda juga harus membuat Profil Startup untuk performa startup yang lebih baik.

Mengaktifkan pengoptimalan yang ditingkatkan

R8 menyertakan serangkaian pengoptimalan tambahan (disebut sebagai "mode penuh") yang membuatnya berperilaku berbeda dengan ProGuard. Pengoptimalan ini diaktifkan secara default sejak plugin Android Gradle versi 8.0.0.

Anda dapat menonaktifkan pengoptimalan tambahan ini dengan menyertakan baris berikut dalam file gradle.properties project:

android.enableR8.fullMode=false

Karena pengoptimalan tambahan membuat R8 berperilaku berbeda dengan ProGuard, Anda mungkin harus menyertakan aturan ProGuard tambahan guna menghindari masalah runtime jika Anda menggunakan aturan yang dirancang untuk ProGuard. Misalnya, misalkan kode Anda mereferensikan class melalui Java Reflection API. Jika tidak menggunakan "mode penuh", R8 akan berasumsi bahwa Anda bermaksud memeriksa dan memanipulasi objek class tersebut selama runtime—meskipun kode Anda sebenarnya tidak melakukannya—dan secara otomatis mempertahankan class tersebut dan penginisialisasi statisnya.

Namun, saat menggunakan "mode penuh", R8 tidak membuat asumsi ini dan, jika R8 menyatakan bahwa kode Anda tidak pernah menggunakan class pada runtime, R8 akan menghapus class dari DEX akhir aplikasi Anda. Artinya, jika ingin mempertahankan class dan inisialisasi statisnya, Anda harus menyertakan aturan keep dalam file aturan untuk melakukannya.

Jika Anda mengalami masalah saat menggunakan "mode penuh" R8, lihat halaman FAQ R8 untuk menemukan solusi yang mungkin. Jika Anda tidak dapat menyelesaikan masalah ini, harap laporkan bug.

Retrace pelacakan tumpukan

Kode yang diproses oleh R8 diubah dengan berbagai cara yang dapat membuat pelacakan tumpukan lebih sulit dipahami karena pelacakan tumpukan tidak akan sama persis dengan kode sumber. Hal ini dapat terjadi pada perubahan nomor baris saat informasi proses debug tidak disimpan. Hal ini dapat disebabkan oleh pengoptimalan seperti inline dan outline. Kontributor terbesar adalah obfuscation yang bahkan nama class dan metodenya akan berubah.

Untuk memulihkan pelacakan tumpukan yang asli, R8 menyediakan alat command line retrace, yang dipaketkan dengan paket alat command line.

Untuk mendukung retrace pelacakan tumpukan aplikasi, Anda harus memastikan bahwa build menyimpan informasi yang cukup untuk melakukan retrace dengan menambahkan aturan berikut ke file proguard-rules.pro modul Anda:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

Atribut LineNumberTable menyimpan informasi posisi dalam metode sehingga posisi tersebut dicetak dalam pelacakan tumpukan. Atribut SourceFile memastikan semua runtime potensial benar-benar mencetak info posisi. Perintah -renamesourcefileattribute menetapkan nama file sumber dalam pelacakan tumpukan hanya ke SourceFile. Nama file sumber asli yang sebenarnya tidak diperlukan saat melakukan retrace karena file pemetaan berisi file sumber asli.

R8 membuat file mapping.txt setiap kali dijalankan, yang berisi informasi yang diperlukan untuk memetakan pelacakan tumpukan kembali ke pelacakan tumpukan awal. Android Studio menyimpan file ini dalam direktori <module-name>/build/outputs/mapping/<build-type>/.

Saat memublikasikan aplikasi di Google Play, Anda dapat mengupload file mapping.txt untuk setiap versi aplikasi Anda. Saat memublikasikan menggunakan Android App Bundle, file ini disertakan secara otomatis sebagai bagian dari konten app bundle. Selanjutnya, Google Play akan melakukan retrace pelacakan tumpukan yang masuk dari masalah yang dilaporkan pengguna, sehingga Anda dapat meninjaunya di Konsol Play. Untuk mengetahui informasi selengkapnya, lihat artikel Pusat Bantuan tentang cara men-deobfuscate pelacakan tumpukan error.

Memecahkan masalah dengan R8

Bagian ini menjelaskan beberapa strategi untuk mengatasi masalah saat mengaktifkan penyingkatan, obfuscation, dan pengoptimalan menggunakan R8. Jika tidak dapat menemukan solusi untuk masalah Anda di bawah ini, baca juga halaman FAQ R8 dan panduan pemecahan masalah ProGuard.

Membuat laporan kode yang dihapus (atau dipertahankan)

Untuk membantu Anda memecahkan masalah R8 tertentu, sebaiknya lihat laporan semua kode yang dihapus R8 dari aplikasi Anda. Untuk setiap modul yang ingin Anda peroleh laporannya, tambahkan -printusage <output-dir>/usage.txt ke file aturan kustom Anda. Saat Anda mengaktifkan R8 dan membuat aplikasi, R8 akan meng-output laporan yang berisi jalur dan nama file yang Anda tentukan. Laporan kode yang dihapus terlihat mirip dengan berikut ini:

androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
    public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
    public boolean hasWindowFeature(int)
    public void setHandleNativeActionModesEnabled(boolean)
    android.view.ViewGroup getSubDecor()
    public void setLocalNightMode(int)
    final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
    public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
    private static final boolean DEBUG
    private static final java.lang.String KEY_LOCAL_NIGHT_MODE
    static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...

Jika Anda ingin melihat laporan titik entri yang ditentukan R8 dari aturan keep project Anda, sertakan -printseeds <output-dir>/seeds.txt di file aturan kustom Anda. Saat Anda mengaktifkan R8 dan membuat aplikasi, R8 akan meng-output laporan yang berisi jalur dan nama file yang Anda tentukan. Laporan titik entri yang dipertahankan terlihat mirip dengan berikut ini:

com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...

Memecahkan masalah penyingkatan resource

Saat Anda menyingkatkan resource, jendela Build akan menampilkan ringkasan resource yang dihapus dari aplikasi. (Anda harus mengklik Toggle view di sisi kiri jendela terlebih dahulu untuk menampilkan output teks mendetail dari Gradle.) Contoh:

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle juga membuat file diagnostik bernama resources.txt di <module-name>/build/outputs/mapping/release/ (folder yang sama dengan file output ProGuard). File ini menyertakan detail seperti resource mana yang mereferensikan resource lain, dan resource mana yang digunakan atau dihapus.

Misalnya, untuk mengetahui alasan @drawable/ic_plus_anim_016 masih berada dalam aplikasi, buka file resources.txt dan telusuri nama file tersebut. Anda mungkin mendapati bahwa file itu direferensikan dari resource lain, seperti berikut:

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     @drawable/ic_plus_anim_016

Sekarang Anda perlu mengetahui alasan @drawable/add_schedule_fab_icon_anim dapat diakses—dan jika mencari ke atas, Anda akan menemukan resource tersebut dicantumkan pada "The root reachable resources are:". Ini berarti terdapat referensi kode ke add_schedule_fab_icon_anim (artinya, R.drawable ID-nya ditemukan dalam kode yang dapat diakses).

Jika Anda tidak menggunakan pemeriksaan ketat, ID resource dapat ditandai sebagai dapat diakses jika ada konstanta string yang tampaknya dapat digunakan untuk membuat nama resource bagi resource yang dimuat secara dinamis. Dalam hal ini, jika mencari output build untuk nama resource tersebut, Anda mungkin menemukan pesan seperti ini:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because it format-string matches string pool constant ic_plus_anim_%1$d.

Jika melihat salah satu string ini dan yakin bahwa string tersebut tidak digunakan untuk memuat resource yang diberikan secara dinamis, Anda dapat menggunakan atribut tools:discard untuk memberi tahu sistem build agar menghapusnya, seperti yang dijelaskan di bagian tentang cara menyesuaikan resource yang perlu dipertahankan.