Genel olarak, saklama kuralı bir sınıfı (veya alt sınıfı ya da uygulamayı) ve ardından bu sınıfın korunacak üyelerini (yöntemler, oluşturucular veya alanlar) belirtir.
Saklama kuralının genel söz dizimi şu şekildedir:
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Aşağıda, keepclassmembers öğesini saklama seçeneği, allowoptimization öğesini değiştirici olarak kullanan ve someSpecificMethod() öğesini com.example.MyClass öğesinden saklayan bir saklama kuralı örneği verilmiştir:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Saklama seçeneği
Saklama seçeneği, saklama kuralınızın ilk bölümüdür. Sınıfın hangi yönlerinin korunacağını belirtir. Altı farklı saklama seçeneği vardır: keep,
keepclassmembers, keepclasseswithmembers, keepnames,
keepclassmembernames, keepclasseswithmembernames.
Aşağıdaki tabloda bu saklama seçenekleri açıklanmaktadır:
| Keep seçeneği | Açıklama |
|---|---|
keepclassmembers |
Yalnızca optimizasyondan sonra sınıf varsa belirtilen üyeleri korur. |
keep |
Belirtilen sınıfları ve belirtilen üyeleri (alanlar ve yöntemler) korur ve bunların optimize edilmesini engeller. Not: keep, genellikle yalnızca keep seçeneği değiştiricileriyle birlikte kullanılmalıdır. Bunun nedeni, keep'nin tek başına kullanıldığında eşleşen sınıflarda her türlü optimizasyonun yapılmasını engellemesidir. |
keepclasseswithmembers |
Bir sınıfı ve belirtilen üyelerini yalnızca sınıf spesifikasyonundaki tüm üyelere sahipse korur. |
keepclassmembernames |
Belirtilen sınıf üyelerinin yeniden adlandırılmasını engeller ancak sınıfın veya üyelerinin kaldırılmasını engellemez. Not: Bu seçeneğin anlamı genellikle yanlış anlaşılır. Bunun yerine eşdeğer olan -keepclassmembers,allowshrinking seçeneğini kullanabilirsiniz. |
keepnames |
Sınıfların ve üyelerinin yeniden adlandırılmasını engeller ancak kullanılmadığı düşünülen sınıfların tamamen kaldırılmasını engellemez. Not: Bu seçeneğin anlamı genellikle yanlış anlaşılır. Bunun yerine eşdeğer olan -keep,allowshrinking seçeneğini kullanabilirsiniz. |
keepclasseswithmembernames |
Sınıfların ve belirtilen üyelerinin yeniden adlandırılmasını engeller ancak yalnızca üyeler son kodda varsa. Kodun kaldırılmasını engellemez. Not: Bu seçeneğin anlamı genellikle yanlış anlaşılır. Bunun yerine eşdeğer olan -keepclasseswithmembers,allowshrinking seçeneğini kullanabilirsiniz. |
Doğru Keep seçeneğini belirleme
Doğru tutma seçeneğini belirlemek, uygulamanız için doğru optimizasyonu belirlemek açısından çok önemlidir. Belirli tutma seçenekleri, referans verilmeyen kodun kaldırıldığı bir işlem olan kodu küçültürken diğerleri kodu karartır veya yeniden adlandırır. Aşağıdaki tabloda, çeşitli saklama seçeneklerinin işlemleri belirtilmektedir:
| Keep seçeneği | Sınıfları daraltır | Sınıfları karartır | Shrinks üyeleri | Üyeleri karartır |
|---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Seçenek değiştiriciyi koruma
Saklama seçeneği değiştiricisi, saklama kuralının kapsamını ve davranışını kontrol etmek için kullanılır. Saklama kuralınıza 0 veya daha fazla saklama seçeneği değiştiricisi ekleyebilirsiniz.
Bir koruma seçeneği değiştiricisi için olası değerler aşağıdaki tabloda açıklanmıştır:
| Değer | Açıklama |
|---|---|
allowoptimization |
Belirtilen öğelerin optimize edilmesine olanak tanır. Ancak belirtilen öğeler yeniden adlandırılmaz veya kaldırılmaz. |
allowobfucastion |
Belirtilen öğelerin yeniden adlandırılmasına izin verir. Ancak öğeler kaldırılmaz veya başka bir şekilde optimize edilmez. |
allowshrinking |
R8, belirtilen öğelere referans bulamazsa bu öğelerin kaldırılmasına izin verir. Ancak öğeler yeniden adlandırılmaz veya başka bir şekilde optimize edilmez. |
includedescriptorclasses |
R8'e, tutulan yöntemlerin (parametre türleri ve dönüş türleri) ve alanların (alan türleri) tanımlayıcılarında görünen tüm sınıfları tutmasını bildirir. |
allowaccessmodification |
R8'in optimizasyon işlemi sırasında sınıfların, yöntemlerin ve alanların erişim değiştiricilerini (public, private, protected) değiştirmesine (genellikle genişletmesine) olanak tanır. |
allowrepackage |
R8'in sınıfları varsayılan (kök) paket de dahil olmak üzere farklı paketlere taşımasına olanak tanır. |
Sınıf özellikleri
Her saklama kuralında bir sınıf (arayüz, enum ve açıklama sınıfları dahil) belirtmeniz gerekir. İsterseniz bir üst sınıf veya uygulanan arayüz belirterek ya da sınıf için erişim değiştiricisini belirterek kuralı notlara göre kısıtlayabilirsiniz. java.lang ad alanındaki java.lang.String gibi sınıflar da dahil olmak üzere tüm sınıflar, tam nitelikli Java adları kullanılarak belirtilmelidir. Kullanılması gereken adları anlamak için Oluşturulan Java adlarını inceleme bölümünde açıklanan araçları kullanarak bayt kodunu inceleyin.
Aşağıdaki örnekte MaterialButton sınıfını nasıl belirtmeniz gerektiği gösterilmektedir:
- Doğru:
com.google.android.material.button.MaterialButton - Yanlış:
MaterialButton
Sınıf özellikleri, bir sınıfta tutulması gereken üyeleri de belirtir. Örneğin, aşağıdaki kural MyClass sınıfını ve someSpecificMethod() yöntemini korur:
-keep class com.example.MyClass {
void someSpecificMethod();
}
Notlara göre sınıfları belirtme
Sınıfları ek açıklamalarına göre belirtmek için ek açıklamanın tam nitelikli Java adının önüne @ simgesini ekleyin. Örneğin:
-keep class @com.example.MyAnnotation com.example.MyClass
Bir saklama kuralında birden fazla açıklama varsa listedeki tüm açıklamalara sahip sınıflar saklanır. Birden fazla ek açıklama listeleyebilirsiniz ancak kural yalnızca sınıfta listelenen tüm ek açıklamalar varsa geçerli olur. Örneğin, aşağıdaki kural hem Annotation1 hem de Annotation2 tarafından açıklama eklenen tüm sınıfları korur.
-keep class @com.example.Annotation1 @com.example.Annotation2 *
Alt sınıfları ve uygulamaları belirtme
Bir alt sınıfı veya arayüz uygulayan bir sınıfı hedeflemek için sırasıyla extend ve implements kullanın.
Örneğin, aşağıdaki gibi alt sınıfı Foo olan Bar sınıfınız varsa:
class Foo : Bar()
Aşağıdaki saklama kuralı, Bar sınıfının tüm alt sınıflarını korur. Saklama kuralının Bar üst sınıfını içermediğini unutmayın.
-keep class * extends Bar
Bar arayüzünü uygulayan Foo sınıfınız varsa:
class Foo : Bar
Aşağıdaki saklama kuralı, Bar uygulayan tüm sınıfları korur. Saklama kuralının Bar arayüzünü içermediğini unutmayın.
-keep class * implements Bar
Erişim değiştiricilere göre sınıfları belirtin
Not alma kurallarınızı daha hassas hale getirmek için public, private, static ve final gibi erişim değiştiricileri belirtebilirsiniz.
Örneğin, aşağıdaki kural, api paketindeki ve alt paketlerindeki tüm public sınıflarını ve bu sınıflardaki tüm genel ve korumalı üyeleri tutar.
-keep public class com.example.api.** { public protected *; }
Bir sınıftaki üyeler için değiştiriciler de kullanabilirsiniz. Örneğin, aşağıdaki kural yalnızca bir Utils sınıfının public static yöntemlerini saklar:
-keep class com.example.Utils {
public static void *(...);
}
Kotlin'e özgü değiştiriciler
R8, internal ve suspend gibi Kotlin'e özgü değiştiricileri desteklemez.
Bu tür alanları korumak için aşağıdaki yönergeleri kullanın.
internalsınıfını, yöntemini veya alanını korumak için herkese açık olarak değerlendirin. Örneğin, aşağıdaki Kotlin kaynağını ele alalım:package com.example internal class ImportantInternalClass { internal val f: Int internal fun m() {} }Kotlin derleyicisi tarafından oluşturulan
.classdosyalarındainternalsınıfları, yöntemleri ve alanlarıpublicolduğundan aşağıdaki örnekte gösterildiği gibipublicanahtar kelimesini kullanmanız gerekir:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }Bir
suspendüyesi derlendiğinde, derlenen imzasını keep kuralında eşleştirin.Örneğin,
fetchUserişlevini aşağıdaki snippet'te gösterildiği gibi tanımladıysanız:suspend fun fetchUser(id: String): UserDerlendiğinde, bayt kodundaki imzası aşağıdaki gibi görünür:
public final Object fetchUser(String id, Continuation<? super User> continuation);Bu işlev için saklama kuralı yazmak üzere bu derlenmiş imzayla eşleşmeniz veya
...kullanmanız gerekir.Derlenmiş imza kullanımına dair bir örnek aşağıda verilmiştir:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }...kullanan bir örneği aşağıda bulabilirsiniz:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Üyelik şartları
Sınıf belirtimi, isteğe bağlı olarak korunacak sınıf üyelerini içerir. Bir sınıf için bir veya daha fazla üye belirtirseniz kural diğer üyeler için geçerli olmaz.
Üyeleri açıklamalara göre belirleme
Üyeleri ek açıklamalarına göre belirtebilirsiniz. Sınıflara benzer şekilde, açıklamanın tam nitelikli Java adının önüne @ ekleyin. Bu sayede, bir sınıfta yalnızca belirli ek açıklamalarla işaretlenmiş üyeleri tutabilirsiniz. Örneğin, @com.example.MyAnnotation ile açıklama eklenmiş yöntemleri ve alanları korumak için:
-keep class com.example.MyClass {
@com.example.MyAnnotation <methods>;
@com.example.MyAnnotation <fields>;
}
Güçlü ve hedeflenmiş kurallar için bunu sınıf düzeyinde ek açıklama eşleştirme ile birleştirebilirsiniz:
-keep class @com.example.ClassAnnotation * {
@com.example.MethodAnnotation <methods>;
@com.example.FieldAnnotation <fields>;
}
Bu, @ClassAnnotation ile açıklama eklenen sınıfları ve bu sınıflarda @MethodAnnotation ile açıklama eklenen yöntemleri ve @FieldAnnotation ile açıklama eklenen alanları korur.
Mümkün olduğunda açıklama tabanlı saklama kurallarını kullanmayı düşünebilirsiniz. Bu yaklaşım, kodunuz ile saklama kurallarınız arasında açık bir bağlantı sağlar ve genellikle daha sağlam yapılandırmalara yol açar. Örneğin, androidx.annotation ek açıklama kitaplığı bu mekanizmayı kullanır.
Yöntemler
Bir saklama kuralının üye spesifikasyonunda yöntem belirtme söz dizimi aşağıdaki gibidir:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
Örneğin, aşağıdaki koruma kuralı, void döndüren ve setLabel() alan setLabel() adlı herkese açık bir yöntemi korur.String
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Bir sınıftaki tüm yöntemleri aşağıdaki gibi eşleştirmek için <methods> kısayolunu kullanabilirsiniz:
-keep class com.example.MyView {
<methods>;
}
Dönüş türleri ve parametre türleri için türlerin nasıl belirtileceği hakkında daha fazla bilgi edinmek için Türler başlıklı makaleyi inceleyin.
Üreticiler
Bir oluşturucu belirtmek için <init> öğesini kullanın. Bir saklama kuralının üye spesifikasyonunda oluşturucu belirtme söz dizimi aşağıdaki gibidir:
[<access_modifier>] <init>(parameter_types);
Örneğin, aşağıdaki koruma kuralı, Context ve AttributeSet alan özel bir View oluşturucuyu korur.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
Herkese açık tüm oluşturucuları tutmak için aşağıdaki örneği referans olarak kullanın:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Fields'ın oynadığı filmler
Saklama kuralı için üye spesifikasyonunda alan belirtme söz dizimi aşağıdaki gibidir:
[<access_modifier>...] [<type>] <field_name>;
Örneğin, aşağıdaki saklama kuralı userId adlı özel bir dize alanını ve STATUS_ACTIVE adlı herkese açık statik bir tam sayı alanını saklar:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
Bir sınıftaki tüm alanları aşağıdaki gibi eşleştirmek için <fields> kısayolunu kullanabilirsiniz:
-keep class com.example.models.User {
<fields>;
}
Türler
Bu bölümde, saklama kuralı üye spesifikasyonlarında dönüş türlerinin, parametre türlerinin ve alan türlerinin nasıl belirtileceği açıklanmaktadır. Kotlin kaynak kodundan farklı türler belirtmek için oluşturulan Java adlarını kullanmayı unutmayın.
Temel türler
Bir temel türü belirtmek için Java anahtar kelimesini kullanın. R8, aşağıdaki temel türleri tanır: boolean, byte, short, char, int, long, float, double.
Temel tür içeren bir kural örneği:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
Genel türler
Derleme sırasında Kotlin/Java derleyicisi genel tür bilgilerini siler. Bu nedenle, genel türleri içeren keep kuralları yazdığınızda kodunuzun derlenmiş gösterimini hedeflemeniz gerekir. Orijinal kaynak kodu hedeflememelisiniz. Jenerik türlerin nasıl değiştirildiği hakkında daha fazla bilgi edinmek için Tür silme konusuna bakın.
Örneğin, Box.kt içinde tanımlanmış sınırsız bir genel tür içeren aşağıdaki koda sahipseniz:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
Tür silme işleminden sonra T yerine Object kullanılır. Sınıf oluşturucuyu ve yöntemi korumak için kuralınızda genel T yerine java.lang.Object kullanılmalıdır.
Örnek bir saklama kuralı aşağıdaki gibi olabilir:
# 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();
}
NumberBox.kt içinde sınırlanmış bir genel tür içeren aşağıdaki kodunuz varsa:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
Bu durumda, tür silme işlemi T değerini sınırlı değeri olan java.lang.Number ile değiştirir.
Örnek bir saklama kuralı aşağıdaki gibi olabilir:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
Uygulamaya özel genel türleri temel sınıf olarak kullanırken temel sınıflar için de saklama kurallarını eklemek gerekir.
Örneğin, aşağıdaki kod için:
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) {
}
includedescriptorclasses ile birlikte bir saklama kuralı kullanarak hem UnpackOptions sınıfını hem de Box sınıfı yöntemini tek bir kuralda aşağıdaki gibi koruyabilirsiniz:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
Bir nesne listesini işleyen belirli bir işlevi korumak için işlevin imzasıyla tam olarak eşleşen bir kural yazmanız gerekir. Genel türler silindiğinden List<Product> gibi bir parametrenin java.util.List olarak görüleceğini unutmayın.
Örneğin, bir Product nesne listesini aşağıdaki şekilde işleyen bir işlevi olan bir yardımcı sınıfınız varsa:
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)
Yalnızca processProducts işlevini korumak için aşağıdaki saklama kuralını kullanabilirsiniz:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Dizi türleri
Dizinin her boyutu için bileşen türüne [] ekleyerek bir dizi türü belirtin. Bu durum hem sınıf türleri hem de temel türler için geçerlidir.
- Tek boyutlu sınıf dizisi:
java.lang.String[] - İki boyutlu temel dizi:
int[][]
Örneğin, aşağıdaki koda sahipseniz:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
Aşağıdaki saklama kuralını kullanabilirsiniz:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Örnekler
Örneğin, belirli bir sınıfı ve tüm üyelerini korumak için aşağıdakileri kullanın:
-keep class com.myapp.MyClass { *; }
Yalnızca sınıfı ve varsayılan oluşturucusunu korumak, ancak diğer üyeleri korumamak için aşağıdakileri kullanın:
-keep class com.myapp.MyClass
Her zaman bazı üyeleri belirtmeniz önerilir. Örneğin, aşağıdaki örnekte text herkese açık alanı ve updateText() herkese açık yöntemi MyClass sınıfında tutulur.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
Herkese açık alanları ve herkese açık yöntemleri korumak için aşağıdaki örneğe bakın:
-keep public class com.example.api.ApiClient {
public *;
}
Üye belirtimini atlama
Üye spesifikasyonunun atlanması, R8'in sınıf için varsayılan oluşturucuyu tutmasına neden olur.
Örneğin, -keep class com.example.MyClass veya -keep class com.example.MyClass {} yazarsanız R8 bunları aşağıdaki gibi yazmışsınız gibi değerlendirir:
-keep class com.example.MyClass{
void <init>();
}
Paket düzeyindeki işlevler
Bir sınıfın dışında tanımlanan bir Kotlin işlevine (genellikle üst düzey işlevler olarak adlandırılır) referans vermek için Kotlin derleyicisi tarafından örtülü olarak eklenen sınıf için oluşturulan Java adını kullandığınızdan emin olun. Sınıf adı, Kt eklenmiş Kotlin dosya adıdır. Örneğin, aşağıdaki gibi tanımlanmış MyClass.kt adlı bir Kotlin dosyanız varsa:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
isEmailValid işlevi için saklama kuralı yazmak üzere sınıf belirtiminin, oluşturulan MyClassKt sınıfını hedeflemesi gerekir:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Joker karakterler
Aşağıdaki tabloda, belirli bir kalıba uyan birden fazla sınıfa veya üyeye saklama kurallarını uygulamak için joker karakterlerin nasıl kullanılacağı gösterilmektedir.
| Joker karakter | Sınıflar veya üyeler için geçerlidir | Açıklama |
|---|---|---|
| ** | Her ikisi de | En sık kullanılan. Paket ayırıcı sayısı da dahil olmak üzere herhangi bir tür adıyla eşleşir. Bu, bir paketteki ve alt paketlerindeki tüm sınıfları eşleştirmek için kullanışlıdır. |
| * | Her ikisi de | Sınıf belirtimleri için, paket ayırıcıları (.) içermeyen bir tür adının herhangi bir bölümüyle eşleşir. Üye belirtimleri için herhangi bir yöntem veya alan adıyla eşleşir. Tek başına kullanıldığında ** için de takma ad olarak kullanılır. |
| ? | Her ikisi de | Bir sınıf veya üye adındaki herhangi bir tek karakterle eşleşir. |
| *** | Üyeler | İlkel türler (ör. int), sınıf türleri (ör. java.lang.String) ve herhangi bir boyuttaki dizi türleri (ör. byte[][]) dahil olmak üzere tüm türlerle eşleşir. |
| ... | Üyeler | Bir yöntem için herhangi bir parametre listesiyle eşleşir. |
| % | Üyeler | Herhangi bir temel türle (ör. "int", "float", "boolean" vb.) eşleşir. |
Özel joker karakterlerin nasıl kullanılacağına dair bazı örnekleri aşağıda bulabilirsiniz:
Giriş olarak farklı temel türleri alan aynı ada sahip birden fazla yönteminiz varsa hepsini tutan bir tutma kuralı yazmak için
%kullanabilirsiniz. Örneğin, buDataStoresınıfında birden fazlasetValueyöntemi vardır:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }Aşağıdaki saklama kuralı tüm yöntemleri saklar:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }Adları bir karakter farklılık gösteren birden fazla sınıfınız varsa hepsini saklayacak bir saklama kuralı yazmak için
?kullanın. Örneğin, aşağıdaki sınıflarınız varsa:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}Aşağıdaki saklama kuralı tüm sınıfları saklar:
-keep class com.example.models.UserV?ExampleveAnotherExamplesınıflarını (kök düzeyinde sınıflar olmaları durumunda) eşleştirmek ancakcom.foo.Examplesınıfını eşleştirmemek için aşağıdaki saklama kuralını kullanın:-keep class *ExampleTek başına kullanılan * işareti, ** için takma ad görevi görür. Örneğin, aşağıdaki saklama kuralları eşdeğerdir:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Oluşturulan Java adlarını inceleme
Saklama kuralları yazarken, sınıfları ve diğer referans türlerini Java bayt koduna derlendikten sonra adlarını kullanarak belirtmeniz gerekir (örnekler için Sınıf spesifikasyonu ve Türler'e bakın). Kodunuz için oluşturulan Java adlarının ne olduğunu kontrol etmek üzere Android Studio'da aşağıdaki araçlardan birini kullanın:
- APK Analyzer
- Kotlin kaynak dosyası açıkken Tools > Kotlin > Show Kotlin Bytecode > Decompile'a (Araçlar > Kotlin > Kotlin Bayt Kodunu Göster > Derlemeyi Kaldır) giderek bayt kodunu inceleyin.