App-Verhalten in der Android-Laufzeit (ART) überprüfen

Die Android-Laufzeit (ART) ist die Standardlaufzeit für Geräte mit Android 5.0 (API-Level 21) und höher. Diese Laufzeit bietet eine Reihe von Funktionen, die die Leistung und Flüssigkeit der Android-Plattform und -Apps verbessern. Weitere Informationen zu den neuen Funktionen von ART finden Sie unter Einführung in ART.

Einige Techniken, die bei Dalvik funktionieren, funktionieren jedoch nicht bei ART. In diesem Dokument erfahren Sie, worauf Sie beim Migrieren einer vorhandenen Anwendung achten sollten, damit sie mit ART kompatibel ist. Die meisten Anwendungen sollten einfach mit ART funktionieren.

Probleme bei der automatischen Speicherbereinigung beheben

Unter Dalvik ist es in Anwendungen häufig hilfreich, System.gc() explizit aufzurufen, um die automatische Speicherbereinigung (GC) anzufordern. Dies sollte bei ART weitaus weniger notwendig sein, insbesondere wenn Sie die automatische Speicherbereinigung aufrufen, um GC_FOR_ALLOC-Vorgänge zu verhindern oder die Fragmentierung zu reduzieren. Wenn Sie prüfen möchten, welche Laufzeit verwendet wird, rufen Sie System.getProperty("java.vm.version") auf. Wenn ART verwendet wird, ist der Wert der Eigenschaft "2.0.0" oder höher.

ART verwendet den Collector Concurrent Copying (CC), der gleichzeitig den Java-Heap verdichtet. Aus diesem Grund sollten Sie Verfahren vermeiden, die mit der Komprimierung der automatischen Speicherbereinigung nicht kompatibel sind (z. B. das Speichern von Verweisen auf Objektinstanzdaten). Dies ist besonders wichtig für Anwendungen, die das Java Native Interface (JNI) verwenden. Weitere Informationen finden Sie unter JNI-Probleme verhindern.

JNI-Probleme vermeiden

Die JNI von ART ist etwas strenger als die von Dalvik. Es empfiehlt sich besonders, den CheckJNI-Modus zu verwenden, um häufige Probleme zu erkennen. Wenn Ihre App C-/C++-Code verwendet, lesen Sie den folgenden Artikel:

Fehlerbehebung in Android JNI mit CheckJNI

JNI-Code wird auf Probleme bei der automatischen Speicherbereinigung geprüft

Der Collector „Gleichzeitiges Kopieren“ (CC) kann Objekte zur Verdichtung im Arbeitsspeicher verschieben. Führen Sie mit C-/C++-Code keine Vorgänge aus, die mit der Komprimierung von GC nicht kompatibel sind. Wir haben CheckJNI verbessert, um einige potenzielle Probleme zu erkennen (wie unter Änderungen bei JNI Local Reference in ICS beschrieben).

Ein besonders wichtiger Bereich ist die Verwendung der Funktionen Get...ArrayElements() und Release...ArrayElements(). Bei Laufzeiten mit nicht komprimierter Speicherbereinigung geben die Get...ArrayElements()-Funktionen in der Regel einen Verweis auf den tatsächlichen Arbeitsspeicher zurück, der das Array-Objekt unterstützt. Wenn Sie eine Änderung an einem der zurückgegebenen Arrayelemente vornehmen, wird das Array-Objekt selbst geändert. Die Argumente für Release...ArrayElements() werden normalerweise ignoriert. Wenn jedoch die Komprimierung von Speicherbereinigungsvorgängen verwendet wird, können die Get...ArrayElements()-Funktionen eine Kopie des Arbeitsspeichers zurückgeben. Wenn Sie die Referenz bei der Komprimierung von GC missbrauchen, kann dies zu Speicherschäden oder anderen Problemen führen. Beispiele:

  • Wenn Sie Änderungen an den zurückgegebenen Arrayelementen vornehmen, müssen Sie anschließend die entsprechende Release...ArrayElements()-Funktion aufrufen, damit die vorgenommenen Änderungen korrekt zurück in das zugrunde liegende Arrayobjekt kopiert werden.
  • Wenn Sie die Elemente des Speicherarrays freigeben, müssen Sie abhängig von den vorgenommenen Änderungen den entsprechenden Modus verwenden:
    • Wenn Sie keine Änderungen an den Arrayelementen vorgenommen haben, verwenden Sie den Modus JNI_ABORT. Dadurch wird der Arbeitsspeicher freigegeben, ohne Änderungen in das zugrunde liegende Arrayobjekt zu kopieren.
    • Wenn Sie Änderungen am Array vorgenommen haben und den Verweis nicht mehr benötigen, verwenden Sie den Code 0. Dadurch wird das Array-Objekt aktualisiert und die Kopie des Arbeitsspeichers freigegeben.
    • Wenn Sie Änderungen am Array vorgenommen haben, für das Sie einen Commit durchführen möchten, und Sie die Kopie des Arrays behalten möchten, verwenden Sie JNI_COMMIT. Dadurch wird das zugrunde liegende Arrayobjekt aktualisiert und die Kopie beibehalten.
  • Wenn Sie Release...ArrayElements() aufrufen, wird der gleiche Pointer zurückgegeben, der ursprünglich von Get...ArrayElements() zurückgegeben wurde. Es ist beispielsweise nicht sicher, den ursprünglichen Zeiger zu erhöhen (um die zurückgegebenen Arrayelemente zu durchsuchen) und dann den inkrementierten Zeiger an Release...ArrayElements() zu übergeben. Das Übergeben dieses geänderten Zeigers kann dazu führen, dass der falsche Arbeitsspeicher freigegeben wird, was zu Speicherschäden führen kann.

Fehlerbehandlung

Die JNI von ART gibt Fehler in einer Reihe von Fällen aus, in denen Dalvik dies nicht tut. (Noch einmal) können Sie viele solcher Fälle durch Tests mit CheckJNI erkennen.

Wenn beispielsweise RegisterNatives mit einer Methode aufgerufen wird, die nicht existiert (möglicherweise weil die Methode von einem Tool wie ProGuard entfernt wurde), löst ART jetzt NoSuchMethodError ordnungsgemäß aus:

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

ART protokolliert auch einen Fehler (sichtbar in Logcat), wenn RegisterNatives ohne Methoden aufgerufen wird:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

Außerdem geben die JNI-Funktionen GetFieldID() und GetStaticFieldID() jetzt korrekt NoSuchFieldError aus, anstatt einfach null zurückzugeben. In ähnlicher Weise geben GetMethodID() und GetStaticMethodID() jetzt korrekt NoSuchMethodError aus. Dies kann aufgrund der unbehandelten Ausnahmen oder der Ausnahmen, die an Java-Aufrufer von nativem Code ausgegeben werden, zu CheckJNI-Fehlern führen. Daher ist es besonders wichtig, ART-kompatible Anwendungen mit dem CheckJNI-Modus zu testen.

ART erwartet, dass Nutzer der JNI-CallNonvirtual...Method()-Methoden (z. B. CallNonvirtualVoidMethod()) gemäß der JNI-Spezifikation die Deklarationsklasse der Methode und keine Unterklasse verwenden.

Probleme mit der Stackgröße vermeiden

Dalvik hatte separate Stacks für nativen und Java-Code mit einer standardmäßigen Java-Stackgröße von 32 KB und einer Standardgröße für native Stacks von 1 MB. ART hat einen einheitlichen Stack für bessere Lokalität. Normalerweise sollte der ART-Stack Thread etwa die gleiche Größe wie Dalvik haben. Wenn Sie jedoch explizit Stackgrößen festlegen, müssen Sie diese Werte für Anwendungen, die in ART ausgeführt werden, möglicherweise noch einmal überprüfen.

  • Sehen Sie sich in Java die Aufrufe des Konstruktors Thread an, die eine explizite Stapelgröße angeben. Sie müssen sie beispielsweise erhöhen, wenn StackOverflowError auftritt.
  • Sehen Sie sich in C/C++ die Verwendung von pthread_attr_setstack() und pthread_attr_setstacksize() für Threads an, die auch Java-Code über JNI ausführen. Hier ein Beispiel für den Fehler, der protokolliert wird, wenn eine Anwendung versucht, die JNI AttachCurrentThread() aufzurufen, wenn die pthread-Größe zu klein ist:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Objektmodelländerungen

Dalvik hat Unterklassen fälschlicherweise erlaubt, Paket-private Methoden zu überschreiben. ART gibt in folgenden Fällen eine Warnung aus:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

Wenn Sie die Methode einer Klasse in einem anderen Paket überschreiben möchten, deklarieren Sie die Methode als public oder protected.

Object hat jetzt private Felder. Achten Sie bei Anwendungen, die Felder in ihren Klassenhierarchien berücksichtigen, darauf, nicht die Felder von Object zu untersuchen. Wenn Sie z. B. eine Klassenhierarchie im Rahmen eines Serialisierungs-Frameworks iterieren,

Class.getSuperclass() == java.lang.Object.class

anstatt fortzufahren, bis die Methode null zurückgibt.

Der Proxy InvocationHandler.invoke() empfängt jetzt null, wenn keine Argumente anstelle eines leeren Arrays vorhanden sind. Dieses Verhalten war zuvor dokumentiert, wurde in Dalvik aber nicht korrekt gehandhabt. Bei früheren Versionen von Mockito treten hierbei Probleme auf. Verwenden Sie daher beim Testen mit ART eine aktualisierte Mockito-Version.

Probleme bei der AOT-Kompilierung beheben

Die AOT-Java-Kompilierung (Ahead-Of-Time) von ART sollte für jeglichen Standard-Java-Code funktionieren. Die Kompilierung wird mit dem dex2oat-Tool von ART durchgeführt. Wenn bei der Installation Probleme mit dex2oat auftreten, lass es uns wissen (siehe Probleme melden), damit wir sie so schnell wie möglich beheben können. Beachten Sie folgende Punkte:

  • ART führt bei der Installation eine strengere Bytecode-Überprüfung durch als Dalvik. Mit den Android-Build-Tools erstellter Code sollte in Ordnung sein. Einige Nachbearbeitungstools (insbesondere Tools, die Verschleierung ausführen) können jedoch ungültige Dateien erzeugen, die von Dalvik toleriert, aber von ART abgelehnt werden. Wir arbeiten mit Toolanbietern zusammen, um solche Probleme zu finden und zu beheben. In vielen Fällen können diese Probleme durch das Herunterladen der neuesten Versionen der Tools und das Neugenerieren der DEX-Dateien behoben werden.
  • Einige typische Probleme, die von der ART-Überprüfung gemeldet werden, sind:
    • ungültiger Kontrollfluss
    • monitorenter/monitorexit unausgeglichen
    • Listengröße des Parametertyps mit 0 Länge
  • Einige Anwendungen haben Abhängigkeiten vom installierten Dateiformat .odex in /system/framework, /data/dalvik-cache oder im optimierten Ausgabeverzeichnis von DexClassLoader. Diese Dateien sind jetzt ELF-Dateien und keine erweiterte Form von DEX-Dateien. ART versucht zwar, denselben Namens- und Sperrregeln wie Dalvik zu folgen, Apps sollten jedoch nicht vom Dateiformat abhängig sein; das Format kann ohne Vorankündigung geändert werden.

    Hinweis: In Android 8.0 (API-Level 26) und höher wurde das optimierte Ausgabeverzeichnis DexClassLoader eingestellt. Weitere Informationen finden Sie in der Dokumentation zum Konstruktor DexClassLoader().

Probleme mit der Berichterstellung

Wenn Probleme auftreten, die nicht auf JNI-Probleme in der App zurückzuführen sind, melden Sie sie über den Issue Tracker für das Android Open Source Project unter https://code.google.com/p/android/issues/list. Füge ein "adb bugreport" und einen Link zur App im Google Play Store hinzu, sofern verfügbar. Hängen Sie andernfalls, falls möglich, ein APK an, das das Problem reproduziert. Beachten Sie, dass Probleme (einschließlich Anhängen) öffentlich sichtbar sind.