Menyusutkan, meng-obfuscate, dan mengoptimalkan aplikasi Anda

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

Tindakan ini akan mengaktifkan penyingkatan, yang akan menghapus kode dan resource yang tidak digunakan; obfuscation, yang mempersingkat nama class dan anggota aplikasi; dan pengoptimalan, yang menerapkan strategi yang lebih agresif untuk lebih mengurangi ukuran dan meningkatkan performa aplikasi Anda. Halaman ini menjelaskan bagaimana R8 melakukan tugas-tugas waktu kompilasi ini untuk proyek Anda dan bagaimana Anda dapat menyesuaikan mereka.

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 runtime dan mengurangi ukuran file DEX aplikasi Anda. Ini meningkatkan kinerja {i>runtime<i} kode hingga 30%, meningkat secara drastis waktu mulai dan waktu 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, yang menghasilkan ukuran file DEX yang lebih kecil. Untuk mempelajari lebih lanjut, buka bagian tentang cara meng-obfuscate kode.

Saat membangun versi rilis aplikasi, R8 dapat dikonfigurasi untuk menjalankan tugas-tugas waktu kompilasi yang dijelaskan di atas untuk Anda. Anda juga dapat menonaktifkan tugas 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 ini 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, level modul akan skrip build menyertakan file aturan ini dalam build rilis Anda keamanan 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

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

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

Jika library AAR atau JAR dipublikasikan dengan file aturannya sendiri, dan Anda menyertakan library tersebut sebagai dependensi waktu kompilasi, R8 secara otomatis menerapkan aturan-aturan itu ketika mengompilasi proyek 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 khusus (R8 atau ProGuard), serta versi penyingkat.

Menggunakan file aturan yang dikemas dengan pustaka akan berguna jika suatu aturan diperlukan agar library berfungsi dengan baik—yaitu, library pengembang telah melakukan langkah pemecahan masalah untuk Anda.

Namun, Anda harus mengetahui bahwa karena aturan ini bersifat aditif, aturan tertentu yang disertakan oleh dependensi library tidak dapat dihapus dan mungkin memengaruhi kompilasi bagian lain aplikasi Anda. Misalnya, jika mencakup aturan untuk menonaktifkan pengoptimalan kode, yaitu aturan yang menonaktifkan pengoptimalan untuk seluruh proyek.

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 penyusutan yang ditargetkan

Plugin Android Gradle 3.6 atau yang lebih tinggi mendukung library aturan yang menargetkan penyingkat khusus (R8 atau ProGuard), serta versi penyingkat tertentu. Ini memungkinkan developer library menyesuaikan aturan mereka agar berfungsi secara optimal dalam project yang menggunakan versi penyingkat baru, sekaligus mengizinkan aturan yang sudah ada digunakan dalam proyek-proyek 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 penyusutan yang ditargetkan disimpan di META-INF/com.android.tools direktori JAR atau di direktori META-INF/com.android.tools di dalam classes.jar AAR.

Di bawah direktori itu, bisa ada beberapa direktori dengan nama dalam formulir dari r8-from-<X>-upto-<Y> atau proguard-from-<X>-upto-<Y> untuk menunjukkan versi yang penyingkat aturan di dalam direktori yang dibuat. Perhatikan bahwa bagian -from-<X> dan -upto-<Y> bersifat opsional, sedangkan versi <Y> bersifat eksklusif, dan rentang versinya 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. Aturan yang tercakup dalam 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 target aturan penyusutan, plugin Android Gradle akan memilih aturan dari lokasi (proguard.txt untuk AAR atau META-INF/proguard/<ProGuard-rules-file> untuk JAR).

Developer library dapat memilih untuk menyertakan aturan penyusutan yang ditargetkan atau aturan lama Aturan ProGuard dalam library mereka, atau kedua jenis tersebut jika ingin dipertahankan kompatibilitas dengan plugin Android Gradle yang lebih lama dari 3.6 atau fitur 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 dapat juga menyertakan aturan tambahan dari file lain dengan menambahkannya ke proguardFiles di 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 disertakan dalam APK pengujian saja:

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 menyusut, aplikasi yang bergantung pada library tersebut mencakup class library yang menyusut. Anda mungkin perlu menyesuaikan aturan {i>library<i} jika ada class yang hilang dalam APK library. Jika Anda membangun dan memublikasikan library dalam format AAR, file JAR lokal yang tidak menjadi dependensi library Anda dikecilkan dalam file AAR.

Menyesuaikan kode yang perlu dipertahankan

Untuk sebagian besar situasi, file aturan ProGuard default (proguard-android-optimize.txt) cukup bagi R8 untuk menghapus kode yang tidak digunakan saja. 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 di skrip build Anda (bersama 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 Anda menggunakan nama yang unik untuk file keep. Kapan {i>library<i} yang berbeda saling terhubung, aturan keep mereka akan bertentangan sebaliknya, menyebabkan potensi masalah dengan aturan yang diabaikan atau aturan yang tidak diperlukan Google Cloud Platform.

Menentukan resource yang akan dihapus mungkin tampak aneh karena sebenarnya Anda bisa langsung menghapusnya, tetapi cara ini berguna saat Anda menggunakan varian build. Sebagai misalnya, Anda dapat menempatkan semua resource ke dalam direktori proyek umum, lalu buat file my.package.build.variant.keep.xml yang berbeda untuk masing-masing ketika Anda mengetahui bahwa sumber daya tertentu tampaknya digunakan dalam kode (dan karenanya tidak dihapus oleh penyingkat) tetapi Anda tahu bahwa sebenarnya tidak yang digunakan untuk varian build yang ditentukan. Mungkin juga alat build salah mengidentifikasi sumber daya sebagai yang dibutuhkan, yang mungkin terjadi karena compiler menambahkan ID resource inline, lalu penganalisis resource mungkin tidak ketahui perbedaan antara sumber daya yang benar-benar direferensikan dan 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 jauh lagi, R8 akan memeriksa kode Anda secara lebih mendalam untuk menghapus lebih banyak kode yang tidak terpakai atau, jika memungkinkan, menulis ulang kode Anda untuk jadi lebih singkat. 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 menghapus metode tersebut dan memasukkannya 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 setiap aturan ProGuard yang mencoba untuk 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 pada performa runtime

Jika penyingkatan, obfuscation, dan pengoptimalan diaktifkan, R8 akan meningkatkan performa runtime kode (termasuk waktu startup dan frame di UI thread) hingga 30%. Menonaktifkan salah satunya secara drastis membatasi serangkaian pengoptimalan yang digunakan R8.

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

Mengaktifkan pengoptimalan yang lebih agresif

R8 mencakup serangkaian pengoptimalan tambahan (disebut sebagai "mode penuh") yang membuatnya berperilaku berbeda dari ProGuard. Pengoptimalan ini diaktifkan oleh default sejak Plugin Android Gradle versi 8.0.0.

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

android.enableR8.fullMode=false

Karena pengoptimalan tambahan membuat R8 berperilaku berbeda dari ProGuard, mereka mungkin mengharuskan Anda menyertakan aturan ProGuard tambahan untuk menghindari runtime jika Anda menggunakan aturan yang dirancang untuk ProGuard. Misalnya, kode mereferensikan class melalui Java Reflection API. Jika tidak menggunakan "mode penuh," R8 berasumsi bahwa Anda bermaksud untuk memeriksa dan memanipulasi class tersebut saat runtime—meskipun kode Anda sebenarnya tidak—dan secara otomatis mempertahankan class dan penginisialisasi statisnya.

Namun, ketika menggunakan "mode penuh", R8 tidak membuat asumsi ini dan, jika R8 menegaskan bahwa jika tidak, kode Anda tidak akan pernah menggunakan class pada runtime, hal ini akan menghapus dari DEX akhir aplikasi Anda. Artinya, jika Anda ingin mempertahankan class dan penginisialisasi statis, Anda harus menyertakan aturan pertahanan dalam file aturan agar itu.

Jika Anda mengalami masalah saat menggunakan "mode penuh" R8, lihat Halaman FAQ R8 solusi yang memungkinkan. 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.