Verificação do comportamento do aplicativo no Android Runtime (ART)

O Android Runtime (ART) é o tempo de execução padrão para dispositivos que executam o Android 5.0 (API de nível 21) e versões superiores. Esse tempo de execução oferece diversos recursos que melhoram o desempenho e a estabilidade da plataforma e dos apps Android. Saiba mais sobre os novos recursos do ART em Introdução ao ART.

No entanto, algumas técnicas que funcionam na Dalvik não funcionam no ART. Este documento apresenta considerações necessárias ao migrar um aplicativo existente para que ele seja compatível com o ART. A maioria dos aplicativos deve funcionar com o ART.

Solução de problemas de coleta de lixo (GC)

Na Dalvik, é conveniente que os aplicativos chamem System.gc() explicitamente para solicitar a coleta de lixo (GC). Isso deve ser menos necessário com o ART, especialmente se você estiver invocando a coleta de lixo para evitar ocorrências tipo GC_FOR_ALLOC ou para reduzir a fragmentação. Você pode verificar qual tempo de execução está em uso chamando System.getProperty("java.vm.version"). Se o ART estiver em uso, o valor da property será "2.0.0" ou mais.

Além disso, um coletor de lixo com compactação está sendo desenvolvido no Android Open-Source Project (AOSP) para melhorar o gerenciamento de memória. Por esse motivo, evite o uso de técnicas incompatíveis com a GC com compactação (como salvar ponteiros em dados de instância de objeto). Isso é especialmente importante para aplicativos que usam a Java Native Interface (JNI). Para saber mais, consulte Prevenção de problemas de JNI.

Prevenção de problemas de JNI

A JNI do ART é ligeiramente mais rígida do que a da Dalvik. É altamente recomendável usar o modo CheckJNI para identificar problemas comuns. Se seu aplicativo usar código C/C++, consulte o seguinte artigo:

Depuração do Android JNI com o CheckJNI

Verificação do código JNI para problemas de coleta de lixo

O ART tem um coletor de lixo com compactação em desenvolvimento no Android Open Source Project (AOSP). Quando esse coletor estiver em uso, os objetos poderão ser movidos na memória. Se usar código C/C++, não execute operações incompatíveis com a GC com compactação. Nós temos um CheckJNI aprimorado para identificar possíveis problemas (conforme é descrito em Mudanças na referência local da JNI em ICS).

Uma área que deve ser um foco de atenção é o uso das funções Get...ArrayElements() e Release...ArrayElements(). Em tempos de execução com GC sem compactação, as funções Get...ArrayElements() normalmente retornam uma referência à memória real compatível com o objeto de matriz. Se você fizer uma alteração em um dos elementos de matriz retornados, o objeto de matriz em si se alterará (e os argumentos para Release...ArrayElements() geralmente são ignorados). No entanto, se a GC com compactação estiver em uso, as funções Get...ArrayElements() poderão retornar uma cópia da memória. Se você utilizar incorretamente a referência enquanto a GC com compactação estiver em uso, isso pode fazer com que a memória seja corrompida ou causar outros problemas. Por exemplo:

  • Se você fizer alterações nos elementos de matriz retornados, chame a função Release...ArrayElements() apropriada quando terminar para garantir que as alterações feitas sejam copiadas corretamente para o objeto de matriz subjacente.
  • Quando liberar os elementos de matriz de memória, você deve usar o modo apropriado dependendo das alterações feitas:
    • Se você não tiver feito alterações nos elementos de matriz, use o modo JNI_ABORT, que libera a memória sem copiar as alterações para o objeto de matriz subjacente.
    • Se tiver feito alterações na matriz e não precisar mais da referência, use o código 0 (que atualiza o objeto de matriz e libera a cópia da memória).
    • Se tiver feito alterações na matriz que queira salvar permanentemente e quiser manter a cópia da matriz, use JNI_COMMIT (que atualiza o objeto de matriz subjacente e mantém a cópia).
  • Ao chamar Release...ArrayElements(), retorne o mesmo ponteiro que foi originalmente retornado por Get...ArrayElements(). Por exemplo, se não for seguro incrementar o ponteiro original (para verificar os elementos de matriz retornados), passe o ponteiro incrementado para Release...ArrayElements(). Passar esse ponteiro modificado pode fazer com que a memória incorreta seja liberada, resultando no corrupção da memória.

Tratamento de erros

A JNI do ART gera erros em diversos casos nos quais a Dalvik não o faz. Novamente, você pode identificar muitos desses casos ao realizar testes com o CheckJNI.

Por exemplo, se RegisterNatives for chamado com um método que não exista (talvez por ter sido removido por uma ferramenta como o ProGuard), o ART agora gera 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)

O ART também registra um erro (visível no logcat) se RegisterNatives for chamado sem métodos:

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

Além disso, as funções de JNI GetFieldID() e GetStaticFieldID() agora geram NoSuchFieldError em vez de simplesmente retornarem nulas. Da mesma forma, GetMethodID() e GetStaticMethodID() agora acionam NoSuchMethodError adequadamente. Isso pode levar a falhas CheckJNI devido às exceções não gerenciadas ou às exceções sendo lançadas para os autores da chamada Java do código nativo. Isso torna especialmente importante o teste de aplicativos compatíveis com o ART com o modo CheckJNI.

O ART espera que os usuários dos métodos CallNonvirtual...Method() da JNI (como CallNonvirtualVoidMethod()) usem a classe de declaração do método, não uma subclasse, conforme é exigido pela especificação da JNI.

Prevenção de problemas de tamanho da pilha

A Dalvik tinha pilhas separadas para códigos Java e nativos, com um tamanho de pilha Java padrão de 32 kB e um tamanho de pilha nativa padrão de 1 MB. O ART tem uma pilha unificada para proporcionar uma localidade melhor. Normalmente, o tamanho da pilha Thread do ART deve ser aproximadamente igual ao da Dalvik. No entanto, se você definir tamanhos de pilha explicitamente, pode ser necessário modificar esses valores para aplicativos executados no ART.

  • No Java, verifique as chamadas para o construtor Thread que especificam um tamanho de pilha explícito. Por exemplo, você precisará aumentar o tamanho se StackOverflowError ocorrer.
  • Em C/C++, verifique o uso de pthread_attr_setstack() e pthread_attr_setstacksize() para threads que também executam código Java via JNI. Veja um exemplo do erro registrado quando um aplicativo tenta chamar AttachCurrentThread() da JNI quando o tamanho do thread é pequeno demais:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Mudanças no modelo de objeto

A Dalvik permitiu incorretamente que a subclasse modificasse os métodos privados do pacote. O ART enviará um aviso nestes casos:

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

Se você pretende modificar o método de uma classe em um pacote diferente, declare esse método como public ou protected.

Object agora tem campos privados. Os aplicativos que refletem em campos em suas hierarquias de classes devem evitar tentativas de examinar os campos de Object. Por exemplo, se você estiver iterando uma hierarquia de classes como parte de uma biblioteca de serialização, pare quando

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

em vez de continuar até o método retornar null.

O proxy InvocationHandler.invoke() agora recebe null se não houver argumentos em vez de uma matriz vazia. Esse comportamento foi documentado anteriormente, mas não foi corretamente gerenciado na Dalvik. Versões anteriores do Mockito têm dificuldades com isso, portanto, use uma versão atualizada do Mockito ao realizar testes com o ART.

Correção de problemas de compilação de AOT

A compilação Java Ahead-Of-Time (AOT) do ART deve funcionar com todos os códigos Java padrão. A compilação é executada pela ferramenta dex2oat do ART. Se tiver problemas relacionados ao dex2oat no momento da instalação, entre em contato (consulte Relatar problemas) para que os corrijamos assim que possível. Algumas observações necessárias:

  • O ART realiza uma verificação de bytecode mais rígida no momento da instalação do que a Dalvik. O código produzido por ferramentas de compilação Android deve funcionar normalmente. Entretanto, algumas ferramentas de pós-processamento (especialmente ferramentas que realizam ofuscação) podem produzir arquivos inválidos que são tolerados pela Dalvik, mas rejeitados pelo ART. Nós temos trabalhado com fornecedores de ferramentas para descobrir e corrigir esses problemas. Em muitos casos, ter as versões mais recentes das suas ferramentas e regenerar os arquivos DEX pode corrigir os problemas.
  • Alguns problemas típicos que são sinalizados pelo verificador do ART:
    • controle de fluxo inválido
    • moniterenter/moniterexit não balanceado
    • tamanho de lista de tipo de parâmetro 0
  • Alguns aplicativos têm dependências no formato do arquivo .odex instalado em /system/framework, /data/dalvik-cache ou no diretório de saída otimizado do DexClassLoader. Esses arquivos agora são arquivos ELF e não uma forma estendida dos arquivos DEX. Embora o ART tente seguir as mesmas regras de nomenclatura e bloqueio que a Dalvik, os aplicativos não devem depender do formato de arquivo, já que ele está sujeito a mudança sem aviso prévio.

    Observação: no Android 8.0 (API de nível 26) ou superior, o diretório de saída otimizado DexClassLoader está obsoleto. Para ver mais informações, consulte a documentação da classe do construtor DexClassLoader().

Relatar problemas

Em caso de problemas não causados pela JNI do aplicativo, relate-os pelo Android Open Source Project Issue Tracker em https://code.google.com/p/android/issues/list. Inclua um "adb bugreport" e um link para o app na Google Play Store, se o recurso estiver disponível. Caso contrário, se possível, anexe um APK que reproduza o problema. Os problemas (inclusive os anexos) podem ser visualizados publicamente.