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
adalahpublic
dalam file.class
yang dihasilkan oleh compiler Kotlin, jadi Anda harus menggunakan kata kuncipublic
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, classDataStore
ini memiliki beberapa metodesetValue
: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
danAnotherExample
(jika merupakan class tingkat root), tetapi tidakcom.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.