Menambahkan aturan simpan

Secara umum, aturan keep menentukan class (atau subclass atau implementasi), lalu anggota—metode, konstruktor, atau kolom—dalam class tersebut yang akan dipertahankan.

Sintaksis umum untuk aturan penyimpanan adalah sebagai berikut, tetapi opsi penyimpanan tertentu tidak menerima keep_option_modfier opsional.


-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>

Berikut adalah contoh aturan penyimpanan yang menggunakan keepclassmembers sebagai opsi penyimpanan, allowoptimization sebagai pengubah, dan menyimpan someSpecificMethod() dari com.example.MyClass:

-keepclassmembers,allowoptimization class com.example.MyClass {
  void someSpecificMethod();
}

Opsi pertahankan

Opsi keep adalah bagian pertama dari aturan penyimpanan Anda. Menentukan aspek class yang akan dipertahankan. Ada enam opsi penyimpanan yang berbeda, yaitu keep, keepclassmembers, keepclasseswithmembers, keepnames, keepclassmembernames, keepclasseswithmembernames.

Tabel berikut menjelaskan opsi penyimpanan ini:

Opsi Keep Deskripsi
keepclassmembers Mempertahankan anggota tertentu saja jika kelas ada setelah pengoptimalan.
keep Mempertahankan class tertentu dan anggota tertentu (kolom dan metode), sehingga tidak dioptimalkan.

Catatan: keep umumnya hanya boleh digunakan dengan pengubah opsi tetap karena keep dengan sendirinya mencegah terjadinya pengoptimalan apa pun pada class yang cocok.
keepclasseswithmembers Mempertahankan class dan anggota yang ditentukan hanya jika class memiliki semua anggota dari spesifikasi class.
keepclassmembernames Mencegah penggantian nama anggota class yang ditentukan, tetapi tidak mencegah penghapusan class atau anggotanya.

Catatan: Arti opsi ini sering disalahpahami; sebaiknya gunakan -keepclassmembers,allowshrinking yang setara.
keepnames Mencegah penggantian nama kelas dan anggotanya, tetapi tidak mencegah penghapusan seluruhnya jika dianggap tidak digunakan.

Catatan: Arti opsi ini sering disalahpahami; sebaiknya gunakan -keep,allowshrinking yang setara.
keepclasseswithmembernames Mencegah penggantian nama kelas dan anggota yang ditentukan, tetapi hanya jika anggota ada dalam kode akhir. Opsi ini tidak mencegah penghapusan kode.

Catatan: Arti opsi ini sering disalahpahami; sebaiknya gunakan -keepclasseswithmembers,allowshrinking yang setara.

Memilih opsi penyimpanan yang tepat

Memilih opsi keep yang tepat sangat penting dalam menentukan pengoptimalan yang tepat untuk aplikasi Anda. Opsi keep tertentu menyusutkan kode, sebuah proses yang menghapus kode yang tidak direferensikan, sementara opsi lainnya meng-obfuscate, atau mengganti nama, kode. Tabel berikut menunjukkan tindakan dari berbagai opsi penyimpanan:

Opsi Keep Kelas penyusutan Meng-obfuscate class Mengecilkan anggota Mengaburkan anggota
keep
keepclassmembers
keepclasseswithmembers
keepnames
keepclassmembernames
keepclasseswithmembernames

Pengubah opsi pertahankan

Pengubah opsi penyimpanan digunakan untuk mengontrol cakupan dan perilaku aturan penyimpanan. Anda dapat menambahkan 0 atau lebih pengubah opsi penyimpanan ke aturan penyimpanan.

Kemungkinan nilai untuk pengubah opsi tetap dijelaskan dalam tabel berikut:

Nilai Deskripsi
allowoptimization Memungkinkan pengoptimalan elemen yang ditentukan. Namun, elemen yang ditentukan tidak diganti namanya atau dihapus.
allowobfucastion Mengizinkan penggantian nama elemen yang ditentukan. Namun, elemen tidak akan dihapus atau dioptimalkan.
allowshrinking Memungkinkan penghapusan elemen yang ditentukan jika R8 tidak menemukan referensi ke elemen tersebut. Namun, elemen tidak diganti namanya atau dioptimalkan.
includedescriptorclasses Memerintahkan R8 untuk menyimpan semua class yang muncul dalam deskriptor metode (jenis parameter dan jenis nilai yang ditampilkan) dan kolom (jenis kolom) yang disimpan.
allowaccessmodification Mengizinkan R8 mengubah (biasanya memperluas) pengubah akses (public, private, protected) class, metode, dan kolom selama proses pengoptimalan.
allowrepackage Memungkinkan R8 memindahkan class ke paket yang berbeda, termasuk paket default (root).

Spesifikasi kelas

Anda harus menentukan class, superclass, atau antarmuka yang diimplementasikan sebagai bagian dari aturan keep. Semua class, termasuk class dari namespace java.lang seperti java.lang.String, harus ditentukan menggunakan nama Java yang sepenuhnya memenuhi syarat. Untuk memahami nama yang harus digunakan, periksa bytecode menggunakan alat yang dijelaskan dalam Mendapatkan nama Java yang dihasilkan.

Contoh berikut menunjukkan cara menentukan class MaterialButton:

  • Benar: com.google.android.material.button.MaterialButton
  • Salah: MaterialButton

Spesifikasi class juga menentukan anggota dalam class yang harus dipertahankan. Aturan berikut mempertahankan class MaterialButton dan semua anggotanya:

-keep class com.google.android.material.button.MaterialButton { *; }

Subclass dan penerapan

Untuk menargetkan subclass atau class yang mengimplementasikan antarmuka, gunakan extend dan implements.

Misalnya, jika Anda memiliki class Bar dengan subclass Foo sebagai berikut:

class Foo : Bar()

Aturan keep berikut mempertahankan semua subkelas Bar. Perhatikan bahwa aturan keep tidak menyertakan superclass Bar itu sendiri.

-keep class * extends Bar

Jika Anda memiliki class Foo yang menerapkan Bar:

class Foo : Bar

Aturan keep berikut mempertahankan semua class yang menerapkan Bar. Perhatikan bahwa aturan penyimpanan tidak menyertakan antarmuka Bar itu sendiri.

-keep class * implements Bar

Pengubah akses

Anda dapat menentukan pengubah akses seperti public, private, static, dan final untuk membuat aturan penyimpanan Anda lebih presisi.

Misalnya, aturan berikut menyimpan semua class public dalam paket api dan sub-paketnya, serta semua anggota publik dan yang dilindungi dalam class ini.

-keep public class com.example.api.** { public protected *; }

Anda juga dapat menggunakan pengubah untuk anggota dalam class. Misalnya, aturan berikut hanya menyimpan metode public static dari class Utils:

-keep class com.example.Utils {
    public static void *(...);
}

Pengubah khusus Kotlin

R8 tidak mendukung pengubah khusus Kotlin seperti internal dan suspend. Gunakan panduan berikut untuk mempertahankan kolom tersebut.

  • Untuk mempertahankan class, metode, atau kolom internal, perlakukan sebagai publik. Misalnya, perhatikan sumber Kotlin berikut:

    package com.example
    internal class ImportantInternalClass {
      internal f: Int
      internal fun m() {}
    }
    

    Class, metode, dan kolom internal adalah public dalam file .class yang dihasilkan oleh compiler Kotlin, jadi Anda harus menggunakan kata kunci public seperti yang ditunjukkan dalam contoh berikut:

    -keepclassmembers public class com.example.ImportantInternalClass {
      public int f;
      public void m();
    }
    
  • Saat anggota suspend dikompilasi, cocokkan tanda tangan yang dikompilasi dalam aturan keep.

    Misalnya, jika Anda memiliki fungsi fetchUser yang ditentukan seperti yang ditunjukkan dalam cuplikan berikut:

    suspend fun fetchUser(id: String): User
    

    Saat dikompilasi, tanda tangannya dalam bytecode akan terlihat seperti berikut:

    public final Object fetchUser(String id, Continuation<? super User> continuation);
    

    Untuk menulis aturan keep untuk fungsi ini, Anda harus mencocokkan tanda tangan yang dikompilasi ini, atau menggunakan ....

    Contoh penggunaan tanda tangan yang dikompilasi adalah sebagai berikut:

    -keepclassmembers class com.example.repository.UserRepository {
    public java.lang.Object fetchUser(java.lang.String,  kotlin.coroutines.Continuation);
    }
    

    Contoh penggunaan ... adalah sebagai berikut:

    -keepclassmembers class com.example.repository.UserRepository {
    public java.lang.Object fetchUser(...);
    }
    

Spesifikasi anggota

Spesifikasi kelas secara opsional mencakup anggota kelas yang akan dipertahankan. Jika Anda menentukan satu atau beberapa anggota untuk kelas, aturan hanya berlaku untuk anggota tersebut.

Misalnya, untuk mempertahankan class tertentu dan semua anggotanya, gunakan berikut ini:

-keep class com.myapp.MyClass { *; }

Untuk mempertahankan hanya class dan bukan anggotanya, gunakan perintah berikut:

-keep class com.myapp.MyClass

Biasanya, Anda ingin menentukan beberapa anggota. Misalnya, contoh berikut mempertahankan kolom publik text dan metode publik updateText() dalam class MyClass.

-keep class com.myapp.MyClass {
    public java.lang.String text;
    public void updateText(java.lang.String);
}

Untuk mempertahankan semua kolom publik dan metode publik, lihat contoh berikut:

-keep public class com.example.api.ApiClient {
    public *;
}

Metode

Sintaksis untuk menentukan metode dalam spesifikasi anggota untuk aturan penyimpanan adalah sebagai berikut:

[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);

Misalnya, aturan keep berikut mempertahankan metode publik bernama setLabel() yang menampilkan void dan mengambil String.

-keep class com.example.MyView {
    public void setLabel(java.lang.String);
}

Anda dapat menggunakan <methods> sebagai pintasan untuk mencocokkan semua metode dalam class sebagai berikut:

-keep class com.example.MyView {
    <methods>;
}

Untuk mempelajari lebih lanjut cara menentukan jenis untuk jenis nilai yang ditampilkan dan jenis parameter, lihat Jenis.

Konstruktor

Untuk menentukan konstruktor, gunakan <init>. Sintaksis untuk menentukan konstruktor dalam spesifikasi anggota untuk aturan penyimpanan adalah sebagai berikut:

[<access_modifier>] <init>(parameter_types);

Misalnya, aturan keep berikut mempertahankan konstruktor View kustom yang mengambil Context dan AttributeSet.

-keep class com.example.ui.MyCustomView {
    public <init>(android.content.Context, android.util.AttributeSet);
}

Untuk mempertahankan semua konstruktor publik, gunakan contoh berikut sebagai referensi:

-keep class com.example.ui.MyCustomView {
    public <init>(...);
}

Kolom

Sintaksis untuk menentukan kolom dalam spesifikasi anggota untuk aturan penyimpanan adalah sebagai berikut:

[<access_modifier>...] [<type>] <field_name>;

Misalnya, aturan penyimpanan berikut menyimpan kolom string pribadi bernama userId dan kolom bilangan bulat statis publik bernama STATUS_ACTIVE:

-keep class com.example.models.User {
    private java.lang.String userId;
    public static int STATUS_ACTIVE;
}

Anda dapat menggunakan <fields> sebagai pintasan untuk mencocokkan semua kolom dalam class sebagai berikut:

-keep class com.example.models.User {
    <fields>;
}

Fungsi tingkat paket

Untuk mereferensikan fungsi Kotlin yang ditentukan di luar class (biasanya disebut fungsi level teratas), pastikan untuk menggunakan nama Java yang dihasilkan untuk class yang ditambahkan secara implisit oleh compiler Kotlin. Nama class adalah nama file Kotlin dengan Kt ditambahkan. Misalnya, jika Anda memiliki file Kotlin bernama MyClass.kt yang ditentukan sebagai berikut:

package com.example.myapp.utils

// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
    return email.contains("@")
}

Untuk menulis aturan keep untuk fungsi isEmailValid, spesifikasi class harus menargetkan class MyClassKt yang dihasilkan:

-keep class com.example.myapp.utils.MyClassKt {
    public static boolean isEmailValid(java.lang.String);
}

Jenis

Bagian ini menjelaskan cara menentukan jenis nilai yang ditampilkan, jenis parameter, dan jenis kolom dalam spesifikasi anggota aturan penyimpanan. Ingatlah untuk menggunakan nama Java yang dihasilkan untuk menentukan jenis jika berbeda dari kode sumber Kotlin.

Jenis primitif

Untuk menentukan jenis primitif, gunakan kata kunci Java-nya. R8 mengenali jenis primitif berikut: boolean, byte, short, char, int, long, float, double.

Contoh aturan dengan jenis primitif adalah sebagai berikut:

# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
    public void setValues(int, float);
}

Jenis generik

Selama kompilasi, compiler Kotlin/Java menghapus informasi jenis generik, jadi saat Anda menulis aturan keep yang melibatkan jenis generik, Anda harus menargetkan representasi kode yang dikompilasi, dan bukan kode sumber asli. Untuk mempelajari lebih lanjut cara mengubah jenis generik, lihat Penghapusan jenis.

Misalnya, jika Anda memiliki kode berikut dengan jenis generik tanpa batas yang ditentukan dalam Box.kt:

package com.myapp.data

class Box<T>(val item: T) {
    fun getItem(): T {
        return item
    }
}

Setelah penghapusan jenis, T diganti dengan Object. Untuk mempertahankan konstruktor dan metode class, aturan Anda harus menggunakan java.lang.Object, bukan T generik.

Contoh aturan penyimpanan adalah sebagai berikut:

# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
    public init(java.lang.Object);
    public java.lang.Object getItem();
}

Jika Anda memiliki kode berikut dengan jenis generik terikat di NumberBox.kt:

package com.myapp.data

// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)

Dalam hal ini, penghapusan jenis menggantikan T dengan batasnya, java.lang.Number.

Contoh aturan penyimpanan adalah sebagai berikut:

-keep class com.myapp.data.NumberBox {
    public init(java.lang.Number);
}

Saat menggunakan jenis generik khusus aplikasi sebagai class dasar, Anda juga harus menyertakan aturan penyimpanan untuk class dasar.

Misalnya, untuk kode berikut:

package com.myapp.data

data class UnpackOptions(val useHighPriority: Boolean)

// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}

Anda dapat menggunakan aturan penyimpanan dengan includedescriptorclasses untuk mempertahankan class UnpackOptions dan metode class Box dengan satu aturan sebagai berikut:

-keep,includedescriptorclasses class com.myapp.data.Box {
    public <init>(com.myapp.data.UnpackOptions);
}

Untuk mempertahankan fungsi tertentu yang memproses daftar objek, Anda perlu menulis aturan yang cocok dengan tanda tangan fungsi secara tepat. Perhatikan bahwa karena jenis generik dihapus, parameter seperti List<Product> akan terlihat sebagai java.util.List.

Misalnya, jika Anda memiliki class utilitas dengan fungsi yang memproses daftar objek Product sebagai berikut:

package com.myapp.utils

import com.myapp.data.Product
import android.util.Log

class DataProcessor {
    // This is the function we want to keep
    fun processProducts(products: List<Product>) {
        Log.d("DataProcessor", "Processing ${products.size} products.")
        // Business logic ...
    }
}

// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)

Anda dapat menggunakan aturan keep berikut untuk melindungi hanya fungsi processProducts:

-keep class com.myapp.utils.DataProcessor {
    public void processProducts(java.util.List);
}

Jenis array

Tentukan jenis array dengan menambahkan [] ke jenis komponen untuk setiap dimensi array. Hal ini berlaku untuk jenis class dan jenis primitif.

  • Array kelas satu dimensi: java.lang.String[]
  • Array dasar dua dimensi: int[][]

Misalnya, jika Anda memiliki kode berikut:

package com.example.data

class ImageProcessor {
  fun process(): ByteArray {
    // process image to return a byte array
  }
}

Anda dapat menggunakan aturan penyimpanan berikut:

# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
    public byte[] process();
}

Karakter pengganti

Tabel berikut menunjukkan cara menggunakan karakter pengganti untuk menerapkan aturan penyimpanan ke beberapa class atau anggota yang cocok dengan pola tertentu.

Karakter pengganti Berlaku untuk kelas atau anggota Deskripsi
** Keduanya Paling sering digunakan. Mencocokkan nama jenis apa pun, termasuk sejumlah pemisah paket. Hal ini berguna untuk mencocokkan semua class dalam paket dan sub-paketnya.
* Keduanya Untuk spesifikasi class, cocok dengan bagian nama jenis yang tidak berisi pemisah paket (.)
Untuk spesifikasi anggota, cocok dengan nama metode atau kolom. Jika digunakan sendiri, nilai ini juga merupakan alias untuk **.
? Keduanya Mencocokkan satu karakter dalam nama kelas atau anggota.
*** Pelanggan Mencocokkan jenis apa pun, termasuk jenis primitif (seperti int), jenis class (seperti java.lang.String), dan jenis array dari dimensi apa pun (seperti byte[][]).
... Pelanggan Mencocokkan daftar parameter untuk suatu metode.
% Pelanggan Mencocokkan jenis primitif apa pun (seperti `int`, `float`, `boolean`, atau lainnya).

Berikut beberapa contoh cara menggunakan karakter pengganti khusus:

  • Jika Anda memiliki beberapa metode dengan nama yang sama yang menggunakan jenis primitif yang berbeda sebagai input, Anda dapat menggunakan % untuk menulis aturan keep yang mempertahankan semuanya. Misalnya, class DataStore ini memiliki beberapa metode setValue:

    class DataStore {
        fun setValue(key: String, value: Int) { ... }
        fun setValue(key: String, value: Boolean) { ... }
        fun setValue(key: String, value: Float) { ... }
    }
    

    Aturan keep berikut akan mempertahankan semua metode:

    -keep class com.example.DataStore {
        public void setValue(java.lang.String, %);
    }
    
  • Jika Anda memiliki beberapa class dengan nama yang berbeda satu karakter, gunakan ? untuk menulis aturan penyimpanan yang menyimpan semuanya. Misalnya, jika Anda memiliki class berikut:

    com.example.models.UserV1 {...}
    com.example.models.UserV2 {...}
    com.example.models.UserV3 {...}
    

    Aturan penyimpanan berikut akan menyimpan semua class:

    -keep class com.example.models.UserV?
    
  • Untuk mencocokkan class Example dan AnotherExample (jika merupakan class tingkat root), tetapi tidak com.foo.Example, gunakan aturan keep berikut:

    -keep class *Example
    
  • Jika Anda menggunakan * dengan sendirinya, * akan bertindak sebagai alias untuk **. Misalnya, aturan penyimpanan berikut setara:

    -keepclasseswithmembers class * { public static void main(java.lang.String[];) }
    
    -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
    

Memeriksa nama Java yang dihasilkan

Saat menulis aturan penyimpanan, Anda harus menentukan class dan jenis referensi lainnya menggunakan namanya setelah dikompilasi ke bytecode Java (lihat Spesifikasi class dan Jenis untuk melihat contoh). Untuk memeriksa nama Java yang dihasilkan untuk kode Anda, gunakan salah satu alat berikut di Android Studio:

  • APK Analyzer
  • Dengan file sumber Kotlin terbuka, periksa bytecode dengan membuka Tools > Kotlin > Show Kotlin Bytecode > Decompile.