Aşağıdaki örnekler, optimizasyon için R8'i kullandığınız ancak saklama kurallarını oluşturmak için gelişmiş rehberliğe ihtiyaç duyduğunuz yaygın senaryolara dayanmaktadır.
Yansıma
Genel olarak, optimum performans için yansıma kullanılması önerilmez. Ancak bazı durumlarda bu durum kaçınılmaz olabilir. Aşağıdaki örneklerde, yansıtma kullanan yaygın senaryolarda saklama kuralları için rehberlik sağlanmaktadır.
Yansıma, kendi uygulamanızda veya kitaplık kodunuzda yer alıyorsa saklama kurallarını manuel olarak yazmak yerine saklama ek açıklamalarını kullanabilirsiniz. Bir Gradle eklentisi, kitaplıklar için tüketici saklama kuralları da dahil olmak üzere kaynak kodunuzdaki ek açıklamalardan saklama kuralları oluşturur.
Adla yüklenen sınıflarla yansıma
Kitaplıklar genellikle sınıf adını String olarak kullanarak sınıfları dinamik olarak yükler.
Ancak R8, bu şekilde yüklenen sınıfları algılayamaz ve kullanılmadığını düşündüğü sınıfları kaldırabilir.
Örneğin, bir kitaplığınızın ve kitaplığı kullanan bir uygulamanızın olduğu aşağıdaki senaryoyu ele alalım. Kod, bir uygulama tarafından uygulanan bir StartupTask arayüzünü örnekleyen bir kitaplık yükleyiciyi gösterir.
Kitaplık kodu aşağıdaki gibidir:
// The interface for a task that runs once.
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(className: String) {
// R8 won't retain classes specified by this string value at runtime
val taskClass = Class.forName(className)
val task = taskClass.getDeclaredConstructor().newInstance() as StartupTask
task.run()
}
}
Kitaplığı kullanan uygulamada aşağıdaki kod bulunur:
// The app's task to pre-cache data.
// R8 will remove this class because it's only referenced by a string.
class PreCacheTask : StartupTask {
override fun run() {
// This log will never appear if the class is removed by R8.
Log.d("AppTask", "Warming up the cache...")
}
}
fun onCreate() {
// The library is told to run the app's task by its name.
TaskRunner.execute("com.example.app.PreCacheTask")
}
Bu senaryoda kitaplığınız, aşağıdaki saklama kurallarını içeren bir tüketici saklama kuralları dosyası içermelidir:
-keep class * implements com.example.library.StartupTask {
<init>();
}
Bu kural olmadan R8, uygulama sınıfı doğrudan kullanmadığı için PreCacheTask öğesini uygulamadan kaldırır ve entegrasyonu bozar. Kural, kitaplığınızın StartupTask arayüzünü uygulayan sınıfları bulur ve bağımsız değişken içermeyen kurucularıyla birlikte korur. Böylece kitaplık, PreCacheTask sınıfını başarıyla örneklendirebilir ve yürütebilir.
::class.java ile düşünme egzersizi
Kitaplıklar, Class nesnesini doğrudan uygulamaya ileterek sınıfları yükleyebilir. Bu yöntem, sınıfları ada göre yüklemekten daha sağlamdır. Bu, R8'in algılayabileceği sınıfa güçlü bir referans oluşturur. Ancak bu, R8'in sınıfı kaldırmasını engellese de sınıfın yansıtmalı olarak örneklendirildiğini bildirmek ve yapıcı gibi yansıtmalı olarak erişilen üyeleri korumak için yine de bir saklama kuralı kullanmanız gerekir.
Örneğin, bir kitaplığınız ve bu kitaplığı kullanan bir uygulamanız olduğunu varsayalım. Kitaplık yükleyici, sınıf referansını doğrudan ileterek bir StartupTask arayüzünü örneklendirir.
Kitaplık kodu aşağıdaki gibidir:
// The interface for a task that runs once.
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()
}
}
Kitaplığı kullanan uygulamada aşağıdaki kod bulunur:
// The app's task is to pre-cache data.
class PreCacheTask : StartupTask {
override fun run() {
Log.d("AppTask", "Warming up the cache...")
}
}
fun onCreate() {
// The library is given a direct reference to the app's task class.
TaskRunner.execute(PreCacheTask::class.java)
}
Bu senaryoda, kitaplığınızda aşağıdaki saklama kurallarını içeren bir tüketici saklama kuralları dosyası bulunmalıdır:
# Allow any implementation of StartupTask to be removed if unused.
-keep,allowobfuscation,allowshrinking class * implements com.example.library.StartupTask
# Keep the default constructor, which is called via reflection.
-keepclassmembers class * implements com.example.library.StartupTask {
<init>();
}
Bu kurallar, bu tür yansıtmayla mükemmel şekilde çalışacak şekilde tasarlanmıştır. Kodun doğru şekilde çalıştığından emin olurken maksimum optimizasyona olanak tanır. Kurallar, R8'in sınıf adını gizlemesine ve uygulama tarafından hiç kullanılmıyorsa StartupTask sınıfının uygulamasını küçültmesine veya kaldırmasına olanak tanır. Ancak örnekte kullanılan PrecacheTask gibi herhangi bir uygulama için kitaplığınızın çağırması gereken varsayılan oluşturucuyu (<init>()) korurlar.
-keep,allowobfuscation,allowshrinking class * implements com.example.library.StartupTask: Bu kural,StartupTaskarayüzünüzü uygulayan tüm sınıfları hedefler.-keep class * implements com.example.library.StartupTask: Bu, arayüzünüzü uygulayan tüm sınıfları (*) korur.,allowobfuscation: Bu, R8'e sınıfı tutmasına rağmen yeniden adlandırabileceği veya karartabileceği talimatını verir. Kitaplığınız sınıfın adına bağlı olmadığından veClassnesnesini doğrudan aldığından bu işlem güvenlidir.,allowshrinking: Bu değiştirici, R8'e kullanılmayan sınıfı kaldırabileceğini bildirir. Bu, R8'inTaskRunner.execute()'ye hiçbir zaman aktarılmayanStartupTaskuygulamasını güvenli bir şekilde silmesine yardımcı olur. Kısacası bu kural şunları ifade eder: Bir uygulamaStartupTaskuygulayan bir sınıf kullanıyorsa R8 bu sınıfı korur. R8, sınıfı yeniden adlandırarak boyutunu küçültebilir ve uygulama tarafından kullanılmıyorsa silebilir.
-keepclassmembers class * implements com.example.library.StartupTask { <init>(); }: Bu kural, ilk kuralda tanımlanan sınıfların belirli üyelerini (bu örnekte yapıcı) hedefler.-keepclassmembers class * implements com.example.library.StartupTask: Bu,StartupTaskarayüzünü uygulayan sınıfın belirli üyelerini (yöntemler, alanlar) korur ancak yalnızca uygulanan sınıfın kendisi korunuyorsa.{ <init>(); }: Bu, üye seçicidir.<init>, Java bayt kodundaki bir kurucunun özel dahili adıdır. Bu bölüm, özellikle varsayılan, bağımsız değişken içermeyen yapıcıyı hedef alır.- Kodunuz,
getDeclaredConstructor().newInstance()yöntemini bağımsız değişken olmadan çağırarak varsayılan kurucuyu yansıtmalı olarak çağırdığı için bu kural çok önemlidir. Bu kural olmadan R8, hiçbir kodun doğrudannew PreCacheTask()öğesini çağırmadığını görür, kurucunun kullanılmadığını varsayar ve kaldırır. Bu durum, uygulamanızın çalışma zamanındaInstantiationExceptionile kilitlenmesine neden olur.
Yöntem ek açıklamasına dayalı yansıma
Kitaplıklar genellikle geliştiricilerin yöntemleri veya alanları etiketlemek için kullandığı ek açıklamaları tanımlar.
Kitaplık, çalışma zamanında bu ek açıklamalı üyeleri bulmak için yansımayı kullanır. Örneğin, @OnLifecycleEvent ek açıklaması, çalışma zamanında gerekli yöntemleri bulmak için kullanılır.
Örneğin, bir kitaplığınız ve bu kitaplığı kullanan bir uygulamanız olduğunu varsayalım. Bu örnekte, @OnEvent ile ek açıklama eklenmiş yöntemleri bulan ve çağıran bir etkinlik veri yolu gösterilmektedir.
Kitaplık kodu aşağıdaki gibidir:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class OnEvent
class EventBus {
fun dispatch(listener: Any) {
// Find all methods annotated with @OnEvent and invoke them
listener::class.java.declaredMethods.forEach { method ->
if (method.isAnnotationPresent(OnEvent::class.java)) {
try {
method.invoke(listener)
} catch (e: Exception) { /* ... */ }
}
}
}
}
Kitaplığı kullanan uygulamada aşağıdaki kod bulunur:
class MyEventListener {
@OnEvent
fun onSomethingHappened() {
// This method will be removed by R8 without a keep rule
Log.d(TAG, "Event received!")
}
}
fun onCreate() {
// Instantiate the listener and the event bus
val listener = MyEventListener()
val eventBus = EventBus()
// Dispatch the listener to the event bus
eventBus.dispatch(listener)
}
Kitaplık, ek açıklamalarını kullanan tüm yöntemleri otomatik olarak koruyan bir tüketici saklama kuralları dosyası içermelidir:
-keepattributes RuntimeVisibleAnnotations
-keep @interface com.example.library.OnEvent;
-keepclassmembers class * {
@com.example.library.OnEvent <methods>;
}
-keepattributes RuntimeVisibleAnnotations: Bu kural, çalışma zamanında okunması amaçlanan ek açıklamaları korur.-keep @interface com.example.library.OnEvent: Bu kural,OnEventek açıklama sınıfının kendisini korur.-keepclassmembers class * {@com.example.library.OnEvent <methods>;}: Bu kural, yalnızca sınıf kullanılıyorsa ve sınıf bu üyeleri içeriyorsa bir sınıfı ve belirli üyeleri korur.-keepclassmembers: Bu kural, yalnızca sınıf kullanılıyorsa ve sınıf bu üyeleri içeriyorsa bir sınıfı ve belirli üyeleri korur.class *: Kural tüm sınıflar için geçerlidir.@com.example.library.OnEvent <methods>;: Bu,@com.example.library.OnEventile açıklama eklenmiş bir veya daha fazla yöntemi (<methods>) olan sınıfları ve açıklama eklenmiş yöntemlerin kendilerini korur.
Sınıf notlarına dayalı düşünme
Kitaplıklar, belirli bir ek açıklamaya sahip sınıfları taramak için yansımayı kullanabilir. Bu durumda, görev çalıştırıcı sınıfı, yansıma kullanarak ReflectiveExecutor ile ek açıklama eklenmiş tüm sınıfları bulur ve execute yöntemini yürütür.
Örneğin, bir kitaplığınız ve bu kitaplığı kullanan bir uygulamanız olduğunu varsayalım.
Kitaplıkta aşağıdaki kod bulunur:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class ReflectiveExecutor
class TaskRunner {
fun process(task: Any) {
val taskClass = task::class.java
if (taskClass.isAnnotationPresent(ReflectiveExecutor::class.java)) {
val methodToCall = taskClass.getMethod("execute")
methodToCall.invoke(task)
}
}
}
Kitaplığı kullanan uygulamada aşağıdaki kod bulunur:
// In consumer app
@ReflectiveExecutor
class ImportantBackgroundTask {
fun execute() {
// This class will be removed by R8 without a keep rule
Log.e("ImportantBackgroundTask", "Executing the important background task...")
}
}
// Usage of ImportantBackgroundTask
fun onCreate(){
val task = ImportantBackgroundTask()
val runner = TaskRunner()
runner.process(task)
}
Kitaplık, belirli sınıfları almak için yansıtma özelliğini yansıtıcı bir şekilde kullandığından kitaplık, aşağıdaki koruma kurallarını içeren bir tüketici koruma kuralları dosyası içermelidir:
# Retain annotation metadata for runtime reflection.
-keepattributes RuntimeVisibleAnnotations
# Keep the annotation interface itself.
-keep @interface com.example.library.ReflectiveExecutor
# Keep the execute method in the classes which are being used
-keepclassmembers @com.example.library.ReflectiveExecutor class * {
public void execute();
}
Bu yapılandırma, R8'e tam olarak neyi koruyacağını söylediği için son derece verimlidir.
İsteğe bağlı bağımlılıkları desteklemek için yansıtma
Yansıma için yaygın bir kullanım alanı, temel kitaplık ile isteğe bağlı eklenti kitaplığı arasında zayıf bir bağımlılık oluşturmaktır. Çekirdek kitaplık, eklentinin uygulamaya dahil edilip edilmediğini kontrol edebilir ve dahil edilmişse ek özellikleri etkinleştirebilir. Bu sayede, çekirdek kitaplığı doğrudan bağımlı olmaya zorlamadan eklenti modülleri gönderebilirsiniz.
Temel kitaplık, ada göre belirli bir sınıfı aramak için yansıtma (Class.forName) kullanır. Sınıf bulunursa özellik etkinleştirilir. Aksi takdirde, işlem sorunsuz bir şekilde başarısız olur.
Örneğin, bir çekirdek AnalyticsManager sınıfının video analizini etkinleştirmek için isteğe bağlı bir VideoEventTracker sınıfını kontrol ettiği aşağıdaki kodu ele alalım.
Temel kitaplıkta aşağıdaki kod bulunur:
object AnalyticsManager {
private const val VIDEO_TRACKER_CLASS = "com.example.analytics.video.VideoEventTracker"
fun initialize() {
try {
// Attempt to load the optional module's class using reflection
Class.forName(VIDEO_TRACKER_CLASS).getDeclaredConstructor().newInstance()
Log.d(TAG, "Video tracking enabled.")
} catch (e: ClassNotFoundException) {
Log.d(TAG,"Video tracking module not found. Skipping.")
} catch (e: Exception) {
Log.e(TAG, e.printStackTrace())
}
}
}
İsteğe bağlı video kitaplığında aşağıdaki kod bulunur:
package com.example.analytics.video
class VideoEventTracker {
// This constructor must be kept for the reflection call to succeed.
init { /* ... */ }
}
İsteğe bağlı kitaplığın geliştiricisi, gerekli tüketici saklama kuralını sağlamaktan sorumludur. Bu saklama kuralı, isteğe bağlı kitaplığı kullanan tüm uygulamaların, çekirdek kitaplığın bulması gereken kodu korumasını sağlar.
# In the video library's consumer keep rules file
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
Bu kural olmadan R8, modüldeki hiçbir şey doğrudan kullanmadığı için VideoEventTracker öğesini isteğe bağlı kitaplıktan kaldırabilir. Keep kuralı, sınıfı ve kurucusunu koruyarak temel kitaplığın sınıfı başarıyla örneklendirmesini sağlar.
Özel üyelere erişmek için yansıma
Bir kitaplığın genel API'sinin parçası olmayan özel veya korumalı koda erişmek için yansıma kullanmak önemli sorunlara yol açabilir. Bu tür kodlar önceden haber verilmeksizin değiştirilebilir. Bu da uygulamanızda beklenmeyen davranışlara veya kilitlenmelere yol açabilir.
Herkese açık olmayan API'ler için yansımayı kullandığınızda aşağıdaki sorunlarla karşılaşabilirsiniz:
- Engellenen güncellemeler: Özel veya korumalı koddaki değişiklikler, daha yüksek kitaplık sürümlerine güncellemenizi engelleyebilir.
- Kaçırılan avantajlar: Yeni işlevleri, önemli kilitlenme düzeltmelerini veya temel güvenlik güncellemelerini kaçırabilirsiniz.
R8 optimizasyonları ve yansıma
Bir kitaplığın özel veya korumalı koduna yansıtmanız gerekiyorsa R8'in optimizasyonlarına çok dikkat edin. Bu üyelere doğrudan referans yoksa R8, bunların kullanılmadığını varsayabilir ve daha sonra kaldırabilir veya yeniden adlandırabilir.
Bu durum, çalışma zamanı çökmelerine yol açabilir. Bu çökmeler genellikle NoSuchMethodException veya NoSuchFieldException gibi yanıltıcı hata mesajlarıyla birlikte görülür.
Örneğin, bir kitaplık sınıfından özel bir alana nasıl erişebileceğinizi gösteren aşağıdaki senaryoyu göz önünde bulundurun.
Sahibi olmadığınız bir kitaplıkta aşağıdaki kod var:
class LibraryClass {
private val secretMessage = "R8 will remove me"
}
Uygulamanızda aşağıdaki kod var:
fun accessSecretMessage(instance: LibraryClass) {
// Use Java reflection from Kotlin to access the private field
val secretField = instance::class.java.getDeclaredField("secretMessage")
secretField.isAccessible = true
// This will crash at runtime with R8 enabled
val message = secretField.get(instance) as String
}
R8'in özel alanı kaldırmasını önlemek için uygulamanıza bir -keep kuralı ekleyin:
-keepclassmembers class com.example.LibraryClass {
private java.lang.String secretMessage;
}
-keepclassmembers: Bu, bir sınıfın belirli üyelerini yalnızca sınıfın kendisi korunuyorsa korur.class com.example.LibraryClass: Bu, alanı içeren tam sınıfı hedefler.private java.lang.String secretMessage;: Bu, belirli özel alanı adıyla ve türüyle tanımlar.
Java Native Interface (JNI)
R8'in optimizasyonları, yerel (C/C++ kodu) dilden Java veya Kotlin'e yapılan yukarı çağrılarla çalışırken sorunlara neden olabilir. Tersi de geçerlidir. Java veya Kotlin'den yerel koda yapılan alt çağrılarda sorunlar yaşanabilir. Varsayılan proguard-android-optimize.txt dosyası, alt çağrıların çalışmaya devam etmesi için aşağıdaki kuralı içerir. Bu kural, yerel yöntemlerin kırpılmasını önler.
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
Java Native Interface (JNI) aracılığıyla yerel kodla etkileşim
Uygulamanız, yerel (C/C++) koddan Java veya Kotlin'e yukarı çağrı yapmak için JNI kullandığında R8, yerel kodunuzdan hangi yöntemlerin çağrıldığını göremez. Uygulamanızda bu yöntemlere doğrudan referans yoksa R8, bu yöntemlerin kullanılmadığını yanlış bir şekilde varsayıp kaldırır ve uygulamanızın kilitlenmesine neden olur.
Aşağıdaki örnekte, yerel bir kitaplıktan çağrılması amaçlanan bir yönteme sahip Kotlin sınıfı gösterilmektedir. Yerel kitaplık, bir uygulama türünü örneklendirir ve yerel koddan Kotlin koduna veri aktarır.
package com.example.models
// This class is used in the JNI bridge method signature
data class NativeData(val id: Int, val payload: String)
package com.example.app
// In package com.example.app
class JniBridge {
/**
* This method is called from the native side.
* R8 will remove it if it's not kept.
*/
fun onNativeEvent(data: NativeData) {
Log.d(TAG, "Received event from native code: $data")
}
// Use 'external' to declare a native method
external fun startNativeProcess()
companion object {
init {
// Load the native library
System.loadLibrary("my-native-lib")
}
}
}
Bu durumda, uygulama türünün optimize edilmesini önlemek için R8'i bilgilendirmeniz gerekir. Ayrıca, yerel koddan yapılan yöntem çağrıları imzalarında parametre veya dönüş türü olarak kendi sınıflarınızı kullanıyorsa bu sınıfların yeniden adlandırılmadığını da doğrulamanız gerekir.
Uygulamanıza aşağıdaki saklama kurallarını ekleyin:
-keepclassmembers,includedescriptorclasses class com.example.JniBridge {
public void onNativeEvent(com.example.model.NativeData);
}
-keep class NativeData{
<init>(java.lang.Integer, java.lang.String);
}
Bu saklama kuralları, R8'in onNativeEvent yöntemini ve en önemlisi parametre türünü kaldırmasını veya yeniden adlandırmasını engeller.
-keepclassmembers,includedescriptorclasses class com.example.JniBridge{ public void onNativeEvent(com.example.model.NativeData);}: Bu, bir sınıfın belirli üyelerini yalnızca sınıf önce Kotlin veya Java kodunda oluşturulursa korur. Bu, R8'e uygulamanın sınıfı kullandığını ve sınıfın belirli üyelerini koruması gerektiğini söyler.-keepclassmembers: Bu, bir sınıfın belirli üyelerini yalnızca sınıfın Kotlin veya Java kodunda örneklendirilmesi durumunda korur. R8'e, uygulamanın sınıfı kullandığını ve sınıfın belirli üyelerini koruması gerektiğini bildirir.class com.example.JniBridge: Bu, alanı içeren tam sınıfı hedefler.includedescriptorclasses: Bu değiştirici, yöntemin imzasında veya tanımlayıcısında bulunan tüm sınıfları da korur. Bu durumda, R8'in parametre olarak kullanılancom.example.models.NativeDatasınıfını yeniden adlandırmasını veya kaldırmasını engeller.NativeDatayeniden adlandırılırsa (ör.a.aolarak) yöntem imzası artık yerel kodun beklediğiyle eşleşmez ve kilitlenmeye neden olur.public void onNativeEvent(com.example.models.NativeData);: Bu, korunacak yöntemin tam Java imzasını belirtir.
-keep class NativeData{<init>(java.lang.Integer, java.lang.String);}:includedescriptorclasses,NativeDatasınıfının kendisinin korunmasını sağlarkenNativeDataiçindeki ve yerel JNI kodunuzdan doğrudan erişilen tüm üyeler (alanlar veya yöntemler) için kendi koruma kuralları gerekir.-keep class NativeData: Bu,NativeDataadlı sınıfı hedefler ve blok,NativeDatasınıfındaki hangi üyelerin tutulacağını belirtir.<init>(java.lang.Integer, java.lang.String): Bu, oluşturucunun imzasıdır. Bu işlev, iki parametre alan oluşturucuyu benzersiz şekilde tanımlar: ilkiInteger, ikincisi iseString.
Ayrıca, yerel kodun çağırdığı yöntemlere saklama kuralları yazmak yerine Jetpack Keep'ten @UnconditionallyKeep ek açıklamasını ekleyebilirsiniz. Ek açıklama, yöntemin imzasındaki sınıfları (ör. NativeData) korumaz. Bu nedenle, bu sınıfları ayrı ayrı korumanız gerekir.
Dolaylı platform çağrıları
Parcelable uygulamasıyla veri aktarma
Android çerçevesi, Parcelable nesnelerinizin örneklerini oluşturmak için yansımayı kullanır. Modern Kotlin geliştirmede, çerçeve için gerekli CREATOR alanını ve yöntemleri de içeren gerekli Parcelable uygulamasını otomatik olarak oluşturan kotlin-parcelize eklentisini kullanmanız gerekir.
Örneğin, kotlin-parcelize eklentisinin Parcelable sınıfı oluşturmak için kullanıldığı aşağıdaki örneği ele alalım:
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
// Add the @Parcelize annotation to your data class
@Parcelize
data class UserData(
val name: String,
val age: Int
) : Parcelable
Bu senaryoda önerilen saklama kuralı yoktur. kotlin-parcelize
Gradle eklentisi, @Parcelize ile ek açıklama eklediğiniz sınıflar için gerekli tutma kurallarını otomatik olarak oluşturur. Karmaşık işlemleri sizin yerinize hallederek oluşturulan CREATOR ve kurucuların Android çerçevesinin yansıma çağrıları için korunmasını sağlar.
@Parcelize kullanmadan Kotlin'de manuel olarak bir Parcelable sınıfı yazarsanız CREATOR alanını ve Parcel kabul eden oluşturucuyu saklamanız gerekir. Bunu yapmayı unutursanız sistem nesnenizi seri durumdan çıkarma işlemi yapmaya çalıştığında uygulamanız kilitlenir. @Parcelize kullanmak standart ve
daha güvenli bir uygulamadır.
kotlin-parcelize eklentisini kullanırken aşağıdakilere dikkat edin:
- Eklenti, derleme sırasında otomatik olarak
CREATORalan oluşturur. proguard-android-optimize.txtdosyası, bu alanların düzgün çalışması için gereklikeepkurallarını içerir.- Uygulama geliştiriciler, özellikle özel uygulamalar veya üçüncü taraf bağımlılıkları için gerekli tüm
keepkurallarının mevcut olduğunu doğrulamalıdır.
Popüler kütüphaneler
Yansıma veya bayt kodu dönüşümleri kullanan kitaplıklar, çalışma zamanında koda dinamik olarak erişir. R8, bu şekilde erişilen sınıfları, alanları veya yöntemleri kaldırır ya da yeniden adlandırırsa uygulamanız kilitlenebilir.
Ancak popüler üçüncü taraf kitaplıkları (ör. Gson, Retrofit ve Kotlinx Serialization), kendi R8 tüketici saklama kurallarını otomatik olarak paketler. Bu kitaplıkların son sürümlerini kullanırken projenize manuel saklama kuralları eklemeniz gerekmez.
Gson
Gson, büyük ölçüde yansımaya dayanan bir JSON serileştirme ve seri durumdan çıkarma kitaplığıdır. Uygulamanızı optimize etmek için tam modu kullandığınızda, aksi açıkça belirtilmediği sürece genel tür imzaları, varsayılan yapıcılar ve ek açıklama içermeyen alanlar kaldırılır.
Gson'un doğru şekilde çalışması için veri modeli sınıflarınızda geçici olmayan alanları tutacak ve TypeToken hiyerarşisini koruyacak belirli kurallar ekleyin:
# Preserve generic type information required for deserialization
-keepattributes Signature
# Keep all non-transient fields in your data model classes for reflection
-keepclassmembers class com.example.models.** {
!transient <fields>;
}
# Keep TypeToken itself and any anonymous classes extending it
-keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken { *; }
-keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken
transient değiştiricisiyle işaretlenen alanlar, Gson tarafından serileştirme ve seri durumdan çıkarma sırasında yoksayılır. Bu nedenle, keep kuralı özellikle geçici olmayan alanları (!transient) hedefler.
Güçlendirme
Retrofit, ağ istekleri oluşturmak ve yanıtları dönüştürmek için yansıma kullanarak HTTP ek açıklamalarıyla (@GET veya @POST gibi) ek açıklama eklenmiş hizmet arayüzü yöntemlerini inceleyen bir ağ kitaplığıdır.
Retrofit, Proxy.newProxyInstance() kullanarak çalışma zamanında API arayüzlerinizin uygulamalarını dinamik olarak oluşturur. R8, bu arayüzleri statik olarak uygulayan herhangi bir sınıf görmediğinden yöntemleri veya genel dönüş türlerini kaldırabilir.
Paketlenmiş saklama kuralları
Retrofit, genel parametreleri, yöntem ek açıklamalarını ve parametre ek açıklamalarını incelemek için çalışma zamanı yansıtmasına dayanır. Doğru yapılandırma olmadan R8 tam modu; dönüş türlerinden, Kotlin devamlılıklarından ve yanıt sınıflarından genel imzaları tamamen kaldırabilir, hatta Retrofit arayüzleri bir proxy ile dinamik olarak örneklendirildiğinden arayüz değerlerini null ile değiştirebilir.
Retrofit 2.10.0 sürümünden itibaren kitaplık, ek açıklama varsayılanlarını, hizmet yöntemi parametrelerini ve gerekli sınıf meta verilerini korumak için gereken resmi saklama kurallarını otomatik olarak paketler. Daha fazla bilgi için Retrofit tarafından kullanılan kurallar başlıklı makaleyi inceleyin.
Genel dönüş türlerini koruma
Retrofit, ağ yanıtını doğru şekilde seri durumdan çıkarmak için dönüş türünün genel imzasını (ör. Observable<Data>) inceler. R8, genel imzayı kaldırırsa Retrofit, örneklendirilmiş nesneyi null ile değiştirir.
R8 tam modunun, dönüş türlerinizin genel imzasını kaldırmasını önlemek için aşağıdaki koşullu kuralı kullanın:
# Preserve generic type information for Call/Observable return types
-keepattributes Signature
# If an interface has a Retrofit HTTP annotation, keep its return type (class <3>)
-if interface * {
@retrofit2.http.* public *** *(...);
}
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
Dönüştürücü (ör. Gson) tarafından yansıtıcı olarak oluşturulacağından, döndürülen gerçek veri modeli sınıfı (ör. Data in
Observable<Data>) da korunmalıdır.
Eş yordamlar
Kotlin eş yordamlarını kullandığınızda Kotlin derleyicisi, derlenen yöntem imzasına bir Continuation parametresi ekleyerek suspend işlevlerini dönüştürür.
Retrofit gibi kitaplıklar, bir suspend işlevinin genel imzasını yansıtıcı bir şekilde okuduğunda bu Continuation parametresine güvenir. Tam mod kullanılırken Signature özelliği yalnızca açıkça saklanan sınıflar için saklanır. Continuation sentetik bir parametre olduğundan R8, varsayılan olarak imzasını kaldırarak yansımayı bozar.
Tam modda çalışma zamanı uyumluluğunu sağlamak ve imza kaldırma işlemini önlemek için aşağıdaki kuralı ekleyin:
# Keep the signature attribute globally
-keepattributes Signature
# Explicitly keep the Continuation class so its signature is not stripped
-keep class kotlin.coroutines.Continuation