Bibliotheken mit Bedacht auswählen

Damit die App optimiert werden kann, müssen Sie Bibliotheken verwenden, die mit der Android-Optimierung kompatibel sind. Wenn eine Bibliothek nicht für die Android-Optimierung konfiguriert ist, z. B. wenn sie Reflection ohne zugehörige Keep-Regeln verwendet, ist sie möglicherweise nicht gut für eine Android-App geeignet. Auf dieser Seite wird erläutert, warum einige Bibliotheken besser für die App-Optimierung geeignet sind, und es werden allgemeine Tipps zur Auswahl gegeben.

Codegenerierung gegenüber Reflexion bevorzugen

Im Allgemeinen sollten Sie Bibliotheken auswählen, die Codegenerierung (codegen) anstelle von Reflection verwenden. Durch die Codegenerierung kann der Optimierer leichter ermitteln, welcher Code zur Laufzeit tatsächlich verwendet wird und welcher Code entfernt werden kann. Es kann schwierig sein, festzustellen, ob eine Bibliothek Codegenerierung oder Reflection verwendet. Es gibt jedoch einige Anzeichen. Tipps können dabei helfen.

Weitere Informationen zu Codegenerierung und Reflexion finden Sie unter Optimierung für Bibliotheksautoren.

Allgemeine Tipps zur Auswahl von Bibliotheken

Mit diesen Tipps können Sie dafür sorgen, dass Ihre Bibliotheken mit der App-Optimierung kompatibel sind.

Auf Optimierungsprobleme prüfen

Wenn Sie eine neue Bibliothek in Betracht ziehen, sollten Sie im Issue Tracker der Bibliothek und in Online-Diskussionen nachsehen, ob es Probleme im Zusammenhang mit der Minimierung oder der Konfiguration der App-Optimierung gibt. Wenn das der Fall ist, sollten Sie nach Alternativen für diese Bibliothek suchen. Beachten Sie Folgendes:

  • Die AndroidX-Bibliotheken und Bibliotheken wie Hilt eignen sich gut für die App-Optimierung, da sie Codegenerierung anstelle von Reflection verwenden. Wenn sie Reflection verwenden, stellen sie minimale Keep-Regeln bereit, um nur den benötigten Code beizubehalten.
  • Serialisierungsbibliotheken verwenden häufig Reflection, um Boilerplate-Code beim Instanziieren oder Serialisieren von Objekten zu vermeiden. Anstelle von reflexionsbasierten Ansätzen (z. B. Gson für JSON) sollten Sie nach Bibliotheken suchen, die Codegenerierung verwenden, um diese Probleme zu vermeiden, z. B. indem Sie stattdessen Kotlin Serialization verwenden.
  • Bibliotheken, die paketweite Keep-Regeln enthalten, sollten nach Möglichkeit vermieden werden. Paketweite Keep-Regeln können helfen, Fehler zu beheben. Breite Keep-Regeln sollten jedoch letztendlich verfeinert werden, damit nur der benötigte Code beibehalten wird. Weitere Informationen finden Sie unter Optimierungen schrittweise übernehmen.
  • Für Bibliotheken sollte es nicht erforderlich sein, dass Sie Keep-Regeln aus der Dokumentation in eine Datei in Ihrem Projekt kopieren und einfügen müssen, insbesondere nicht paketweite Keep-Regeln. Diese Regeln stellen langfristig eine Belastung für den App-Entwickler dar und sind im Laufe der Zeit schwer zu optimieren und zu ändern.

Optimierung nach dem Hinzufügen einer neuen Bibliothek aktivieren

Wenn Sie eine neue Bibliothek hinzufügen, aktivieren Sie die Optimierung anschließend und prüfen Sie, ob Fehler auftreten. Wenn Fehler auftreten, suchen Sie nach Alternativen zu dieser Bibliothek oder schreiben Sie Keep-Regeln. Wenn eine Bibliothek nicht mit der Optimierung kompatibel ist, melden Sie einen Fehler für diese Bibliothek.

Regeln werden kombiniert

Beachten Sie, dass Aufbewahrungsregeln additiv sind. Das bedeutet, dass bestimmte Regeln, die in einer Bibliotheksabhängigkeit enthalten sind, nicht entfernt werden können und sich auf die Kompilierung anderer Teile Ihrer App auswirken können. Wenn eine Bibliothek beispielsweise eine Regel zum Deaktivieren von Codeoptimierungen enthält, werden Optimierungen für Ihr gesamtes Projekt deaktiviert.

Verwendung von Reflection prüfen (erweitert)

Ob eine Bibliothek Reflection verwendet, lässt sich möglicherweise anhand des Codes erkennen. Wenn die Bibliothek Reflection verwendet, prüfen Sie, ob zugehörige Keep-Regeln vorhanden sind. Eine Bibliothek verwendet wahrscheinlich Reflection, wenn sie Folgendes tut:

  • Verwendet Klassen oder Methoden aus den Paketen kotlin.reflect oder java.lang.reflect
  • Verwendet die Funktionen Class.forName oder classLoader.getClass
  • Liest Anmerkungen zur Laufzeit, z. B. wenn ein Anmerkungswert mit val value = myClass.getAnnotation() oder val value = myMethod.getAnnotation() gespeichert und dann mit value etwas unternommen wird.
  • Ruft Methoden mit dem Methodennamen als String auf, z. B.:

    // Calls the private `processData` API with reflection
    myObject.javaClass.getMethod("processData", DataType::class.java)
    ?.invoke(myObject, data)
    

Unerwünschte Aufbewahrungsregeln herausfiltern (erweitert)

Sie sollten Bibliotheken mit Keep-Regeln vermeiden, die Code beibehalten, der eigentlich entfernt werden sollte. Wenn Sie sie jedoch verwenden müssen, können Sie die Regeln herausfiltern, wie im folgenden Code gezeigt:

// If you're using AGP 8.4 and higher
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreFrom("com.somelibrary:somelibrary")
        }
    }
}

// If you're using AGP 7.3-8.3
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreExternalDependencies("com.somelibrary:somelibrary")
        }
    }
}

Fallstudie: Warum Gson bei Optimierungen nicht funktioniert

Gson ist eine Serialisierungsbibliothek, die häufig Probleme bei der App-Optimierung verursacht, da sie stark auf Reflection basiert. Das folgende Code-Snippet zeigt, wie Gson normalerweise verwendet wird. Das kann leicht zu Laufzeitabstürzen führen. Wenn Sie mit Gson eine Liste von User-Objekten abrufen, rufen Sie den Konstruktor nicht auf und übergeben auch keine Factory an die Funktion fromJson(). Wenn Sie App-definierte Klassen ohne eine der folgenden Optionen erstellen oder verwenden, ist das ein Zeichen dafür, dass eine Bibliothek möglicherweise offene Reflektion verwendet:

  • App-Klasse, die eine Bibliothek oder Standardschnittstelle oder -klasse implementiert
  • Plug-in zur Codegenerierung wie KSP
class User(val name: String)
class UserList(val users: List<User>)

// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()

Wenn R8 diesen Code analysiert und UserList oder User nirgends instanziiert sieht, kann es Felder umbenennen oder Konstruktoren entfernen, die scheinbar nicht verwendet werden, was zum Absturz Ihrer App führt. Wenn Sie andere Bibliotheken auf ähnliche Weise verwenden, sollten Sie prüfen, ob sie die App-Optimierung beeinträchtigen. Falls dies der Fall ist, sollten Sie sie nicht verwenden.

Sowohl Room als auch Hilt erstellen anwendungsdefinierte Typen, verwenden aber Codegenerierung, um die Notwendigkeit von Reflection zu vermeiden.