R8 optimize edicisinin tüm potansiyelini etkinleştirme

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. Bu kılavuzda, uyumluluk modu ile tam mod arasındaki temel farklılıklar ele alınmakta 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ğlanmaktadır.

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ımayı 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.

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 özelliği, 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 listesini seri durumdan çıkarırken List öğesinin içereceğ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. Sarmalama TypeToken gerekli seri durumundan çı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 veriler şeklinde kaydeder. TypeToken, çalışma zamanında türü kurtarmak için bu Signature meta verilerini kullanır. Bu, Gson'un yansıtma kullanarak Signature öğesini okumasını ve seri durumdan çıkarma için ihtiyaç duyduğu List<User> türünü başarıyla bulmasını sağlar.

R8, uyumluluk modunda etkinleştirildiğinde, belirli koruma kuralları açıkça tanımlanmamış olsa bile $GsonRemoteJsonListExample$listType$1 gibi anonim iç sınıflar da dahil olmak üzere sınıflar için Signature ö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 tür bilgisi olmadan Gson, doğru uygulama türünü bulamaz ve bu da IllegalStateException ile sonuçlanır. Bunu önlemek için gerekli saklama 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, seri durumdan çıkarılan nesnenin türünü TypeToken sarmaladığı 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.

Gson 2.11.0 sürümünden itibaren kitaplık, tam modda seri durumdan çıkarma için gerekli keep kurallarını paketler. Uygulamanızı R8 etkin olarak oluşturduğunuzda R8, bu kuralları kitaplıktan otomatik olarak bulup uygular. Bu sayede, projenize bu kuralları manuel olarak eklemeniz veya bu kuralları projenizde tutmanız gerekmeden uygulamanızın ihtiyaç duyduğu koruma sağlanır.

Daha önce paylaşılan kuralların yalnızca genel türü (ör. List<User>). R8, sınıfların alanlarını da yeniden adlandırır. Veri modellerinizde @SerializedName ek açıklamaları kullanmıyorsanız alan adları artık JSON anahtarlarıyla eşleşmeyeceğinden Gson, JSON'ı seri durumdan çı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 bağımsız değişken içermeyen oluşturucuyu 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ı gereklidir.

// 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.

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 için bu örneğe bakın.