Verifica del comportamento delle app sul runtime Android (ART)

Android Runtime (ART) è il runtime predefinito per i dispositivi con Android 5.0 (livello API 21) e versioni successive. Questo runtime offre una serie di funzionalità che migliorano le prestazioni e la fluidità della piattaforma e delle app Android. Puoi trovare ulteriori informazioni sulle nuove funzionalità di ART nella sezione Introduzione ad ART.

Tuttavia, alcune tecniche che funzionano su Dalvik non funzionano con l'ART. Questo documento illustra gli aspetti da considerare quando si esegue la migrazione di un'app esistente in modo che sia compatibile con ART. La maggior parte delle app dovrebbe funzionare solo con ART.

Risolvere i problemi di garbage collection (GC)

In Dalvik, le app spesso trovano utile chiamare esplicitamente System.gc() per richiedere la garbage collection (GC). Questa operazione dovrebbe essere molto meno necessaria con ART, in particolare se stai richiamando la garbage collection per prevenire occorrenze di tipo GC_FOR_ALLOC o per ridurre la frammentazione. Puoi verificare quale runtime è in uso chiamando System.getProperty("java.vm.version"). Se ART è in uso, il valore della proprietà è pari o superiore a "2.0.0".

ART utilizza il raccoglitore CC (Concurrent Copia) che compatta contemporaneamente l'heap Java. Per questo motivo, evita di utilizzare tecniche incompatibili con la compattazione di GC (come il salvataggio dei puntatori ai dati delle istanze di oggetti). Ciò è particolarmente importante per le app che utilizzano Java Native Interface (JNI). Per ulteriori informazioni, consulta la sezione Prevenzione dei problemi JNI.

Prevenzione dei problemi JNI

JNI di ART è un po' più rigoroso di quello di Dalvik. È consigliabile usare la modalità CheckJNI per individuare i problemi comuni. Se la tua app utilizza codice C/C++, consulta il seguente articolo:

Debug di Android JNI con CheckJNI

Controllo del codice JNI per problemi di garbage collection in corso...

Il raccoglitore Copia simultanea (CC) potrebbe spostare gli oggetti in memoria per la compattazione. Se utilizzi il codice C/C++, non eseguire operazioni incompatibili con la compattazione di GC. Abbiamo migliorato CheckJNI per identificare alcuni potenziali problemi (come descritto in Modifiche ai riferimenti locali di JNI in ICS).

Un aspetto da tenere d'occhio in particolare è l'uso delle funzioni Get...ArrayElements() e Release...ArrayElements(). Nei runtime con GC non compatta, le funzioni Get...ArrayElements() in genere restituiscono un riferimento alla memoria effettiva che supporta l'oggetto array. Se apporti una modifica a uno degli elementi array restituiti, l'oggetto array viene modificato (e gli argomenti per Release...ArrayElements() vengono generalmente ignorati). Tuttavia, se il GC compatta è in uso, le funzioni Get...ArrayElements() possono restituire una copia della memoria. L'uso improprio del riferimento durante la compattazione di GC potrebbe danneggiare la memoria o causare altri problemi. Ecco alcuni esempi:

  • Se apporti modifiche agli elementi dell'array restituiti, devi chiamare la funzione Release...ArrayElements() appropriata al termine dell'operazione per assicurarti che le modifiche apportate vengano copiate correttamente nell'oggetto array sottostante.
  • Quando rilasci gli elementi dell'array di memoria, devi utilizzare la modalità appropriata, a seconda delle modifiche apportate:
    • Se non hai apportato modifiche agli elementi array, utilizza la modalità JNI_ABORT, che rilascia la memoria senza copiare le modifiche nell'oggetto array sottostante.
    • Se hai apportato modifiche all'array e non hai più bisogno del riferimento, utilizza il codice 0 (che aggiorna l'oggetto array e libera la copia di memoria).
    • Se hai apportato modifiche all'array di cui vuoi eseguire il commit e vuoi conservare la copia dell'array, utilizza JNI_COMMIT (che aggiorna l'oggetto array sottostante e conserva la copia).
  • Quando chiami Release...ArrayElements(), restituisci lo stesso puntatore originariamente restituito da Get...ArrayElements(). Ad esempio, non è sicuro incrementare il puntatore originale (per analizzare gli elementi dell'array restituiti) e poi passare il puntatore incrementato a Release...ArrayElements(). Se passi questo puntatore modificato, puoi liberare la memoria errata, danneggiando la memoria.

Gestione degli errori

La JNI di ART genera errori in una serie di casi in cui Dalvik non lo fa. Anche in questo caso, puoi rilevare molti di questi casi eseguendo un test con CheckJNI.

Ad esempio, se RegisterNatives viene chiamato con un metodo che non esiste (forse perché il metodo è stato rimosso da uno strumento come ProGuard), ART ora genera correttamente 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 registra anche un errore (visibile in logcat) se RegisterNatives viene chiamato senza metodi:

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

Inoltre, le funzioni JNI GetFieldID() e GetStaticFieldID() ora generano correttamente NoSuchFieldError anziché restituire semplicemente null. Allo stesso modo, GetMethodID() e GetStaticMethodID() ora restituiscono correttamente NoSuchMethodError. Ciò può portare a errori CheckJNI a causa delle eccezioni non gestite o delle eccezioni che vengono generate per i chiamanti Java del codice nativo. Ciò rende particolarmente importante testare app compatibili con ART con la modalità CheckJNI.

ART si aspetta che gli utenti dei metodi CallNonvirtual...Method() JNI (come CallNonvirtualVoidMethod()) utilizzino la classe dichiarante del metodo, non una sottoclasse, come richiesto dalla specifica JNI.

Evitare i problemi relativi alle dimensioni dello stack

Dalvik aveva stack separati per codice nativo e Java, con una dimensione dello stack Java predefinita di 32 kB e una dimensione dello stack nativa predefinita di 1 MB. ART ha uno stack unificato per una migliore località. In genere, le dimensioni dello stack Thread ART devono essere all'incirca le stesse di Dalvik. Tuttavia, se imposti esplicitamente le dimensioni dello stack, potresti dover rivedere questi valori per le app in esecuzione in ART.

  • In Java, esamina le chiamate al costruttore Thread che specificano una dimensione esplicita dello stack. Ad esempio, dovrai aumentare le dimensioni se si verifica StackOverflowError.
  • In C/C++, esamina l'utilizzo di pthread_attr_setstack() e pthread_attr_setstacksize() per i thread che eseguono anche codice Java tramite JNI. Ecco un esempio dell'errore registrato quando un'app tenta di chiamare JNI AttachCurrentThread() quando la dimensione del file pthread è troppo piccola:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Modifiche al modello a oggetti

Dalvik ha erroneamente consentito alle sottoclassi di eseguire l'override dei metodi pacchetto-private. ART emette un avviso nei seguenti casi:

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

Se intendi eseguire l'override del metodo di una classe in un altro pacchetto, dichiara il metodo come public o protected.

Object ora dispone di campi privati. Le app che riflettono sui campi nelle loro gerarchie di classi dovrebbero fare attenzione a non tentare di esaminare i campi di Object. Ad esempio, se stai ripetendo una gerarchia di classi in un framework di serializzazione,

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

anziché continuare finché il metodo non restituisce null.

Il proxy InvocationHandler.invoke() ora riceve null se non sono presenti argomenti anziché un array vuoto. Questo comportamento era documentato in precedenza, ma non era gestito correttamente in Dalvik. Le versioni precedenti di Mockito riscontrano difficoltà, pertanto utilizza una versione aggiornata di Mockito durante i test con ART.

Risolvere i problemi di compilazione AOT

La compilazione Java Ahead-Of-Time (AOT) di ART dovrebbe funzionare per tutto il codice Java standard. La compilazione viene eseguita dallo strumento dex2oat di ART; se riscontri problemi relativi a dex2oat al momento dell'installazione, comunicacelo (consulta la sezione Segnalazione dei problemi) per consentirci di risolverli il più rapidamente possibile. Un paio di problemi da notare:

  • ART esegue una verifica con bytecode più rigorosa al momento dell'installazione rispetto a Dalvik. Il codice prodotto dagli strumenti di creazione di Android dovrebbe essere valido. Tuttavia, alcuni strumenti di post-elaborazione (in particolare quelli che eseguono l'offuscamento) potrebbero produrre file non validi tollerati da Dalvik, ma rifiutati da ART. Stiamo collaborando con i fornitori di strumenti per individuare e risolvere questi problemi. In molti casi, ottenere le versioni più recenti degli strumenti e rigenerare i file DEX può risolvere questi problemi.
  • Di seguito sono riportati alcuni problemi tipici segnalati dallo strumento di verifica ART:
    • flusso di controllo non valido
    • monitorenter/monitorexit sbilanciati
    • Dimensione elenco dei tipi di parametri di lunghezza 0
  • Alcune app hanno dipendenze per il formato file .odex installato in /system/framework, /data/dalvik-cache o nella directory di output ottimizzata di DexClassLoader. Questi file ora sono file ELF e non una forma estesa di file DEX. Anche se ART cerca di seguire le stesse regole di denominazione e blocco di Dalvik, le app non devono dipendere dal formato file; il formato è soggetto a modifiche senza preavviso.

    Nota: in Android 8.0 (livello API 26) e versioni successive, la directory di output ottimizzato DexClassLoader è stata deprecata. Per maggiori informazioni, consulta la documentazione relativa al costruttore DexClassLoader().

Problemi relativi ai report

Se riscontri problemi non dovuti a JNI dell'app, segnalali tramite il tracker dei problemi del progetto open source Android all'indirizzo https://code.google.com/p/android/issue/list. Includi un "adb bugreport" e un link all'app nel Google Play Store, se disponibile. Altrimenti, se possibile, allega un APK che riproduca il problema. Tieni presente che i problemi (inclusi gli allegati) sono visibili pubblicamente.