Vérifier le comportement de l'application dans l'environnement d'exécution Android (ART)

Android Runtime (ART) est l'environnement d'exécution par défaut pour les appareils équipés d'Android 5.0 (niveau d'API 21) ou version ultérieure. Cet environnement d'exécution offre un certain nombre de fonctionnalités qui améliorent les performances et la fluidité de la plate-forme et des applications Android. Vous trouverez plus d'informations sur les nouvelles fonctionnalités d'ART dans la section Présentation d'ART.

Cependant, certaines techniques qui fonctionnent sur Dalvik ne fonctionnent pas sur ART. Ce document vous indique les éléments à surveiller lors de la migration d'une application existante pour qu'elle soit compatible avec ART. La plupart des applications devraient simplement fonctionner lorsqu'elles sont exécutées avec ART.

Résoudre les problèmes de récupération de mémoire

Sous Dalvik, les applications trouvent souvent utile d'appeler explicitement System.gc() pour déclencher la récupération de mémoire. Cela devrait être beaucoup moins nécessaire avec ART, en particulier si vous appelez la récupération de mémoire pour éviter les occurrences de type GC_FOR_ALLOC ou réduire la fragmentation. Vous pouvez vérifier quel environnement d'exécution est utilisé en appelant System.getProperty("java.vm.version"). Si ART est utilisé, la valeur de la propriété est "2.0.0" ou supérieure.

ART utilise un collecteur de copie simultanée (CC) qui compacte simultanément le tas de mémoire Java. Pour cette raison, évitez d'utiliser des techniques incompatibles avec le compactage de récupération de mémoire (telles que l'enregistrement de pointeurs vers des données d'instance d'objet). Cela est particulièrement important pour les applications qui utilisent l'interface JNI (Java Native Interface). Pour en savoir plus, consultez la page Prévenir les problèmes JNI.

Prévention des problèmes JNI

Le JNI d'ART est un peu plus strict que celui de Dalvik. L'utilisation du mode CheckJNI est particulièrement utile pour détecter les problèmes courants. Si votre application utilise du code C/C++, vous devez consulter l'article suivant:

Déboguer Android JNI avec CheckJNI

Vérifier le code JNI pour détecter des problèmes de récupération de mémoire

Le collecteur de copie simultanée (CC) peut déplacer des objets en mémoire pour les compacter. Si vous utilisez du code C/C++, n'effectuez pas d'opérations incompatibles avec le compactage de récupération de mémoire. Nous avons amélioré CheckJNI pour identifier certains problèmes potentiels (comme décrit dans la section Modifications apportées aux références locales JNI dans ICS).

Un aspect à surveiller en particulier est l'utilisation des fonctions Get...ArrayElements() et Release...ArrayElements(). Dans les environnements d'exécution avec récupération de mémoire non compacte, les fonctions Get...ArrayElements() renvoient généralement une référence à la mémoire réelle sur laquelle repose l'objet tableau. Si vous modifiez l'un des éléments de tableau renvoyés, l'objet de tableau est lui-même modifié (et les arguments de Release...ArrayElements() sont généralement ignorés). Toutefois, si une récupération de mémoire compacte est utilisée, les fonctions Get...ArrayElements() peuvent renvoyer une copie de la mémoire. Si vous faites un usage abusif de la référence lors du compactage de récupération de mémoire, cela peut entraîner une corruption de la mémoire ou d'autres problèmes. Par exemple :

  • Si vous apportez des modifications aux éléments de tableau renvoyés, vous devez ensuite appeler la fonction Release...ArrayElements() appropriée, pour vous assurer que les modifications apportées sont correctement copiées dans l'objet de tableau sous-jacent.
  • Lorsque vous libérez les éléments du tableau de mémoire, vous devez utiliser le mode approprié, en fonction des modifications que vous avez apportées :
    • Si vous n'avez apporté aucune modification aux éléments du tableau, utilisez le mode JNI_ABORT, qui libère de la mémoire sans copier les modifications apportées à l'objet de tableau sous-jacent.
    • Si vous avez modifié le tableau et que vous n'avez plus besoin de la référence, utilisez le code 0 (qui met à jour l'objet tableau et libère la copie de la mémoire).
    • Si vous avez apporté des modifications au tableau pour lequel vous souhaitez effectuer un commit et que vous souhaitez conserver sa copie, utilisez JNI_COMMIT (qui met à jour l'objet tableau sous-jacent et conserve la copie).
  • Lorsque vous appelez Release...ArrayElements(), renvoyez le même pointeur que celui renvoyé par Get...ArrayElements(). Par exemple, il n'est pas prudent d'incrémenter le pointeur d'origine (pour parcourir les éléments du tableau renvoyés), puis de le transmettre à Release...ArrayElements(). La transmission de ce pointeur modifié peut entraîner la libération de la mauvaise mémoire, ce qui entraîne une corruption de la mémoire.

Gestion des exceptions

Le JNI d'ART génère des erreurs dans un certain nombre de cas, alors que Dalvik n'en génère pas. (Une fois encore, vous pouvez détecter de nombreux cas de ce type en effectuant un test avec CheckJNI.)

Par exemple, si RegisterNatives est appelé avec une méthode qui n'existe pas (peut-être parce que la méthode a été supprimée par un outil tel que ProGuard), ART génère désormais correctement NoSuchMethodError:

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 consigne également une erreur (visible dans logcat) si RegisterNatives est appelé sans méthode:

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

De plus, les fonctions JNI GetFieldID() et GetStaticFieldID() génèrent désormais correctement NoSuchFieldError au lieu de simplement renvoyer une valeur nulle. De même, GetMethodID() et GetStaticMethodID() génèrent désormais correctement NoSuchMethodError. Cela peut entraîner des échecs CheckJNI en raison des exceptions non gérées ou des exceptions générées pour les appelants Java de code natif. Il est donc particulièrement important de tester les applications compatibles avec ART avec le mode CheckJNI.

ART s'attend à ce que les utilisateurs des méthodes CallNonvirtual...Method() JNI (telles que CallNonvirtualVoidMethod()) utilisent la classe déclarante de la méthode, et non une sous-classe, comme l'exige la spécification JNI.

Prévenir les problèmes de taille de pile

Dalvik disposait de piles distinctes pour le code natif et Java, avec une taille de pile Java par défaut de 32 Ko et une taille de pile native par défaut de 1 Mo. ART dispose d'une pile unifiée pour une meilleure localité. Normalement, la taille de la pile ART Thread doit être approximativement la même que pour Dalvik. Toutefois, si vous définissez explicitement les tailles de pile, vous devrez peut-être revoir ces valeurs pour les applications exécutées dans ART.

  • En Java, examinez les appels au constructeur Thread qui spécifient une taille de pile explicite. Par exemple, vous devrez augmenter la taille si StackOverflowError se produit.
  • En C/C++, vérifiez l'utilisation de pthread_attr_setstack() et pthread_attr_setstacksize() pour les threads qui exécutent également du code Java via JNI. Voici un exemple de l'erreur consignée lorsqu'une application tente d'appeler JNI AttachCurrentThread() lorsque la taille du pthread est trop petite :
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Modifications apportées au modèle d'objet

Dalvik a autorisé à tort les sous-classes à remplacer les méthodes privées de package. ART émet un avertissement dans les cas suivants:

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

Si vous avez l'intention de remplacer la méthode d'une classe dans un autre package, déclarez-la comme public ou protected.

Object comporte désormais des champs privés. Les applications qui réfléchissent sur des champs de leurs hiérarchies de classes ne doivent pas tenter d'examiner les champs de Object. Par exemple, si vous itérez une hiérarchie de classes dans le cadre d'un framework de sérialisation,

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

au lieu de continuer jusqu'à ce que la méthode renvoie null.

Le proxy InvocationHandler.invoke() reçoit désormais null s'il n'y a pas d'arguments au lieu d'un tableau vide. Ce comportement a été documenté précédemment, mais n'a pas été correctement géré dans Dalvik. Les versions précédentes de Mockito présentent des difficultés. Vous devez donc utiliser une version mise à jour de Mockito lorsque vous effectuez des tests avec ART.

Résoudre les problèmes de compilation anticipée

La compilation Java AOT (Ahead Of Time) d'ART devrait fonctionner pour tout le code Java standard. La compilation est effectuée par l'outil dex2oat d'ART. Si vous rencontrez des problèmes liés à dex2oat au moment de l'installation, veuillez nous en informer (voir la section Signaler des problèmes) afin que nous puissions les résoudre le plus rapidement possible. Quelques points à noter:

  • ART effectue une vérification du bytecode plus stricte au moment de l'installation que Dalvik. Le code produit par les outils de compilation Android devrait être acceptable. Toutefois, certains outils de post-traitement (en particulier ceux qui effectuent l'obscurcissement) peuvent produire des fichiers non valides tolérés par Dalvik, mais rejetés par ART. Nous collaborons avec les fournisseurs d'outils pour identifier et résoudre ces problèmes. Dans de nombreux cas, obtenir les dernières versions de vos outils et regénérer les fichiers DEX permet de résoudre ces problèmes.
  • Voici quelques problèmes courants signalés par l'outil de vérification ART :
    • flux de contrôle non valide
    • monitorenter/monitorexit déséquilibré
    • Taille de la liste des types de paramètres de longueur 0
  • Certaines applications dépendent du format de fichier .odex installé dans /system/framework, /data/dalvik-cache ou dans le répertoire de sortie optimisé de DexClassLoader. Il s'agit désormais de fichiers ELF, et non d'une forme étendue de fichiers DEX. Bien qu'ART tente de suivre les mêmes règles de dénomination et de verrouillage que Dalvik, les applications ne doivent pas dépendre du format de fichier. Ce format est susceptible d'être modifié sans préavis.

    Remarque:Sur Android 8.0 (niveau d'API 26) ou version ultérieure, le répertoire de sortie optimisé DexClassLoader est obsolète. Pour en savoir plus, consultez la documentation du constructeur DexClassLoader().

Problèmes concernant les rapports

Si vous rencontrez des problèmes qui ne sont pas dus à des problèmes JNI de l'application, signalez-les via l'outil Android Open Source Project Issue Tracker à l'adresse https://code.google.com/p/android/issues/list. Incluez un "adb bugreport" et un lien vers l'application sur le Google Play Store, le cas échéant. Sinon, si possible, joignez un APK reproduisant le problème. Notez que les problèmes (y compris les pièces jointes) sont visibles publiquement.