Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

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. Saat mengaktifkan penyusutan, Anda juga mendapatkan manfaat dari obfuscation, yang memperpendek nama class dan anggota aplikasi Anda, dan pengoptimalan, yang menerapkan strategi yang 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 plugin Android Gradle 3.4.0 atau yang lebih tinggi, 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, penyusutan dapat mengidentifikasi kode library yang tidak digunakan aplikasi dan hanya menghapus kode tersebut dari aplikasi Anda. Untuk mempelajari lebih lanjut, buka 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 untuk cabang else {}. Untuk mempelajari lebih lanjut, buka 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 plugin Android Gradle 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 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 penyusutan, obfuscation, dan pengoptimalan, sertakan yang berikut dalam file build.gradle level project Anda.

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 root 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 Gradle Android 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, file build.gradle tingkat 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 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 menyadari 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 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/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 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

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'
        }
    }
}

Menciutkan kode Anda

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 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 pada 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 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 menyusutkan resource.

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 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 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 penyusutan.

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 Anda mem-build Android App Bundle, Anda dapat secara otomatis menyertakan file simbol debug di dalamnya. Untuk menyertakan file ini dalam build rilis, tambahkan hal berikut ke file build.gradle 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 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 untuk APK atau Android App Bundle, plugin Android Gradle menyimpan salinan library yang belum dihapus 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. Mengupload file symbols.zip secara manual ke Konsol Google Play.

Menyusutkan resource

Penyusutan resource hanya dapat dilakukan bersama dengan penyusutan 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 penyusutan resource, tetapkan properti shrinkResources ke true dalam file build.gradle (selain minifyEnabled untuk penyingkatan 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 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/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 bila 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 build tersebut. Mungkin juga alat 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. 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 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 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 penyusutan 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 shrinker 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 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 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:

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

Demikian pula, Anda dapat menyesuaikan kepadatan layar atau resource ABI mana 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 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 kumpulan 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 kumpulan sumber yang sama, Gradle tidak dapat menggabungkannya dan akan mengeluarkan error penggabungan resource. Hal ini dapat terjadi jika Anda menentukan beberapa kumpulan sumber di properti sourceSet dari file build.gradle—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 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 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 {}.
  • 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 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.

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 runtime. Sebagai contoh, misalkan kode Anda mereferensikan class melalui Java Reflection API. Secara default, R8 akan berasumsi bahwa Anda bermaksud memeriksa dan memanipulasi objek dari class tersebut pada waktu proses—meski sebenarnya kode Anda tidak melakukannya—dan secara 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, 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 masuk 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 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 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 mengapa @drawable/ic_plus_anim_016 masih berada dalam APK, 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 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.