R8'i tam modda kullanma

R8, uyumluluk modu ve tam mod olmak üzere iki mod sunar. Tam mod, uygulama performansınızı artıran güçlü optimizasyonlar sunar.

Bu kılavuz, R8'in en güçlü optimizasyonlarını kullanmak isteyen Android geliştiriciler içindir. Uyumluluk modu ile tam mod arasındaki temel farkları ele alır ve projenizi güvenli bir şekilde taşımak ve yaygın çalışma zamanı çökmelerini önlemek için gereken açık yapılandırmaları sağlar.

Tam modu etkinleştirme

Tam modu etkinleştirmek için gradle.properties dosyanızdan aşağıdaki satırı kaldırın:

android.enableR8.fullMode=false // Remove this line to enable full mode

Özelliklerle ilişkili sınıfları koruma

Özellikler, derlenmiş sınıf dosyalarında depolanan ve yürütülebilir kodun parçası olmayan meta verilerdir. Ancak belirli yansıtma türleri için gerekli olabilirler. Sık kullanılan örnekler arasında Signature (tür silme işleminden sonra genel tür bilgilerini korur), InnerClasses ve EnclosingMethod (sınıf yapısını yansıtmak için) ve çalışma zamanında görünür ek açıklamalar yer alır.

Aşağıdaki kodda, bir alandaki Signature özelliğinin nasıl göründüğü gösterilmektedir. Bir alan için:

List<User> users;

Derlenmiş sınıf dosyası aşağıdaki bayt kodunu içerir:

.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

Yansıtmayı yoğun olarak kullanan kitaplıklar (ör. Gson), kodunuzun yapısını dinamik olarak incelemek ve anlamak için genellikle bu özelliklere güvenir. R8'in tam modunda varsayılan olarak özellikler yalnızca ilişkili sınıf, alan veya yöntem açıkça korunursa saklanır.

Aşağıdaki örnekte, özelliklerin neden gerekli olduğu ve uyumluluk modundan tam moda geçiş yaparken hangi koruma kurallarını eklemeniz gerektiği gösterilmektedir. Yansıtılan sınıfları, alanları veya yöntemleri korumanın yanı sıra, bunların bağlı olduğu özellikleri de açıkça korumanız gerekir.

Gson kitaplığını kullanarak kullanıcı listesini seri durumundan çıkardığımız aşağıdaki örneği inceleyin.


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}")
}

Derleme sırasında Java'nın tür silme işlemi, genel tür bağımsız değişkenlerini kaldırır. Bu, çalışma zamanında hem List<String> hem de List<User>'nin ham List olarak görüneceği anlamına gelir. Bu nedenle, yansımaya dayalı Gson gibi kitaplıklar, bir JSON listesi seri durumdan çıkarılırken List öğesinin içerecek şekilde bildirildiği belirli nesne türlerini belirleyemez. Bu durum, çalışma zamanı sorunlarına yol açabilir.

Gson, tür bilgilerini korumak için TypeToken kullanır. Sarma TypeToken gerekli seri durumdan çıkarma bilgilerini korur.

object:TypeToken<List<User>>() {}.type Kotlin ifadesi, TypeToken öğesini genişleten ve genel tür bilgilerini yakalayan anonim bir iç sınıf oluşturur. Bu örnekte, anonim sınıfın adı $GsonRemoteJsonListExample$listType$1'dır.

Java programlama dili, bir üst sınıfın genel imzasını derlenmiş sınıf dosyasında Signature özelliği olarak bilinen meta veri olarak kaydeder. TypeToken, çalışma zamanında türü kurtarmak için bu Signature meta verilerini kullanır. Bu sayede Gson, Signature öğesini okumak için yansıtma özelliğini kullanabilir ve seri durumundan çıkarma için ihtiyaç duyduğu List<User> türünü başarıyla keşfedebilir.

R8, uyumluluk modunda etkinleştirildiğinde, belirli koruma kuralları açıkça tanımlanmamış olsa bile Signature gibi anonim iç sınıflar da dahil olmak üzere sınıflar için $GsonRemoteJsonListExample$listType$1 özelliğini korur. Sonuç olarak, R8 uyumluluk modunun bu örneğin beklendiği gibi çalışması için başka açık tutma kuralları gerekmez.

// keep rule for compatibility mode
-keepattributes Signature

R8 tam modda etkinleştirildiğinde anonim iç sınıfın Signature özelliği kaldırılır.$GsonRemoteJsonListExample$listType$1 Signature içinde bu tür bilgileri olmadan Gson, doğru uygulama türünü bulamaz ve bu da IllegalStateException ile sonuçlanır.

2.11.0'dan eski bir Gson sürümü kullanıyorsanız bunu önlemek için gereken koruma kuralları şunlardır:

// 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: Bu kural, R8'e Gson'un okuması gereken özelliği korumasını söyler. Tam modda R8, yalnızca bir keep kuralıyla açıkça eşleşen sınıflar, alanlar veya yöntemler için Signature özelliğini korur.

  • -keep,allowobfuscation,allowshrinking,allowoptimization class com.google.gson.reflect.TypeToken: Bu kural, TypeToken, seri durumdan çıkarılan nesnenin türünü sardığı için gereklidir. Tür silme işleminden sonra, genel tür bilgilerini korumak için anonim bir iç sınıf oluşturulur. com.google.gson.reflect.TypeToken açıkça korunmadığı sürece, R8 tam modda bu sınıf türünü seri durumdan çıkarma için gereken Signature özelliğine dahil etmez.

  • -keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken: Bu kural, TypeToken'yi genişleten anonim sınıfların tür bilgilerini (ör. bu örnekteki $GsonRemoteJsonListExample$listType$1) saklar. Bu kural olmadan, tam moddaki R8 gerekli tür bilgilerini kaldırarak seri durumdan çıkarma işleminin başarısız olmasına neden olur.

Daha önce paylaşılan kuralların yalnızca genel türü (ör. List<User>) keşfetme sorununu çözdüğünü anlamak önemlidir. R8, sınıfların alanlarını da yeniden adlandırır. Veri modellerinizde @SerializedName ek açıklamalarını kullanmıyorsanız alan adları artık JSON anahtarlarıyla eşleşmeyeceğinden Gson, JSON'ı seri durumundan çıkarma işlemini gerçekleştiremez.

Ancak 2.11'den eski bir Gson sürümü kullanıyorsanız veya modellerinizde @SerializedName ek açıklaması kullanılmıyorsa bu modeller için açık tutma kuralları eklemeniz gerekir.

Varsayılan oluşturucuyu koruma

R8 tam modunda, sınıfın kendisi korunsa bile bağımsız değişken içermeyen/varsayılan oluşturucu örtülü olarak korunmaz. class.getDeclaredConstructor().newInstance() veya class.newInstance() kullanarak bir sınıfın örneğini oluşturuyorsanız tam modda no-args oluşturucusunu açıkça korumanız gerekir. Buna karşılık, uyumluluk modunda her zaman no-args oluşturucu korunur.

PrecacheTask sınıfının bir örneğinin, run yöntemini dinamik olarak çağırmak için yansıtma kullanılarak oluşturulduğu bir örneği ele alalım. Bu senaryo, uyumluluk modunda ek kurallar gerektirmese de tam modda PrecacheTask öğesinin varsayılan oluşturucusu kaldırılır. Bu nedenle, belirli bir saklama kuralı gerekir.

// 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>();
}

Erişim değişikliği varsayılan olarak etkindir.

Uyumluluk modunda R8, bir sınıf içindeki yöntemlerin ve alanların görünürlüğünü değiştirmez. Ancak tam modda R8, yöntemlerinizin ve alanlarınızın görünürlüğünü (ör. özelden herkese açık hale) değiştirerek optimizasyonu iyileştirir. Bu sayede daha fazla satır içi reklam yayınlanabilir.

Kodunuz, üyelerin belirli bir görünürlüğe sahip olmasına özel olarak bağlı olan yansıtma kullanıyorsa bu optimizasyon sorunlara neden olabilir. R8, bu dolaylı kullanımı tanımayacağından uygulama kilitlenebilir. Bunu önlemek için üyeleri korumak üzere belirli -keep kuralları eklemeniz gerekir. Bu kurallar, üyelerin orijinal görünürlüğünü de korur.

Daha fazla bilgi için yansıtma kullanılarak özel üyelere erişmenin neden önerilmediğini ve bu alanları/yöntemleri korumak için hangi kurallara uyulması gerektiğini anlamak üzere bu örneğe bakın.

Kotlin'e özgü meta veriler

Kotlin derleyicisi, Kotlin kodunu derlerken dile özgü meta verileri (ör. nullability, uzantı işlevleri ve coroutine imzaları) her sınıf dosyasındaki @kotlin.Metadata ek açıklamasında saklar.

Uygulamanız veya bağımlılıkları Kotlin yansıtmasını (kotlin.reflect) kullanıyorsa yansıtma kitaplığı, sınıf yapısını incelemek için bu meta verileri çalışma zamanında ayrıştırır. R8 tam modunda, açıkça saklanmayan ek açıklamalar varsayılan olarak R8 tarafından kaldırılır. Ayrıca, R8 meta verileri korumadan ve güncellemeden sınıflarınızı küçültürse veya daraltırsa Kotlin yansıtması çalışma zamanında başarısız olur ve bu da öngörülemeyen davranışlara veya çökmelere (ör. KotlinReflectionInternalError) yol açar.

Öngörülemeyen davranışları önlemek ve Kotlin yansıtma işlevlerinin küçültme işleminden sonra doğru şekilde çalışmasını sağlamak için çalışma zamanında görünür olan ek açıklamaları korumanız ve kotlin.Metadata sınıfını açıkça saklamanız gerekir:

# Preserve runtime-visible annotations required for inspecting metadata
-keepattributes RuntimeVisibleAnnotations

# Keep Kotlin metadata to ensure kotlin.reflect functions correctly
-keep class kotlin.Metadata { *; }