Menyusutkan, meng-obfuscate, dan mengoptimalkan aplikasi Anda

Untuk mencapai ukuran aplikasi yang sekecil mungkin, Anda harus mengaktifkan penyusutan dalam build rilis untuk menghapus kode dan resource yang tidak digunakan. Dengan mengaktifkan penyusutan, Anda juga mendapatkan manfaat dari obfuscation, yang memperpendek nama class dan anggota aplikasi Anda, dan pengoptimalan, yang menerapkan strategi lebih agresif untuk semakin mengurangi ukuran aplikasi. Halaman ini menjelaskan bagaimana R8 melakukan tugas-tugas waktu kompilasi ini untuk project Anda dan bagaimana Anda dapat menyesuaikannya.

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

  • Penyusutan 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 fitur yang berharga untuk mengatasi batasan referensi 64k). Misalnya, jika Anda hanya menggunakan beberapa API untuk sebuah dependensi library, penyusutan dapat mengidentifikasi kode library yang tidak digunakan aplikasi Anda dan menghapus kode itu saja dari aplikasi Anda. Untuk mempelajari lebih lanjut, baca bagian tentang cara menyusutkan kode.
  • Penyusutan resource: menghapus resource yang tidak digunakan dari aplikasi yang dipaketkan, termasuk resource yang tidak digunakan dalam dependensi library aplikasi Anda. Ini berfungsi bersamaan dengan penyusutan 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 menyusutkan resource.
  • Obfuscation: mempersingkat nama class dan anggota, sehingga menghasilkan ukuran file DEX yang lebih kecil. Untuk mempelajari lebih lanjut, baca bagian tentang cara meng-obfuscate kode.
  • Pengoptimalan: memeriksa dan menulis ulang kode Anda untuk semakin mengurangi ukuran file DEX aplikasi Anda. Misalnya, jika R8 mendeteksi bahwa cabang else {} untuk pernyataan if/else tertentu tidak pernah digunakan, R8 akan menghapus kode tersebut untuk cabang else {}. Untuk mempelajari lebih lanjut, baca bagian tentang pengoptimalan kode.

Saat membuat versi rilis aplikasi Anda, secara default, R8 akan menjalankan tugas-tugas kompilasi yang dijelaskan di atas secara otomatis. Namun, Anda dapat menonaktifkan tugas tertentu atau menyesuaikan perilaku R8 melalui file aturan ProGuard. Faktanya, R8 berfungsi dengan semua file aturan ProGuard yang ada, jadi mengupdate Android Gradle Plugin agar menggunakan R8 tidak akan mengharuskan Anda untuk mengubah aturan yang ada.

Mengaktifkan penyusutan, obfuscation, dan pengoptimalan

Saat Anda menggunakan Android Studio 3.4 atau Android Gradle Plugin 3.4.0 dan yang lebih tinggi, 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, penyusutan, obfuscation, dan pengoptimalan kode tidak diaktifkan secara default. Hal itu karena pengoptimalan waktu kompilasi ini meningkatkan waktu pembuatan 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 penyusutan, obfuscation, dan pengoptimalan, sertakan baris berikut ini dalam file build.gradle tingkat project.

android {
        buildTypes {
            release {
                // Enables code shrinking, obfuscation, and optimization for only
                // your project's release build type.
                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 modul itu.

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

Android Gradle Plugin Dihasilkan oleh Android Gradle Plugin pada waktu kompilasi. Android Gradle Plugin 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, file build.gradle tingkat modul akan menyertakan file aturan ini dalam build rilis untuk Anda.

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

Dependensi library Library AAR: <library-dir>/proguard.txt

Library JAR: <library-dir>/META-INF/proguard/

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

Penggunaan file aturan yang dikemas dengan library AAR akan berguna jika aturan keep 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 ProGuard bersifat aditif, aturan tertentu yang disertakan dependensi library AAR tidak dapat dihapus dan dapat memengaruhi kompilasi bagian lain dari aplikasi Anda. Misalnya, jika sebuah library menyertakan aturan untuk menonaktifkan pengoptimalan kode, maka aturan itu akan menonaktifkan pengoptimalan untuk seluruh project Anda.

Android Asset Package Tool 2 (AAPT2) Setelah membuat project Anda dengan minifyEnabled true: <module-dir>/build/intermediates/proguard-rules/debug/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 masuk.
File konfigurasi kustom Secara default, saat Anda 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 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 mengoutput 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
    

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 file build.gradle 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.

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'
            }
        }
        flavorDimensions "version"
        productFlavors {
            flavor1 {
              ...
            }
            flavor2 {
                proguardFile 'flavor2-rules.pro'
            }
        }
    }
    

Menyusutkan kode

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

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

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

Gambar 1 menunjukkan aplikasi dengan dependensi library waktu proses. Selagi memeriksa kode aplikasi, R8 menyimpulkan bahwa metode foo(), faz(), dan bar() dapat diakses dari titik masuk MainActivity.class. Namun, class OkayApi.class atau metode baz()-nya tidak pernah digunakan oleh aplikasi Anda saat waktu proses, 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 masuk 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 masuk yang mungkin ke dalam aplikasi Anda. Android Gradle Plugin 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 menyusutkan resource.

Menyesuaikan kode yang perlu dipertahankan

Dalam sebagian besar situasi, file aturan ProGuard default (proguard-android- optimize.txt) sudah cukup untuk mengarahkan R8 agar 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 waktu proses (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 ke 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. Menambahkan anotasi ini 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 Android Gradle Plugin, seperti dijelaskan di bagian tentang cara mengaktifkan penyusutan.

Ada banyak pertimbangan yang perlu Anda buat saat menggunakan opsi -keep. Untuk informasi selengkapnya tentang menyesuaikan file konfigurasi, baca Panduan ProGuard. Bagian Pemecahan Masalah dalam panduan tersebut menjelaskan masalah umum lain yang mungkin Anda alami jika kode dihilangkan.

Menyusutkan resource

Penyusutan resource hanya dapat dilakukan bersama dengan penyusutan kode. Setelah menghapus semua kode yang tidak digunakan, resource shrinker 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 resource shrinker.

Untuk mengaktifkan penyusutan resource, tetapkan properti shrinkResources ke true dalam file build.gradle (bersama minifyEnabled untuk penyusutan kode). Contoh:

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

Jika Anda belum membuat aplikasi menggunakan minifyEnabled untuk penyusutan 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 Anda 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/keep.xml. Build tidak mengemas file ini ke dalam APK Anda.

Menentukan resource yang akan dihapus mungkin tampak konyol karena sebenarnya Anda bisa langsung menghapusnya, tetapi cara ini berguna saat Anda menggunakan varian build. Misalnya, Anda mungkin menempatkan semua resource ke dalam direktori project bersama, lalu membuat file keep.xml berbeda untuk setiap varian build saat Anda mengetahui bahwa resource tertentu tampaknya digunakan di dalam kode (dan karenanya tidak dihapus oleh shrinker), tetapi sebenarnya Anda tahu bahwa resource itu tidak akan digunakan untuk varian versi tersebut. Mungkin juga fitur build salah mengidentifikasi resource sebagai diperlukan, yang dimungkinkan karena compiler menambahkan ID resource secara inline, lalu penganalisis resource mungkin tidak tahu perbedaan antara resource yang memang benar-benar direferensikan dengan nilai bilangan bulat dalam kode yang kebetulan sama nilainya.

Mengaktifkan pemeriksaan referensi yang ketat

Biasanya, resource shrinker dapat mengetahui secara akurat saat sebuah resource digunakan. Namun, 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 resource shrinker 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());
    

Resource shrinker 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 sepertinya dapat digunakan untuk membuat URL seperti ini, maka shrinker tidak akan menghapusnya.

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

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

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

Menghapus resource alternatif yang tidak digunakan

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

Misalnya, jika Anda menggunakan library yang menyertakan resource bahasa (seperti AppCompat atau Google Play Services), maka APK 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 menggunakan 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:

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

Demikian pula, Anda dapat menyesuaikan kepadatan layar atau resource ABI mana yang akan disertakan dalam APK dengan membuat 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 qualifier 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 file APK.

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 dalam properti sourceSet dari file build.gradle—misalnya jika src/main/res/ dan src/main/res2/ berisi resource yang sama.

Melakukan obfuscation 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 stacktrace Anda setelah obfuscation, bacalah bagian selanjutnya 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 masuk dan menetapkan aturan keep untuknya, seperti dijelaskan di bagian tentang 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. Selain mengubah nama, R8 juga dapat mengubah jumlah baris yang ada di pelacakan tumpukan untuk semakin menghemat ukuran saat Anda menulis file DEX. Untungnya, R8 membuat file mapping.txt setiap kali dijalankan, yang berisi nama class, metode, dan kolom yang di-obfuscate yang dipetakan ke nama aslinya. File pemetaan ini juga berisi informasi untuk memetakan kembali jumlah baris ke jumlah baris file sumber asli. R8 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 APK Anda. Selanjutnya Google Play akan men-deobfuscate pelacakan tumpukan yang masuk dari masalah yang dilaporkan pengguna, sehingga Anda dapat memeriksanya di Konsol Google Play. Untuk informasi selengkapnya, lihat artikel Pusat Bantuan tentang cara men-deobfuscate pelacakan tumpukan yang tidak berfungsi.

Untuk mengonversi pelacakan tumpukan yang di-obfuscate menjadi versi yang dapat Anda baca sendiri, gunakan skrip ReTrace, yang disertakan dengan ProGuard.

Pengoptimalan kode

Untuk menyusutkan ukuran 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 {} tersebut.
  • Jika kode Anda memanggil metode hanya di satu tempat, R8 mungkin akan menghapus metode itu dan menyisipkannya secara inline di situs panggilan tunggal.
  • 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 akan mengabaikan 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.

Mengaktifkan pengoptimalan yang lebih agresif

R8 menyertakan serangkaian pengoptimalan tambahan yang tidak diaktifkan secara default. Anda dapat mengaktifkan pengoptimalan tambahan ini dengan menyertakan baris berikut dalam file gradle.properties project Anda:

android.enableR8.fullMode=true
    

Karena pengoptimalan tambahan membuat R8 berperilaku berbeda dengan ProGuard, Anda mungkin harus menyertakan aturan ProGuard tambahan guna menghindari masalah waktu proses. Sebagai contoh, misalkan kode Anda mereferensikan sebuah class melalui Java Reflection API. Secara default, R8 akan berasumsi bahwa Anda bermaksud memeriksa dan memanipulasi objek class tersebut selama waktu proses—meski sebenarnya kode Anda tidak melakukannya—dan otomatis mempertahankan class tersebut dan penginisialisasi statisnya.

Namun, saat Anda menggunakan "mode penuh", R8 tidak membuat asumsi ini dan, jika R8 menyatakan bahwa kode Anda tidak pernah menggunakan class itu selama waktu proses, maka R8 akan menghapus class tersebut dari DEX akhir aplikasi Anda. Artinya, jika ingin mempertahankan class ini dan penginisialisasi statisnya, Anda harus menyertakan aturan keep dalam file aturan Anda untuk melakukan hal itu.

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, silakan laporkan bug.

Memecahkan masalah dengan R8

Bagian ini menjelaskan beberapa strategi untuk mengatasi masalah saat mengaktifkan penyusutan, 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, ada baiknya Anda melihat 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 masuk yang ditentukan R8 dari aturan keep project Anda, sertakan -printseeds <output-dir>/seeds.txt dalam 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 masuk 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 penyusutan resource

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

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

Gradle juga membuat file diagnostik bernama resources.txt dalam <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 mengapa @drawable/ic_plus_anim_016 masih berada dalam APK Anda, 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] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
    16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016
    

Sekarang Anda perlu mengetahui mengapa @drawable/add_schedule_fab_icon_anim dapat diakses—dan jika mencari ke atas, Anda akan menemukan resource tersebut tercantum di "The root reachable resources are:". Ini berarti terdapat referensi kode ke add_schedule_fab_icon_anim (artinya, ID R.drawable-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 Anda 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 dijelaskan di bagian tentang cara menyesuaikan resource yang perlu dipertahankan.