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 mais recentes. Esse ambiente de execução oferece vários recursos que melhoram o desempenho e a suavidade 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 traz informações a serem observadas ao migrar um app existente para que ele seja compatível com o ART. A maioria dos apps precisa funcionar com o ART.

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

Na Dalvik, os apps acham útil chamar explicitamente System.gc() para solicitar a coleta de lixo (GC). Isso será menos necessário com o ART, especialmente se você estiver invocando a coleta de lixo para evitar ocorrências do tipo GC_FOR_ALLOC ou reduzir a fragmentação. Verifique qual ambiente de execução está em uso chamando System.getProperty("java.vm.version"). Se o ART estiver em uso, o valor da propriedade será "2.0.0" ou maior.

O ART usa o coletor de cópia simultânea (CC, na sigla em inglês), que compacta simultaneamente a heap Java. Por isso, evite o uso de técnicas incompatíveis com a GC com compactação (como salvar ponteiros em dados de instância do objeto). Isso é particularmente importante para apps que usam a Java Native Interface (JNI). Para mais informações, consulte Como evitar problemas de JNI.

Prevenção de problemas de JNI

O JNI do ART é um pouco mais rigoroso que o da Dalvik. É altamente recomendável usar o modo CheckJNI para detectar problemas comuns. Se o app usa código C/C++, consulte este artigo:

Como depurar JNI do Android com o CheckJNI

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

O coletor de cópias simultâneas (CC) pode mover objetos na memória para compactação. Se você usar o código C/C++, não execute operações incompatíveis com a GC com compactação. Melhoria do CheckJNI para identificar possíveis problemas, conforme descrito em Mudanças na referência local da JNI no ICS (link em inglês).

Uma área a ser observada em particular é o uso das funções Get...ArrayElements() e Release...ArrayElements(). Em ambientes 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 será alterado (e os argumentos de Release...ArrayElements() geralmente serã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ê usar a referência de forma incorreta enquanto a GC com compactação estiver em uso, isso pode corromper a memória ou causar outros problemas. Por exemplo:

  • Se você fizer alguma alteração nos elementos da matriz retornados, chame a função Release...ArrayElements() apropriada quando terminar para garantir que as alterações feitas sejam copiadas corretamente de volta para o objeto de matriz subjacente.
  • Quando liberar os elementos de matriz de memória, você precisa usar o modo apropriado, dependendo das mudanças feitas:
    • Se você não fez nenhuma mudança nos elementos da matriz, use o modo JNI_ABORT, que libera a memória sem copiar as mudanças no objeto de matriz subjacente.
    • Se você 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 você fez alterações na matriz que quer confirmar e quer 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, não é seguro incrementar o ponteiro original para verificar os elementos da matriz retornados e, em seguida, transmitir 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 alguns casos em que o Dalvik não ocorre. Mais uma vez, você pode identificar muitos desses casos testando com o CheckJNI.

Por exemplo, se RegisterNatives for chamado com um método que não existe (talvez porque ele foi removido por uma ferramenta como o ProGuard), o ART agora gera NoSuchMethodError corretamente:

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 JNI GetFieldID() e GetStaticFieldID() agora geram NoSuchFieldError corretamente em vez de simplesmente retornar um valor nulo Da mesma forma, GetMethodID() e GetStaticMethodID() agora geram NoSuchMethodError corretamente Isso pode levar a falhas de CheckJNI devido às exceções não processadas ou às exceções geradas para autores de chamada do Java do código nativo. Isso torna especialmente importante o teste de apps 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 nativo e Java, 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 oferecer uma localidade melhor. Normalmente, o tamanho da pilha Thread do ART precisa ser aproximadamente o mesmo da Dalvik. No entanto, se você definir tamanhos de pilha explicitamente, pode ser necessário revisar esses valores para apps em execução no ART.

  • Em Java, revise as chamadas para o construtor Thread que especificam um tamanho de pilha explícito. Por exemplo, será necessário aumentar o tamanho se StackOverflowError ocorrer.
  • Em C/C++, revise o uso de pthread_attr_setstack() e pthread_attr_setstacksize() para linhas de execução que também executam o código Java via JNI. Veja um exemplo do erro registrado quando um app tenta chamar AttachCurrentThread() da JNI quando o tamanho da linha de execução é muito pequeno:
    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 o método como public ou protected.

Object agora tem campos particulares Apps que refletem sobre os campos das hierarquias de classes precisam ter cuidado para não tentar examinar os campos de Object. Por exemplo, se você estiver iterando uma hierarquia de classes como parte de um framework de serialização, pare quando

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

em vez de continuar até que o método retorne 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 processado corretamente na Dalvik. Versões anteriores do Mockito têm dificuldades com isso. Portanto, use uma versão atualizada do Mockito ao testar com o ART.

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

A compilação Java antecipada (AOT, na sigla em inglês) do ART precisa funcionar para todos os códigos Java padrão. A compilação é realizada pela ferramenta dex2oat do ART. Se você encontrar algum problema relacionado ao dex2oat no momento da instalação, entre em contato conosco (consulte Como informar problemas) para que possamos corrigi-los o mais rápido 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. No entanto, algumas ferramentas de pós-processamento (especialmente ferramentas que executam ofuscação) podem produzir arquivos inválidos que são tolerados pela Dalvik, mas rejeitados pelo ART. Estamos trabalhando com fornecedores de ferramentas para encontrar e corrigir esses problemas. Em muitos casos, instalar as versões mais recentes das ferramentas e gerar novamente os arquivos DEX pode corrigir esses problemas.
  • Estes são alguns problemas comuns sinalizados pelo verificador do ART:
    • controle de fluxo inválido
    • desequilibrado monitorenter/monitorexit
    • tamanho de lista de tipo de parâmetro 0
  • Alguns apps têm dependências no formato de arquivo .odex instalado em /system/framework, /data/dalvik-cache ou no diretório de saída otimizado de DexClassLoader. Esses arquivos agora são arquivos ELF e não uma forma estendida de arquivos DEX. Embora o ART tente seguir as mesmas regras de nomenclatura e bloqueio que a Dalvik, os apps não podem depender do formato de arquivo, porque ele está sujeito a mudanças sem aviso prévio.

    Observação:no Android 8.0 (API de nível 26) e versões mais recentes, o diretório de saída otimizado DexClassLoader foi descontinuado. Para mais informações, consulte a documentação do construtor DexClassLoader().

Informar problemas

Se você encontrar algum problema que não seja causado pela JNI do app, informe-o 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 disponível. Caso contrário, se possível, anexe um APK que reproduza o problema. Os problemas (incluindo anexos) são visíveis publicamente.