Проверка поведения приложения в среде выполнения Android (ART)

Среда выполнения Android (ART) — это среда выполнения по умолчанию для устройств под управлением Android 5.0 (уровень API 21) и более поздних версий. Эта среда выполнения предлагает ряд функций, улучшающих производительность и плавность работы платформы Android и приложений. Дополнительную информацию о новых функциях ART можно найти в разделе «Введение в ART» .

Однако некоторые методы, работающие на Dalvik, не работают на ART. В этом документе вы узнаете, на что следует обратить внимание при переносе существующего приложения на совместимость с ART. Большинство приложений должны работать только при работе с ART.

Решение проблем со сборкой мусора (GC)

В Dalvik приложения часто находят полезным явно вызывать System.gc() для запроса сборки мусора (GC). В случае с ART это должно быть гораздо менее необходимым, особенно если вы вызываете сборку мусора, чтобы предотвратить появление типа GC_FOR_ALLOC или уменьшить фрагментацию. Вы можете проверить, какая среда выполнения используется, вызвав System.getProperty("java.vm.version") . Если используется ART, значение свойства равно "2.0.0" или выше.

ART использует сборщик Concurrent Copying (CC), который одновременно сжимает кучу Java. По этой причине вам следует избегать использования методов, несовместимых с сжатием GC (например, сохранения указателей на данные экземпляра объекта). Это особенно важно для приложений, использующих собственный интерфейс Java (JNI). Дополнительные сведения см. в разделе Предотвращение проблем JNI .

Предотвращение проблем JNI

JNI в ART несколько более строгий, чем в Dalvik. Особенно полезно использовать режим CheckJNI для выявления распространенных проблем. Если ваше приложение использует код C/C++, вам следует просмотреть следующую статью:

Отладка Android JNI с помощью CheckJNI

Проверка кода JNI на наличие проблем со сборкой мусора

Сборщик параллельного копирования (CC) может перемещать объекты в памяти для сжатия. Если вы используете код C/C++, не выполняйте операции, несовместимые с сжатием GC. Мы усовершенствовали CheckJNI для выявления некоторых потенциальных проблем (как описано в разделе «Изменения локальных ссылок JNI в ICS »).

В частности, следует обратить внимание на использование функций Get...ArrayElements() и Release...ArrayElements() . Во время выполнения с некомпактным сборщиком мусора функции Get...ArrayElements() обычно возвращают ссылку на фактическую память, в которой хранится объект массива. Если вы вносите изменение в один из возвращаемых элементов массива, сам объект массива изменяется (и аргументы Release...ArrayElements() обычно игнорируются). Однако если используется сжатие GC, функции Get...ArrayElements() могут возвращать копию памяти. Если вы неправильно используете ссылку при использовании сжатия GC, это может привести к повреждению памяти или другим проблемам. Например:

  • Если вы вносите какие-либо изменения в возвращаемые элементы массива, по завершении вы должны вызвать соответствующую функцию Release...ArrayElements() , чтобы убедиться, что внесенные вами изменения правильно скопированы обратно в базовый объект массива.
  • При освобождении элементов массива памяти необходимо использовать соответствующий режим, в зависимости от того, какие изменения вы внесли:
    • Если вы не вносили никаких изменений в элементы массива, используйте режим JNI_ABORT , который освобождает память без копирования изменений обратно в базовый объект массива.
    • Если вы внесли изменения в массив и ссылка больше не нужна, используйте код 0 (который обновляет объект массива и освобождает копию памяти).
    • Если вы внесли изменения в массив, который хотите зафиксировать, и хотите сохранить копию массива, используйте JNI_COMMIT (который обновляет базовый объект массива и сохраняет копию).
  • Когда вы вызываете Release...ArrayElements() , верните тот же указатель, который изначально был возвращен Get...ArrayElements() . Например, небезопасно увеличивать исходный указатель (для сканирования возвращаемых элементов массива), а затем передавать увеличенный указатель в Release...ArrayElements() . Передача этого измененного указателя может привести к освобождению не той памяти, что приведет к повреждению памяти.

Обработка ошибок

JNI ART выдает ошибки в ряде случаев, когда Dalvik этого не делает. (Опять же, вы можете выявить множество таких случаев, тестируя с помощью CheckJNI.)

Например, если RegisterNatives вызывается с несуществующим методом (возможно, потому, что метод был удален таким инструментом, как ProGuard ), ART теперь правильно выдает 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 также регистрирует ошибку (видимую в logcat), если RegisterNatives вызывается без методов:

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

Кроме того, функции JNI GetFieldID() и GetStaticFieldID() теперь корректно выдают NoSuchFieldError , а не просто возвращают значение null. Аналогично, GetMethodID() и GetStaticMethodID() теперь корректно выдают NoSuchMethodError . Это может привести к сбоям CheckJNI из-за необработанных исключений или исключений, выдаваемых Java-вызовам собственного кода. Это делает особенно важным тестирование ART-совместимых приложений в режиме CheckJNI.

ART ожидает, что пользователи методов JNI CallNonvirtual...Method() (таких как CallNonvirtualVoidMethod() ) будут использовать класс, объявляющий метод, а не подкласс, как того требует спецификация JNI.

Предотвращение проблем с размером стека

У Dalvik были отдельные стеки для собственного и Java-кода: размер стека Java по умолчанию составлял 32 КБ, а размер собственного стека по умолчанию — 1 МБ. ART имеет унифицированный стек для лучшей локальности. Обычно размер стека ART Thread должен быть примерно таким же, как и у Dalvik. Однако если вы явно задали размеры стека, вам может потребоваться вернуться к этим значениям для приложений, работающих в ART.

  • В Java просмотрите вызовы конструктора Thread , которые явно указывают размер стека. Например, вам потребуется увеличить размер, если произойдет StackOverflowError .
  • В C/C++ просмотрите использование pthread_attr_setstack() и pthread_attr_setstacksize() для потоков, которые также выполняют код Java через JNI. Вот пример ошибки, регистрируемой, когда приложение пытается вызвать JNI AttachCurrentThread() , когда размер pthread слишком мал:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Изменения объектной модели

Dalvik неправильно разрешал подклассам переопределять частные методы пакета. АРТ выдает предупреждение в таких случаях:

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

Если вы собираетесь переопределить метод класса в другом пакете, объявите этот метод как public или protected .

Object теперь имеет частные поля. Приложениям, которые анализируют поля в своих иерархиях классов, следует быть осторожными и не пытаться просмотреть поля Object . Например, если вы выполняете итерацию по иерархии классов как часть структуры сериализации, остановитесь, когда

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

вместо продолжения, пока метод не вернет null .

Proxy InvocationHandler.invoke() теперь получает null , если аргументов нет, а не пустой массив. Такое поведение было задокументировано ранее, но не было правильно обработано в Dalvik. В предыдущих версиях Mockito с этим были проблемы, поэтому при тестировании с помощью ART используйте обновленную версию Mockito.

Исправление проблем с компиляцией AOT

Компиляция Java Ahead-Of-Time (AOT) компании ART должна работать для всего стандартного кода Java. Компиляция выполняется инструментом dex2oat от ART; Если у вас возникнут какие-либо проблемы, связанные с dex2oat во время установки, сообщите нам об этом (см. «Сообщение о проблемах» ), чтобы мы могли исправить их как можно быстрее. Несколько вопросов, на которые следует обратить внимание:

  • ART выполняет более строгую проверку байт-кода во время установки, чем Dalvik. Код, созданный с помощью инструментов сборки Android, должен подойти. Однако некоторые инструменты постобработки (особенно инструменты, выполняющие обфускацию) могут создавать недействительные файлы, которые принимаются Dalvik, но отклоняются ART. Мы работаем с поставщиками инструментов, чтобы найти и устранить такие проблемы. Во многих случаях эти проблемы могут решить получение последних версий ваших инструментов и восстановление файлов DEX.
  • Некоторые типичные проблемы, на которые указывает верификатор ART, включают:
    • неверный поток управления
    • несбалансированный monitorenter / monitorexit
    • Размер списка типов параметров нулевой длины
  • Некоторые приложения зависят от установленного формата файла .odex в /system/framework , /data/dalvik-cache или в оптимизированном выходном каталоге DexClassLoader . Эти файлы теперь являются файлами ELF, а не расширенной формой файлов DEX. Хотя ART пытается следовать тем же правилам именования и блокировки, что и Dalvik, приложения не должны зависеть от формата файла; формат может быть изменен без предварительного уведомления.

    Примечание. В Android 8.0 (уровень API 26) и более поздних версиях оптимизированный выходной каталог DexClassLoader устарел. Дополнительные сведения см. в документации конструктора DexClassLoader() .

Сообщить о проблемах

Если вы столкнулись с какими-либо проблемами, не связанными с проблемами JNI приложения, сообщите о них через систему отслеживания проблем проекта с открытым исходным кодом Android по адресу https://code.google.com/p/android/issues/list . Включите "adb bugreport" и ссылку на приложение в магазине Google Play, если оно доступно. В противном случае, если возможно, прикрепите APK, воспроизводящий проблему. Обратите внимание, что проблемы (включая вложения) общедоступны.