R8 menyediakan dua mode, yaitu mode kompatibilitas dan mode penuh. Mode penuh memberi Anda pengoptimalan yang efektif untuk meningkatkan performa aplikasi Anda.
Panduan ini ditujukan bagi developer Android yang ingin menggunakan pengoptimalan R8 yang paling canggih. Dokumen ini membahas perbedaan utama antara mode kompatibilitas dan mode penuh, serta memberikan konfigurasi eksplisit yang diperlukan untuk memigrasikan project Anda dengan aman dan menghindari error runtime umum.
Mengaktifkan mode penuh
Untuk mengaktifkan mode penuh, hapus baris berikut dari file gradle.properties
Anda:
android.enableR8.fullMode=false // Remove this line to enable full mode
Mempertahankan kelas yang terkait dengan atribut
Atribut adalah metadata yang disimpan dalam file class yang dikompilasi yang bukan bagian dari
kode yang dapat dieksekusi. Namun, hal ini mungkin diperlukan untuk jenis refleksi tertentu. Contoh umum mencakup Signature (yang mempertahankan informasi jenis generik setelah penghapusan jenis), InnerClasses dan EnclosingMethod (untuk merefleksikan struktur class) serta anotasi yang terlihat saat runtime.
Kode berikut menunjukkan seperti apa atribut Signature untuk kolom dalam bytecode. Untuk kolom:
List<User> users;
File class yang dikompilasi akan berisi bytecode berikut:
.field public static final users:Ljava/util/List;
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/util/List<",
"Lcom/example/package/User;",
">;"
}
.end annotation
.end field
Library yang banyak menggunakan refleksi (seperti Gson) sering mengandalkan atribut ini untuk memeriksa dan memahami struktur kode Anda secara dinamis. Secara default dalam mode penuh R8, atribut dipertahankan hanya jika class, kolom, atau metode terkait dipertahankan secara eksplisit.
Contoh berikut menunjukkan alasan atribut diperlukan dan aturan keep yang perlu Anda tambahkan saat bermigrasi dari mode kompatibilitas ke mode penuh.
Pertimbangkan contoh berikut saat kita melakukan deserialisasi daftar pengguna menggunakan library Gson.
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
data class User(
@SerializedName("username")
var username: String? = null,
@SerializedName("age")
var age: Int = 0
)
fun GsonRemoteJsonListExample() {
val gson = Gson()
// 1. The JSON string for a list of users returned from remote
val jsonOutput = """[{"username":"alice","age":30}, {"username":"bob","age":25}]"""
// 2. Deserialize the JSON string into a List<User>
// We must use TypeToken for generic types like List
val listType = object : TypeToken<List<User>>() {}.type
val deserializedList: List<User> = gson.fromJson(jsonOutput, listType)
// Print the list
println("First user from list: ${deserializedList}")
}
Selama kompilasi, penghapusan jenis Java akan menghapus argumen jenis generik. Artinya, saat runtime, List<String> dan List<User> muncul sebagai List mentah. Oleh karena itu, library seperti Gson, yang mengandalkan refleksi, tidak dapat menentukan jenis objek tertentu yang dideklarasikan List untuk dimuat saat mendeserialisasi daftar JSON, yang dapat menyebabkan masalah runtime.
Untuk mempertahankan informasi jenis, Gson menggunakan TypeToken. Pembungkusan TypeToken
mempertahankan informasi deserialisasi yang diperlukan.
Ekspresi Kotlin object:TypeToken<List<User>>() {}.type membuat
class dalam anonim yang memperluas TypeToken dan mengambil informasi
jenis generik. Dalam contoh ini, class anonim diberi nama
$GsonRemoteJsonListExample$listType$1.
Bahasa pemrograman Java menyimpan tanda tangan generik superclass sebagai metadata, yang dikenal sebagai atribut Signature, dalam file class yang dikompilasi.
TypeToken kemudian menggunakan metadata Signature ini untuk memulihkan jenis saat runtime.
Dengan demikian, Gson dapat menggunakan refleksi untuk membaca Signature dan berhasil menemukan jenis List<User> lengkap yang diperlukan untuk deserialisasi.
Jika diaktifkan dalam mode kompatibilitas, R8 akan mempertahankan atribut Signature
untuk class, termasuk class dalam anonim seperti
$GsonRemoteJsonListExample$listType$1, meskipun aturan keep tertentu tidak
didefinisikan secara eksplisit. Akibatnya, mode kompatibilitas R8 tidak memerlukan aturan keep eksplisit lebih lanjut agar contoh ini berfungsi seperti yang diharapkan.
// keep rule for compatibility mode
-keepattributes Signature
Jika R8 diaktifkan dalam mode penuh, atribut Signature dari class dalam anonim $GsonRemoteJsonListExample$listType$1 akan dihapus. Tanpa informasi jenis ini di Signature, Gson tidak dapat menemukan jenis aplikasi yang benar, yang akan menghasilkan IllegalStateException. Aturan penyimpanan yang diperlukan untuk
mencegah hal ini adalah:
// keep rule required for full mode
-keepattributes Signature
-keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken
-keepattributes Signature: Aturan ini menginstruksikan R8 untuk mempertahankan atribut yang perlu dibaca Gson. Dalam mode penuh, R8 hanya mempertahankan atributSignatureuntuk class, kolom, atau metode yang secara eksplisit dicocokkan oleh aturankeep.-keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken: Aturan ini diperlukan karenaTypeTokenmembungkus jenis objek yang sedang dideserialisasi. Setelah penghapusan jenis, class dalam anonim dibuat untuk mempertahankan informasi jenis generik. Tanpa mempertahankancom.google.gson.reflect.TypeTokensecara eksplisit, R8 dalam mode penuh tidak akan menyertakan jenis class ini dalam atributSignatureyang diperlukan untuk deserialisasi.-keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken: Aturan ini mempertahankan informasi jenis class anonim yang memperluasTypeToken, seperti$GsonRemoteJsonListExample$listType$1dalam contoh ini. Tanpa aturan ini, R8 dalam mode penuh akan menghapus informasi jenis yang diperlukan, sehingga menyebabkan deserialisasi gagal.
Mulai dari Gson versi 2.11.0, library menggabungkan aturan keep yang diperlukan untuk deserialisasi dalam mode penuh. Saat Anda membuat aplikasi dengan R8 diaktifkan, R8 akan otomatis menemukan dan menerapkan aturan ini dari library. Hal ini memberikan perlindungan yang dibutuhkan aplikasi Anda tanpa mengharuskan Anda menambahkan atau memelihara aturan tertentu ini secara manual di project Anda.
Penting untuk memahami bahwa aturan yang dibagikan sebelumnya hanya memecahkan masalah penemuan jenis generik (misalnya, List<User>).
R8 juga mengganti nama kolom class. Jika Anda tidak menggunakan anotasi @SerializedName pada model data, Gson akan gagal mendeserialisasi JSON karena nama kolom tidak lagi cocok dengan kunci JSON.
Namun, jika Anda menggunakan versi Gson yang lebih lama dari 2.11, atau jika model Anda tidak menggunakan anotasi @SerializedName, Anda harus menambahkan aturan keep eksplisit untuk model tersebut.
Mempertahankan konstruktor default
Dalam mode penuh R8, konstruktor tanpa argumen/default tidak dipertahankan secara implisit, bahkan
saat class itu sendiri dipertahankan. Jika Anda membuat instance class
menggunakan class.getDeclaredConstructor().newInstance() atau class.newInstance(),
Anda harus secara eksplisit mempertahankan konstruktor tanpa argumen dalam mode penuh. Sebaliknya,
mode kompatibilitas selalu mempertahankan konstruktor tanpa argumen.
Pertimbangkan contoh saat instance PrecacheTask dibuat menggunakan
refleksi untuk memanggil metode run secara dinamis. Meskipun skenario ini tidak memerlukan aturan tambahan dalam mode kompatibilitas, dalam mode penuh, konstruktor default PrecacheTask akan dihapus. Oleh karena itu, aturan penyimpanan
tertentu diperlukan.
// In library
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(taskClass: Class<out StartupTask>) {
// The class isn't removed, but its constructor might be.
val task = taskClass.getDeclaredConstructor().newInstance()
task.run()
}
}
// In app
class PreCacheTask : StartupTask {
override fun run() {
Log.d("Pre cache task", "Warming up the cache...")
}
}
fun runTaskRunner() {
// The library is given a direct reference to the app's task class.
TaskRunner.execute(PreCacheTask::class.java)
}
# Full mode keep rule
# default constructor needs to be specified
-keep class com.example.fullmoder8.PreCacheTask {
<init>();
}
Modifikasi akses diaktifkan secara default
Dalam mode kompatibilitas, R8 tidak mengubah visibilitas metode dan kolom dalam class. Namun, dalam mode penuh, R8 meningkatkan pengoptimalan dengan mengubah visibilitas metode dan kolom Anda, misalnya, dari pribadi menjadi publik. Hal ini memungkinkan lebih banyak penyisipan inline.
Pengoptimalan ini dapat menyebabkan masalah jika kode Anda menggunakan refleksi yang secara khusus bergantung pada anggota yang memiliki visibilitas tertentu. R8 tidak akan
mengenali penggunaan tidak langsung ini, yang berpotensi menyebabkan error pada aplikasi. Untuk mencegahnya, Anda harus menambahkan aturan -keep tertentu untuk mempertahankan anggota, yang juga akan mempertahankan visibilitas aslinya.
Untuk mengetahui informasi selengkapnya, lihat contoh ini untuk memahami alasan akses anggota pribadi menggunakan refleksi tidak disarankan dan aturan keep untuk mempertahankan kolom/metode tersebut.