Трассировка опен gl. Графика OpenGL в Android приложении

20.02.2019

Переводим... Перевести Китайский (упрощенное письмо) Китайский (традиционное письмо) Английский Французский Немецкий Итальянский Португальский Русский Испанский Турецкий

К сожалению, мы не можем перевести эту информацию прямо сейчас - пожалуйста, повторите попытку позже.

Возможность портирования игр и других приложений, интенсивно использующих 3D-графику на основе стандартов OpenGL, на устройства Android на базе Intel® Atom, имеет для нас огромную важность, поскольку существует множество уже изданных приложений, игровых движков и других программ на базе OpenGL; OpenGL обеспечивает переносимость программ, а Android постоянно расширяет поддержку OpenGL ES и C/C++. Многие приложения на базе OpenGL даже доступны в виде открытого исходного кода, например серия Quake компании Id Software. В данной статье, состоящей из двух частей, показано, как начать портирование, и подробно описаны препятствия, существующие при портировании компонентов отрисовки в приложениях, собранных в более ранних версиях OpenGL, на платформу Android на базе процессоров Intel Atom. Кроме того, здесь рассматривается портирование кода OpenGL из настольных операционных систем, таких как Windows* и Linux*, и из приложений, использующих внедренные версии OpenGL ES, с системой управления окнами или без нее.

В первой части данной статьи содержится вводная информация об использовании OpenGL ES на платформе Android с помощью пакета средств разработки программного обеспечения (SDK) или пакета Android NDK (Native Development Kit) и о том, по какому принципу выбирается тот или иной пакет. Здесь описаны различные примеры приложений OpenGL ES в SDK и NDK и интерфейс JNI (Java* Native Interface), позволяющий комбинировать компоненты Java и C/C++. Также здесь обсуждается выбор версии OpenGL ES - 1.1 или 2.0.

Во второй части рассматриваются препятствия, существующие при портировании игр OpenGL, которые необходимо изучить, прежде чем начинать подобный проект, в том числе различия расширений OpenGL, поддержка операций с плавающей запятой, сжатые форматы текстур и библиотека GLU. Кроме того, здесь описана процедура настройки системы разработки для Android на базе процессоров Intel Atom и пути достижения максимально возможной производительности при эмуляции виртуального устройства Android при использовании OpenGL ES.

Графика на платформе Android

Существуют четыре различных способа отрисовки графики на платформе Android, и у каждого из них есть свои сильные стороны и ограничения (см. табл. 1). В задачи данной статьи не входит подробное описание всех этих вариантов. С точки зрения портирования кода OpenGL с других платформ для нас важны только два из них: классы SDK Wrapper для OpenGL ES и NDK для разработки OpenGL ES в C/C++. Что касается других вариантов, SDK Canvas представляет собой мощный API-интерфейс для создания 2D-графики, который можно использовать в сочетании с OpenGL ES, однако его возможности ограничены 2D-графикой и для его использования требуется написание нового кода.

API-интерфейс Renderscript для Android изначально поддерживал OpenGL ES, но поддержка прекратилась в версии API уровня 16 (Jelly Bean) и была недоступна для новых проектов. Наилучшей областью применения Renderscript в настоящее время является повышение производительности алгоритмов с большими объемами вычислений, но низкой потребностью в выделении памяти и передаче данных, таких как расчет физических параметров в играх-симуляторах.

Таблица 1. Четыре способа отрисовки графики на платформе Android

Портирование приложений OpenGL в более ранние версии Android было затруднено, поскольку старый код OpenGL был чаще всего написан на C или C++, а Android поддерживал только Java до выпуска NDK в версии Android 1.5 (Cupcake). OpenGL ES 1.0 и 1.1 поддерживались с самого начала, но производительность была неустойчива, поскольку ускорение не являлось обязательным. Однако за последние годы Android совершил огромный скачок вперед. В SDK версии Android 2.2 (Froyo) и NDK редакции 3 была добавлена поддержка OpenGL ES 2.0, а в NDK редакции 7 появилась улучшенная поддержка расширений OpenGL ES. На сегодняшний день OpenGL ES 2.0 так интенсивно используется в собственной графической платформе Android, что его наличие стало считаться обязательным. OpenGL ES 1.1 и 2.0 с ускорением теперь в обязательном порядке присутствуют на всех новых устройствах Android, что в особенности проявилось с ростом размера экранов. Сегодня Android обеспечивает устойчивую и надежную производительность приложений с интенсивным использованием 3D-графики, созданных на основе кода OpenGL ES 1.1 или 2.0 на языке Java или C/C++, и у разработчиков появилось несколько средств для упрощения процесса портирования.

Использование Android Framework SDK с классами OpenGL ES Wrapper

Платформа Android SDK предоставляет набор классов-оболочек для трех версий OpenGL ES, поддерживаемых на Android (1.0, 1.1 и 2.0). Данные классы позволяют с легкостью вызывать в системе Android драйверы OpenGL ES из кода Java, даже при том, что эти драйверы выполняются нативно. Если вы создаете новую игру OpenGL ES Android с нуля или хотите преобразовать старый код C/C++ в Java, то этот способ, вероятно, будет для вас наиболее простым. Хотя язык Java предназначен для обеспечения переносимости, при портировании приложений Java могут возникать сложности, поскольку Android не поддерживает полный набор утвержденных классов, библиотек и функций API Java Platform Standard Edition (Java SE) и Java Platform Micro Edition (Java ME). Интерфейс Android Canvas API включает в себя широкий набор функций 2D API, но он поддерживается только в Android и не обеспечивает совместимость со старым кодом.

В Android SDK есть несколько дополнительных классов, упрощающих использование OpenGL ES, например GLSurfaceView и TextureView . Класс GLSurfaceView аналогичен классу SurfaceView, который используется с Canvas API, за исключением нескольких специальных дополнительных функций для OpenGL ES. Он обрабатывает необходимую инициализацию библиотеки EGL (Embedded System Graphics Library), размещает отображаемую в Android поверхность в определенном месте на экране и создает поток. Кроме того, в него включены полезные функции для трассировки и отладки вызовов OpenGL ES. Можно быстро создать новое приложение OpenGL ES, реализовав всего три метода для интерфейса GLSurfaceView.Renderer() , как показано в табице 2.

Таблица 2. Минимально необходимые методы для интерфейса GLSurfaceView.Renderer

Начиная с версии Android 4.0, можно использовать класс TextureView вместо GLSurfaceView , позволяющий отрисовывать поверхности OpenGL ES с дополнительными возможностями, хотя для этого потребуется написать больше кода. Поверхности TextureView работают аналогично обычным проекциям (Views) и могут использоваться для отрисовки поверхностей за пределами экрана. Эта функция позволяет перемещать, преобразовывать, анимировать поверхности и выполнять наложение полупрозрачных изображений TextureView , когда они соединяются на экране. Класс TextureView также позволяет сочетать отрисовку OpenGL ES и Canvas API.

Классы Bitmap и GLUtils упрощают создание текстур для OpenGL ES благодаря использованию функций Android Canvas API и позволяют загружать текстуры из файлов PNG, JPG и GIF. Экземпляры Bitmap используются для размещения отрисованных поверхностей Android Canvas API. Класс GLUtils позволяет преобразовывать изображения из растрового формата в текстуры OpenGL ES. Благодаря этой интеграции с помощью Canvas API можно отрисовывать 2D-изображения, используемые затем в качестве текстур в OpenGL ES. Это особенно полезно при создании графических элементов, которые невозможно создать в OpenGL ES, например виджетов графического интерфейса и шрифтов. Но для использования этих функций, конечно, необходимо написать новый код Java.

Класс Bitmap был разработан прежде всего для использования с Canvas API, и при его использовании для загрузки текстур в OpenGL ES возникает несколько серьезных ограничений. Функции Canvas API соответствуют спецификации наложения по альфа-каналу Портера-Даффа, и класс Bitmap оптимизирует изображения со значениями альфа-канала для каждого пикселя, сохраняя их в формате с премультипликацией (A, R*A, G*A, B*A). Этот механизм подходит для алгоритма Портера-Даффа, но не для OpenGL, где требуется формат матрицы без премультипликации (ARGB). Это означает, что класс Bitmap можно использовать только с полностью непрозрачными текстурами (или с текстурами без значений альфа-канала для каждого пикселя). В трехмерных играх обычно требуются текстуры с альфа-каналом, поэтому вместо растровых изображений следует использовать для загрузки текстур байтовые массивы или NDK.

Еще одним недостатком класса Bitmap является поддержка загрузки только изображений в формате PNG, JPG или GIF, тогда как в играх OpenGL обычно используются сжатые форматы текстур, которые распаковываются графическим процессором и часто зависят от его архитектуры, например ETC1 и PVRTC. Классы Bitmap и GLUtils не поддерживают текстуры с зависимым форматом сжатия и мипмаппинг. Поскольку данные функции интенсивно используются в большинстве 3D-игр, это существенно усложняет портирование старых игр OpenGL на Android с использованием SDK. До тех пор, пока Google не решит эти проблемы, лучше всего просто отказаться от загрузки текстур с помощью классов Bitmap и GLUtils . Форматы текстур обсуждаются далее в разделе "Сжатые форматы текстур".

В Android ApiDemos содержится образец приложения с именем StaticTriangleRenderer , на примере которого показана загрузка непрозрачной текстуры из ресурсного файла PNG с использованием оболочки GLES10 для OpenGL ES 1.0 и классов GLSurfaceView , Bitmap и GLUtils . Аналогичная версия с именем GLES20TriangleRenderer , напротив, использует класс-оболочку GLES20 для OpenGL ES 2.0. Эти образцы служат хорошими отправными точками для разработки игр OpenGL ES, если вы рассчитываете использовать Android Framework SDK и классы-оболочки. Не используйте оригинальную версию с именем TriangleRenderer , поскольку в ней применяется оболочка для более старой версии привязок OpenGL ES к Java с именем javax.microedition.khronos.opengles . Компания Google выпустила новые привязки, чтобы обеспечить статический интерфейс для OpenGL ES специально под Android. У этих статических привязок более высокая производительность, они поддерживают больше функций OpenGL ES и позволяют использовать модель программирования, более близкую к использованию OpenGL ES с кодом C/C++, что упрощает повторное использование кода.

Android Framework SDK обеспечивает поддержку OpenGL ES с помощью нескольких привязок для Java из Google и Khronos, как показано в таблице 3.

Таблица 3. Сводная информация о классах-оболочках для OpenGL ES и образцах приложений

Специальные статические привязки для Android имеют более высокую производительность и должны по мере возможности использоваться вместо привязок Khronos. Статические привязки предоставляют соответствующие классы-оболочки для всех версий OpenGL ES, доступных для разработки приложений под Android. Эти классы перечислены в таблице 4.

Таблица 4. Сводная информация о классах-оболочках Android для OpenGL ES и образцах приложений

Эти классы-оболочки устроены так, что большинство вызовов OpenGL ES в старом коде C/C++ можно преобразовать в Java, просто добавив к именам функций и символов OpenGL ES в виде префикса класс-оболочку подходящей версии API. См. примеры в таблице 5.

Таблица 5. Примеры преобразования вызовов OpenGL ES из C в Java

Язык C Язык Java
GLES11.glDrawArrays(GLES11.GL_TRIANGLES, 0, count)
glDrawArrays(GL_TRIANGLES, 0, count) GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, count)

При портировании игр OpenGL на Android с помощью этих классов-оболочек имеются три значительных ограничения: дополнительное потребление ресурсов средой Java Virtual Machine и JNI и работа по преобразованию старого кода C/C++ в Java. Java представляет собой интерпретируемый язык, и любой код Java на Android выполняется в виртуальной машине Dalvik и поэтому работает медленнее, чем скомпилированный код C/C++. Поскольку драйверы OpenGL ES всегда выполняются нативно, каждый вызов функции OpenGL ES через эти оболочки влечет за собой дополнительное потребление ресурсов интерфейсом JNI, что ограничивает производительность отрисовки игровой графики. Чем больше вызовов OpenGL ES выполняет ваше приложение, тем больше ресурсов потребляет интерфейс JNI. К счастью, структура OpenGL ES помогает минимизировать число необходимых обычно вызовов в ресурсоемких циклах отрисовки. Если производительность действительно ухудшается, вы всегда можете преобразовать ресурсоемкий код в C/C++ с помощью NDK. Разумеется, если изначально код был написан на C/C++, лучше всего сразу выбрать вариант с NDK.

Должны ли вы преобразовывать код C/C++ в Java - это зависит от особенностей вашего проекта. Если кода C/C++ сравнительно немного, он понятен и у вас нет задачи достичь сверхпроизводительности, то преобразование кода в Java будет оправданным. Иначе при большом объеме малопонятного кода C/C++ и высоких требованиях к производительности лучше использовать NDK.

Использование Android NDK

В июне 2009 года компания Google представила пакет NDK для Android, позволяющий выполнять в приложениях код C/C++ как нативный с более высокой производительностью по сравнению с Java. А поскольку большая часть имеющегося кода OpenGL написана на C или C++, в NDK предусмотрен более простой способ портирования приложений, особенно при наличии больших объемов кода C/C++, преобразование которых в Java нецелесообразно. Фактически, основная причина, по которой компания Google решила сделать пакет NDK общедоступным - это упрощение портирования игр OpenGL на Android. Благодаря этим преимуществам NDK становится наиболее популярным вариантом реализации приложений, требующих быстрой отрисовки графики в Android.

Пакет NDK позволяет компилировать код C/C++ в библиотеки общих объектов Linux, статично привязанные к приложению для Android. Библиотеки собираются с помощью средств GNU, которые предоставляются в пакете NDK от компании Google и могут запускаться в системах разработки для Windows, Mac OS* X или Linux при использовании интегрированной среды разработки Eclipse* или интерфейса командной строки. Этот набор средств поддерживает три процессорные архитектуры: ARM, Intel Atom (x86) и MIPS. Доступны все возможности C/C++, но большинство функций Linux API недоступно. Фактически непосредственная поддержка распространяется на API-интерфейсы OpenGL ES, OpenMAX* AL, OpenSL ES*, zlib, API ввода-вывода файлов Linux и немногие другие, которые Google относит к категории стабильных API-интерфейсов . Однако доступна документация по портированию других библиотек Linux в проект NDK, если это необходимо.

NDK позволяет произвольно распределять код между Java и C/C++ в соответствии с требованиями вашего приложения. В NDK поддерживается механизм вызова кода C/C++ из Java, называемый Java Native Interface . Но вызовы JNI приводят к дополнительному расходованию ресурсов, поэтому важно соблюдать осторожность при распределении приложений и использовать нативный код для минимизации вызовов через JNI. Обычно для наилучшей производительности и простоты портирования большая часть кода OpenGL ES остается на C/C++, а новый код Java пишется для использования класса GLSurfaceView и других классов SDK с целью управления событиями жизненного цикла приложения и поддержки других игровых функций. Android поддерживает JNI для процессоров Intel Atom начиная с NDK редакции 6b.

Пакет NDK поддерживает OpenGL ES 1.1 и 2.0 и включает в себя образцы приложений для обеих версий, которые также демонстрируют сочетание функции C и Java при использовании JNI. Эти приложения отличаются распределением кода между Java и C и реализацией многопоточности. Все эти приложения используют NDK и нативный код C, но в образце native-media весь код OpenGL ES остается на языке Java, в образцах san-angeles и native-activity весь код OpenGL ES на языке C, а в образце hello-gl2 код EGL и OpenGL ES распределен между Java и C. Использовать образец hello-gl2 не рекомендуется, поскольку в нем присутствует это разделение и не реализован предпочтительный метод конфигурирования GLSurfaceView для поверхностей OpenGL ES 2.0, состоящий в вызове метода setEGLContextClientVersion(2) . См. в таблице 6.

Таблица 6. Сводная информация об образцах приложений OpenGL ES в NDK

Образец bitmap-plasma не использует OpenGL ES, но он также интересен тем, что демонстрирует использование библиотеки jnigraphics для реализации нативных функций, непосредственно обрабатывающих пиксели растровых изображений Android.

Примечание. Пакет Android NDK можно загрузить по адресу http://developer.android.com/tools/sdk/ndk/index.html .

События жизненного цикла Activity и многопоточность

Android требует, чтобы все вызовы OpenGL ES выполнялись из одного потока, поскольку контекст EGL может быть связан только с одним потоком, а отрисовка графики в главном потоке пользовательского интерфейса категорически не приветствуется. Лучше создать отдельный специальный поток для всего вашего кода OpenGL ES, который всегда будет выполняться из одного этого потока. Если в вашем приложении используется класс GLSurfaceView , этот выделенный поток отрисовки OpenGL ES создается автоматически. В противном случае приложение должно будет само создать поток отрисовки.

В образцах приложений san-angeles и native-activity весь код OpenGL ES на C сохранен, но в san-angeles используется часть кода Java и класс GLSurfaceView для создания потока отрисовки и управления жизненным циклом Activity, а в native-activity кода Java нет. Управление событиями жизненного цикла Activity осуществляется на C без использования GLSurfaceView , и используется поток отрисовки, предоставляемый классом NativeActivity. NativeActivity - это удобный класс из NDK, позволяющий реализовать обработчики событий жизненного цикла Activity в виде нативного кода, например onCreate() , onPause() и onResume() . К некоторым службам и поставщикам контента Android невозможно получить непосредственный доступ из нативного кода, но можно реализовать это через JNI.

Образец native-activity является хорошей отправной точкой для портирования игр OpenGL ES, поскольку в нем показано использование класса NativeActivity и статической библиотеки android_native_app_glue для обработки событий жизненного цикла в нативном коде. Эти классы предоставляют отдельный поток отрисовки для кода OpenGL ES, поверхность отрисовки и окно на экране, поэтому вам не нужны классы GLSurfaceView и TextureView . Основной точкой входа этого нативного приложения является функция android_main() , выполняемая в своем собственном потоке и имеющая свой собственный цикл событий, позволяющий принимать входящие события. К сожалению, NDK не включает в себя версию этого образца для OpenGL ES 2.0, но вы можете заменить весь код версии 1.1 в этом образце на свой код версии 2.0.

Приложения, использующие NativeActivity , должны выполняться в Android 2.3 или более поздней версии и содержать в своем файле манифеста специальные декларации, описанные во второй части данной серии статей.

Java Native Interface

Если вы реализуете свое приложение преимущественно на C/C++, вам будет сложно избежать использования некоторых классов Java в случае крупных и более профессиональных проектов. Это, например, доступные только в SDK API-интерфейсы Android AssetManager и Resources, являющиеся предпочтительными для обработки интернационализации, различных размеров экрана и других ситуаций. Интерфейс JNI также является хорошим решением, поскольку он позволяет вызывать не только функции C/C++ из кода Java, но и классы Java из кода C/C++. Поэтому, несмотря на дополнительное расходование ресурсов, интерфейсом JNI не следует пренебрегать. Он обеспечивает наилучший способ доступа к важным системным функциям, предлагаемым только в SDK, особенно когда для этих функций не обязательно добиваться высокой производительности. Мы не будем приводить в данной статье полное описание использования JNI, но выделим три основных этапа, необходимых для создания вызова C/C++ из Java с помощью JNI:

  1. Добавление объявления функции C/C++ в файл класса Java в качестве нативного типа.
  2. Добавление статического инициализатора библиотеки общих объектов, содержащей нативную функцию.
  3. Добавление функции с соответствующим именем, согласованным с применяемой схемой именования, в файл с нативным исходным кодом программы.

Примечание. Дополнительные сведения об использовании JNI с NDK см. по адресу http://developer.android.com/training/articles/perf-jni.html .

OpenGL ES 1.1 или 2.0?

Итак, на какую версию OpenGL ES вы должны ориентироваться в приложении для Android? Версия OpenGL ES 1.0 была заменена на 1.1, поэтому выбирать приходится только из двух версий: 1.1 и 2.0. Компании Khronos и Google, возможно, будут поддерживать обе эти версии в течение неограниченного срока, но OpenGL ES 2.0 по многим параметрам превосходит версию 1.1. Она гораздо более универсальна и обеспечивает более высокую производительность благодаря функциям программирования шейдеров (обработчиков теней) в языке GLSL (OpenGL Shading Language) ES. В ней даже может быть снижена потребность к коде и памяти для текстур. Но у Khronos и Google остается, по крайней мере, одна веская причина для того, чтобы продолжать поддержку версии 1.1, - эта версия по внешнему виду намного ближе к оригинальному стандарту OpenGL 1.x, применяемому в настольных ПК и игровых консолях во всем мире на протяжении десятилетий. Поэтому старые игры может быть проще портировать на OpenGL ES 1.1, чем на 2.0; и, чем старее игра, тем вероятность этого выше.

Если в портируемой игре нет кода шейдера, можно выбрать OpenGL ES версии как 1.1, так и 2.0, хотя работать с версией 1.1, вероятно, будет проще. Если в игре уже есть код шейдера, то, безусловно, лучше выбрать OpenGL ES 2.0, особенно если учесть, что в последних версиях Android используется преимущественно версия 2.0. Как сообщает компания Google, по состоянию на октябрь 2012 года более 90 процентов устройств Android, обращающихся на веб-сайт Google Play, поддерживают OpenGL ES версии как 1.1, так и 2.0.

Примечание. Дополнительные сведения см. по адресу http://developer.android.com/about/dashboards/index.html .

Заключение

Отрисовку графики OpenGL ES на Android можно реализовать с помощью Android SDK, NDK или их комбинации с использованием JNI. При использовании пакета SDK потребуется написание кода на Java, и этот способ лучше всего подходит для разработки новых приложений, тогда как NDK делает целесообразным портирование уже имеющегося кода OpenGL на языке C/C++. Для большинства проектов портирования игр потребуется сочетание компонентов SDK и NDK. В новых проектах рекомендуется ориентироваться на версию OpenGL ES 2.0, а не 1.1, кроме случаев, когда имеющийся код OpenGL настолько старый, что не использует функции шейдеров GLSL.

Во второй части этой серии статей рассматриваются препятствия, существующие при портировании игр OpenGL, которые необходимо изучить, прежде чем начинать подобный проект, в том числе различия расширений OpenGL, поддержка операций с плавающей запятой, сжатые форматы текстур и библиотека GLU. Кроме того, здесь описана настройка системы разработки для Android на базе процессоров Intel Atom и пути достижения максимально возможной производительности в средствах эмуляции виртуального устройства Android при использовании OpenGL ES.

Об авторе

Клэй Д. Монтгомери (Clay D. Montgomery) является одним из ведущих разработчиков драйверов и приложений для OpenGL во встроенных системах. Он участвовал в разработке аппаратных графических ускорителей, графических драйверов, API-интерфейсов и приложений OpenGL для различных платформ в компаниях STB Systems, VLSI Technology, Philips Semiconductors, Nokia, Texas Instruments, AMX и в качестве независимого консультанта. Он сыграл важную роль в разработке некоторых первых драйверов OpenGL ES, OpenVG* и SVG, приложений для платформ Freescale i.MX и TI OMAP* и графических ядер Vivante, AMD и PowerVR*. Кроме того, он подготовил и провел ряд семинаров по разработке на базе OpenGL ES во встроенных системах Linux и являлся представителем нескольких компаний консорциума Khronos Group.

В Android, как и в других популярных операционных системах, есть свои секреты. Некоторые из них полезны, но используются редко. Мы расскажем о малоизвестных и интересных секретах Андроида.

В основном, когда речь заходит об android-секретах, многие рассказывают про какие-то функции, о которых знают все вокруг. Мы постараемся не вдаваться в привычный функционал смартфонов и планшетов.

Инженерные коды

Первый, самый главный секрет – это инженерные коды. Обычным пользователям они вряд ли пригодятся. В основном, они используются работниками в сервисных центрах, когда нужно узнать что-то об устройстве или выполнить системную команду.

*#*#4636#*#* - информация и настройка;

*#*#8351#*#* - включить запись телефонных разговоров;

*#*#4636#*#* - предоставит полезные данные об устройстве:

  • о телефоне;
  • о батарее;
  • статистика и использование телефона и батареи.

*#*#7780#*#* - отформатирует смартфон или планшет, но оставит все приложения, будь то системные или загруженные. Также останутся все файлы на внешней SD-карте.

*2767*3855# - полностью отформатирует девайс.

*#*#34971539#*#* - позволяет управлять прошивкой камеры, а также получить о ней информацию. После введения кода вы можете выбрать:

  • обновление прошивки камеры в образ (ни в коем случае не делать!);
  • обновление прошивки камеры;
  • данные о прошивке камеры;
  • количество выполняемых ранее прошивок камеры.

*#*#7594#*#* - позволит изменить функцию при длительном зажатии кнопки питания. Другими словами, вы можете назначить для нее выключение или перезагрузку гаджета, включение/выключение мобильных данных и так далее;

*#*#273283*255*663 282*#*#* - позволяет сделать резервное копирование любых файлов на устройстве;

*#*#197328640#*#* - открывает меню обслуживания. Вы можете протестировать ваш гаджет, а также сменить настройки WLAN, Bluetooth и GPS;

*#*#232339#*#* или *#*#526#*#* или *#*#528#*#* - настройки WLAN;

*#*#232338#*#* - поможет узнать МАС-адрес Wi-FI;

*#*#1472365#*#* - тест GPS системы;

*#*#1575#*#* - GPS;

*#*#232331#*#* - Bluetooth;

*#*#232337#*# - поможет узнать адрес Bluetooth.

Коды для тестирования

Они запускают различные тесты устройства.

*#*#0283#*#* - тестирование передающей инфраструктуры;

*#*#0*#*#* - экрана (LCD);

*#*#0673#*#* или *#*#0289#*#* - звука;

*#*#0842#*#* - девайса (подсветки и вибрации);

*#*#2663#*#* - сенсора;

*#*#2664#*#* - еще один тест сенсора;

*#*#0588#*#* - датчика движения;

*#*#3264#*#* - RAM.

Режим разработчика

Теперь поговорим о «Режиме разработчика». Вы уже наверняка видели такой пункт в настройках, но теперь пора рассмотреть его подробнее. Данный режим активирует дополнительные функции, которые вряд ли понадобятся в повседневной жизни. В основном, они используются разработчиками и программистами.

Для начала необходимо включить режим разработчика. Заходим в настройки и листаем в самый низ. Находим пункт «Об устройстве», и нажимаем на него несколько раз подряд. Гаджет потребует подтверждения разблокировки режима разработчика – нажмите ОК.


Теперь нам доступен ряд возможностей, пользоваться которыми нужно аккуратно! Они могут привести к системным ошибкам, о чем предупредит сам смартфон. Ниже представлены и описаны все функции данного режима.

  • Пароль резервного копирования. Если не хотите, чтобы чужие руки делали резервное копирование всех файлов вашего телефона (например, после этого загрузив все данные на свое устройство), поставьте пароль на использование.
  • Активный режим. Если ваш смартфон заряжается, то он не будет гаснуть (конечно, если вы сами его не выключите).
  • Защитить карту памяти SD. Все программы будут спрашивать разрешение на использование данных с карты памяти. Так, например, вы можете предотвратить работу вируса.
  • Отладка USB нужна для выполнения взаимодействия гаджета с ПК.
  • Эмуляция расположения эмулирует расположение.
  • Выберите отлаживаемое приложение.
  • Подождать отладчик. Когда отладчик подключится, откроется заданное выше приложение.
  • Показывать прикосновения. Довольно интересная функция, которая показывает, в каком месте вы прикоснулись к экрану. Очень полезная штука, ведь с помощью нее можно обозначать касания на экран и делать скриншоты к инструкциям, как мы сделали в статье про .
  • Показывать место указателя. Выводит подробную информацию о прикосновении и слайде по экрану (Местоположение по Х и Y и др).


  • Показывать ограничения макета. Интересная функция, с помощью которой можно увидеть размер каждого элемента на экране.

  • Показывать обновления представлений GPU. Окна, представленные посредством GPU, будут мигать.
  • Показывать обновления экрана. Обновляемая область экрана будет мерцать ярко-желтым цветом.
  • Настройка анимации. Включает масштаб анимации окна, масштаб анимации перехода и шкалу длительности аппарата. Их отключение очень помогает .
  • Отключить аппаратное наложение – постоянное использование GPU для композиции экрана.
  • Принудительная обработка GPU. Использовать аппаратное ускорение 2D в приложениях.
  • Строгий режим. Если процесс будет выполнять длительные операции в главном потоке, то экран будет мигать.
  • Выводить использование ЦП – информация об использовании центрального процессора в правом верхнем углу.

  • Профиль обработки GPU – измерение времени обработки в ASDG.
  • Включить трассировку. Включает различные трассировки, например, graphics, Input, View и другие.
  • Не сохранять операции. Удалять операции после их завершения пользователем.
  • Фоновые процессы. Позволяет ограничить количество фоновых процессов от одного до четырех.
  • Показать все ANR. Выводить окно «Приложение не отвечает» для фоновых процессов.

Итак, я нахожусь в точке, что я должен начать освещать мои модели с плоским рисунком. Тестирующее приложение является тестовым примером для реализации только последних методов, поэтому я понял, что в идеале он должен внедрять трассировку лучей (так как теоретически он может быть идеальным для графики в реальном времени через несколько лет).

Но с чего начать?

Предположим, что я никогда не делал освещения в старом OpenGL, поэтому я бы пошел прямо на не устаревшие методы.

В настоящее время приложение правильно настроило объекты буфера вершин, вершинный, нормальный и цветной ввод и правильно рисует и преобразует модели в пространстве в плоском цвете.

Есть ли источник информации, который бы взял один из плоских цветных вершин ко всему, что необходимо для правильного конечного результата через GLSL? В идеале с любыми другими дополнительными методами освещения, которые могут потребоваться для его дополнения.

2 ответов

Я бы не посоветовал попробовать реальную трассировку лучей в OpenGL, потому что для этого вам нужны много хаков и трюков, и, если вы спросите меня, больше нет смысла делать это. Если вы хотите выполнить трассировку лучей на графическом процессоре, вы должны перейти на любой язык GPGPU, такой как CUDA или OpenCL, потому что это делает вещи намного проще (но все же, далеко не тривиально).

Чтобы проиллюстрировать проблему немного дальше: Для трассировки лучей вам нужно проследить вторичные лучи и проверить пересечение с геометрией. Поэтому вам необходим доступ к геометрии каким-то умным способом внутри шейдера, однако внутри шейдера фрагмента вы не можете получить доступ к геометрии, если вы не храните его "закодировано" в некоторую текстуру. Вершинный шейдер также не предоставляет вам эту информацию геометрии изначально, а геометрические шейдеры знают только соседей, поэтому здесь проблема уже началась. Затем вам нужны структуры данных ускорения, чтобы получить разумные частоты кадров. Однако перемещение, например, Kd-Tree внутри шейдера довольно сложно, и, если я правильно помню, есть несколько статей только по этой проблеме. Если вы действительно хотите пройти этот маршрут, тем не менее, есть много статей по этой теме, их не должно быть слишком сложно найти.

Трассировщик лучей требует очень хорошо разработанных шаблонов доступа и кеширования для достижения хорошей производительности. Однако у вас есть только небольшой контроль над ними внутри GLSL, и оптимизация производительности может стать очень сложной.

Еще один момент, который следует отметить, заключается в том, что, по крайней мере, насколько мне известно, трассировка лучей в реальном времени на графических процессорах в основном ограничена статическими сценами, поскольку, например, kd-деревья работают (хорошо) для статических сцен. Если вы хотите иметь динамические сцены, вам нужны другие структуры данных (например, BVH, iirc?), Но вам постоянно нужно их поддерживать. Если я ничего не пропустил, в настоящее время продолжается много исследований по этой проблеме.

Возможно, вы что-то путаете.

OpenGL - это растеризатор. Заставлять его делать трассировку лучей возможно, но сложно. Вот почему raytracing не "идеально подходит для графики в реальном времени через несколько лет". Через несколько лет только гибридные системы будут жизнеспособными.

Итак, у вас есть три возможности.

  • Чистая трассировка. Выполните рендеринг только полноэкранного квадроцикла и в своем шейдере фрагмента прочитайте описание сцены, упакованное в буфер (например, текстуру), перейдите по иерархии и вычислите пересечения лучей треугольников.
  • Гибридная трассировка. Растрируйте свою сцену обычным способом и используйте raytracing в вашем шейдере на некоторых участках сцены, которые действительно требуют этого (преломление,... но это может быть одновременно в растеризации).
  • Чистая растеризация. Фрагментный шейдер выполняет свою нормальную работу.

Что именно вы хотите достичь? Я могу улучшить ответ в зависимости от ваших потребностей.

Платформа Android поддерживает графику OpenGL примерно так же, как шляпа-цилиндр помогает вытаскивать кроликов. Хотя OpenGL – бесспорно, одна из самых захватывающих технологий в Android, она играет в этой операционной системе довольно маргинальную роль. Кроме того, создается впечатление, что еще до выхода окончательной бета-версии интерфейс претерпел крупные изменения. Большая часть кода и многие рекомендации на эту тему, найденные нами в Вебе, устарели и уже не работают.

В версии API Vl_r2 реализована среда OpenGL ES 1.0 и большей частью ES 1.1. В сущности, это доменно-специфичный язык, интегрированный в Java. Если вам хотя бы недолго приходилось разрабатывать игровые пользовательские интерфейсы, вы привыкнете к разработке программ с OpenGL для Android быстрее, чем программист Java или даже эксперт по пользовательским интерфейсам Java.

Перед тем как перейти к обсуждению самой графической библиотеки OpenGL, давайте ненадолго обратимся к вопросу о том, как именно OpenGL рисует пикселы, которые отображаются на экране. До сих пор рассматривался сложный фреймворк View, используемый для организации и представления объектов на экране. OpenGL – это язык, на котором приложение описывает целую сцену. Такая сцена будет отображаться движком, который находится не просто за пределами виртуальной машины Java, но и вообще может работать на другом процессоре (имеется в виду графический процессор или GPU). Скоординировать на экране отображение двух видов, обрабатываемых разными процессорами, довольно непросто.

Класса SurfaceView, рассмотренного выше, для этого почти достаточно. Его задача – создавать поверхность, на которой может рисовать иной поток, кроме графического потока пользовательского интерфейса. Нам очень пригодился бы инструмент, представляющий собой расширение SurfaceView, который бы чуть лучше поддерживал многопоточность, а также обеспечивал поддержку OpenGL.

Оказывается, что именно такой инструмент уже есть. Все демонстрационные приложения, входящие в состав дистрибутива Android SDK и использующие при работе анимацию на основе OpenGL, работают с применением вспомогательного класса GLSurfaceView. Поскольку демонстрационные приложения, написанные самими создателями Android, используют этот класс, мы советуем рассмотреть возможность его применения и в ваших программах.

GLSurfaceView определяет интерфейс GLSurfaceView. Renderer, который радикально упрощает всю работу, связанную с OpenGL и GLSurfaceView. На самом же деле весь этот функционал исключительно сложен. GLSurfaceView вызывает метод отображения getConf і gSpec для получения информации о конфигурации OpenGL. Еще два метода – sizeChanged и surfaceCreated – вызываются GLSurfaceView для того, чтобы сообщить средству отображения (рендереру) соответственно, что его размер изменился или что нужно подготовиться к рисованию. Наконец drawFrame, центральная часть интерфейса, вызывается для отображения нового кадра OpenGL.

В примере 9.16 показаны важные методы из реализации средства отображения OpenGL.

Пример 9.16. Покадровая анимация с применением OpenGL

Метод surfaceCreated готовит сцену. Он задает несколько атрибутов OpenGL, которые должны инициализироваться только в том случае, когда виджет получает новую рисовальную поверхность. Кроме того, этот метод вызывает і nit Image, который считывает ресурс точечного рисунка (bitmap resource) и сохраняет его в виде двухмерной текстуры. Когда наконец вызывается drawFrame, все уже готово для рисования. А именно: текстура применена к плоской фигуре, вершины которой задаются конструктором в vertexBuf, фаза анимации выбрана, и сцена перерисовывается.

Интересны Предупреждения(Alerts) и Кадры(Frames), показывающие подсказки сгенерированные из собранных данных. Давайте взглянем на мою трассировку и выберем одно из предупреждений сверху:

Предупреждение говорит, что был долгий вызов View#draw(). Нам предоставляют описание, ссылки на документацию и даже видео ссылки по соответствующим вопросам.

Смотря на колонку Кадров(Frames) мы видим ниже обозначение каждого отрисованного кадра, которое окрашено зеленым, желтым и красным для обозначения проблем производительности вовремя отрисовки данного кадра. Давайте выберем один из красных кадров.

Внизу мы видим все предупреждения для этого кадра. Их было 3 и одно из них мы видели ранее. Давайте увеличим этот кадр и раскроем предупреждение внизу: Наполнение вовремя рециркуляции ListView (“Inflation during ListView recycling”).

Мы видим, что эта часть составила 32 миллисекунды, а это выходит за границу 16 миллисекунд- требование для достижения 60 кадров в секунду. Там есть больше временной информации для каждого элемента в ListView в этом кадре- около 6 миллисекунд потрачено на каждый из 5 элементов. Описание помогает нам понять проблему и даже предоставляет решение. На графике вверху мы видим всю визуализацию и даже можем увеличить кусочек наполнения макета для просмотра вьюх, которые заняли много времени при наполнении макета.

Другой пример медленно отрисовывающегося кадра:

После выбора кадра мы можем нажать клавишу m чтобы его подсветить и посмотреть сколько времени заняла эта часть. Посмотрев наверх, мы видим, что отрисовка этого кадра заняла 19 миллисекунд. Развернув предупреждение, увидим, что была задержка расписания (“Scheduling delay”).

Задержка расписания обозначает то, что поток, обрабатывающий эту часть, не становился в очередь процессора длительное время. Следовательно, для завершения работы этого потока понадобилось больше времени. Выбор более длинного отрезка в этом кадре показывает более конкретную информацию:

Настенное время(Wall duration) это время, прошедшее с начала и до конца данной части кадра. Оно так называется потому, что это как смотреть на настенные часы сначала старта потока.

Время процессора это время потраченное процессором на обработку данной части.

Заметна большая разница между настенным временем и временем процессора. Первое заняло 18 миллисекунд, а второе 4 миллисекунды. Что немного странно, так что сейчас подходящее время посмотреть, чем занимался процессор все это время:

Все четыре ядра были достаточно заняты. Выбор одного из потоков показывает, что причина приложение com.udinic.keepbusyapp. В этом случае другое приложение нагружало процессор, отказывая нашему приложению в выделении времени. Этот сценарий обычно временный, так как другие приложения в фоне не часто забирают себе работу процессора, этими потоками может стать другой процесс в вашем приложении или даже ваш главный поток. Так как Системная трассировка(Systrace) инструмент обзора есть предел того, как далеко мы можем зайти. Чтобы найти причину того, что нагружает процессор в нашем приложении мы будем использовать другой инструмент Просмотр Трассировки (Traceview).

Просмотр Трассировки (Traceview)

Просмотр Трассировки инструмент анализа, показывающий время работы каждого метода. Посмотрим, как выглядит файл трассировки:

Посмотрим на различные столбцы:

Имя(Name) - имя и цвет метода на графике.
Включительное время процессора(Inclusive CPU Time) - время обработки процессором данного метода и его детей(всех методов которые он вызывает)
Исключительное время процессора(Exclusive CPU Time) - время обработки процессором данного метода исключая методы вызываемые внутри него.
Включительное/исключительное реальное время(Inclusive / Exclusive Real Time) - время с начала и до конца работы метода. Тоже что и настенное время в Системной трассировке(Systrace).
Вызовы + Рекурсия(Calls+Recursion) - сколько раз был вызван метод и также количество рекурсивных вызовов.
Процессорное/Реальное время на вызов(CPU/Real time per Call) - среднее Процессорное/Реальное время, ушедшее на вызов метода. Другие поля со временем показывают суммарное время всех вызовов метода.
Я открыл приложение, в котором прокрутка работала не плавно и долго. Я запустил трассировку, прокрутил немного и остановил трассировку. Я нашел метод getView() и раскрыл его, вот что я увидел:

Этот метод был вызван 12 раз, процессор потратил около 3мс на каждый вызов, но реальное время для завершения каждого вызова составило 162мс! Конечно это проблема…

Смотря на детей этого метода, мы видим распределение времени между различными методами. Thread.join() занял 98% включительного времени. Мы используем этот метод когда нужно дождаться завершения другого потока. Один из других детей Thread.start(), что наталкивает на мысль что метод getView() запускает поток и ждет его окончания.

Но где этот поток?
Мы не можем увидеть что делал этот поток как дочерний от getView(), так как getView() не делает эту работу напрямую. Чтоб найти это, я искал метод Thread.run(), который вызывается при старте нового потока. Я последовал дальше пока не нашел виновника:

Я выяснил, что время вызова метода BgService.doWork() заняло ~14мс, а у нас их 40! Есть шанс, что каждый getView() вызывает BgService.doWork() больше чем 1 раз и объясняет, почему каждый вызов getView() занимает много времени. Этот метод загружает процессор длительное время. Посмотрев на Исключительное время процессора, мы видим, что он использовал 80% всего времени в трассировке. Сортировка по исключительному времени процессора также хороший путь для поиска загруженных методов в трассировке. Возможно, они причастны к вашей проблеме производительности.

Следующие критические методы, такие как getView(), View#onDraw() и другие, помогают нам найти причины почему наше приложение медленно работает. Но иногда, кое-что другое загружает процессор, забирая ценные циклы процессора, которые могли бы быть потрачены на более плавную отрисовку нашего интерфейса. Сборщик мусора запускается произвольно, очищая неиспользуемые объекты и обычно не влияет на приложение, работающее на переднем плане. Если сборщик мусора запускается слишком часто, это может вызвать тормоза в приложении и возможно в этом наша вина.

Анализ памяти(Memory Profiling)

Android Studio была сильно улучшена в последнее время большим числом инструментов, помогающим найти проблемы производительности. Вкладка Память в окне Android, показывает количество памяти, выделенное в куче с течением времени. Вот как она выглядит:

Когда мы видим на графике небольшие спады, происходит сборка мусора, удаляя неиспользуемые объекты и освобождая место в куче.

В левой части графика доступно два инструмента Дамп Кучи(Heap dump) и Трекер выделения памяти(Allocation Tracker).

Дамп кучи(Heap dump)

Для того чтобы рассмотреть что сейчас находиться в куче, мы можем использовать кнопку дамп кучи. В результате сделается снимок того что сейчас находиться в куче, и покажет это в специальном экране отчета в Андроид Студии:

Слева мы видим гистограмму экземпляров в куче, сгруппированных по имени класса. Для каждого экземпляра есть количество объектов в памяти, размер этих экземпляров (Малый размер(Shallow size)) и размер этих объектов хранящихся в памяти. Последнее говорит нам, сколько памяти может освободиться, если эти экземпляры будут освобождены. Это представление показывает объем памяти нашего приложения, помогая нам определить большие структуры данных и связи объектов. Эта информация может помочь нам создавать более эффективные структуры данных, освобождая связи объектов для уменьшения выделенной памяти и в конце концов - уменьшая объем памяти насколько возможно.

Смотря на гистограмму мы видим, что MemoryActivity имеет 39 экземпляров, что странно для активности. Выбрав один из экземпляров справа, увидим все ссылки на этот экземпляр в Дереве ссылок внизу.

Одна из них часть массива объекта ListenersManager. Смотря на другие экземпляры активности, видим, что все они удерживаются этим объектом. Это объясняет почему единственный объект этого класса занимает так много памяти.

Такие ситуации общепринято называются Утечка Памяти, так как активности были чисто уничтожены и эта неиспользуемая память не может быть убрана сборщиком мусора из за этой ссылки. Мы можем избегать подобных ситуаций, удостоверяясь что на наши объекты не ссылаются другие объекты, которые существуют больше времени. В данной ситуации ListenersManager не должен хранить эту ссылку после того как активность была уничтожена. Решение будет удалить эту ссылку, перед тем как активность будет уничтожена, в методе обратного вызова onDestory().

Утечки памяти и другие большие объекты занимают много места в куче, уменьшая доступную память, в результате сборщик мусора запустит много событий для попытки освобождения памяти. Эти события сборщика мусора занимают процессор, что приводит к ухудшению производительности вашего приложения. Если количество доступной памяти не достаточно для приложения и куча не может больше вырасти, будет более драматический результат - OutOfMemoryException, приводящий падению вашего приложения.

Более продвинутый инструмент Анализатор Памяти Eclipse(Eclipse Memory Analyzer Tool - Eclipse MAT):

Этот инструмент может делать тоже, что и Андроид Студия, а также определять потенциальные утечки памяти и предоставлять улучшенный поиск экземпляров, как например поиск всех Растровых изображений объёмом более 2Мб, или всех пустых объектов Rect .

Другой отличный инструмент библиотека LeakCanary , которая следит, чтобы у ваших объектов не было утечек. Вы получите напоминание о том, что произошло и где.

Трекер выделения памяти (Allocation Tracker)

Трекер выделения памяти запускается/останавливается с помощью других кнопок слева от графика памяти. Он сгенерирует отчет всех экземпляров расположенных в памяти в этот промежуток времени, сгруппированных по классу.

Или по методу:

Также есть хорошая визуализация, показывающие наибольшие экземпляры в памяти. Используя эту информацию, мы можем найти критичные ко времени методы, под которые выделяется слишком много памяти и они могут запустить много событий сборщика мусора. Также мы можем найти много экземпляров одного класса с малым сроком жизни, где мы можем решить использовать очередь объектов для уменьшения общего числа выделений памяти.

Общие подсказки по памяти

Вот несколько быстрых подсказок/руководств, которые я использую, когда пишу код:

Перечисления(Enums) уже горячая тема для обсуждения производительности. Есть Видео об этом, показывающее размер занимаемый перечислениями, и обсуждение этого видео и некоторая информация в нем, вводящая в заблуждение. Занимают ли перечисления больше памяти чем обычные константы? Конечно. Это плохо? Не обязательно. Если вы пишите библиотеку и необходима строгая безопасность типов, это может оправдать использование перечислений вместо других решений, таких как @IntDef . Если у вас просто куча констант, которые могут быть сгруппированы - то может быть не разумно использовать для этого перечисления. Как всегда есть компромисс, который нужно принимать во внимание, когда вы принимаете решение.

Автоупаковка(Auto-boxing) - автоупаковка это автоматическое преобразование из примитивных типов в их объектное представление (например, int -> Integer). Каждый раз, когда примитивный тип «упаковывается» в объектное представление, создается новый объект(шок, я знаю). Если у нас их много - сборщик мусора запускается чаще. Количество происходящих автоупаковок легко пропустить, потому, что для нас это происходит автоматически при присваивании объекту примитивного типа. Решение- старайтесь быть последовательными с этими типами. Если вы повсеместно используете примитивные типы в вашем приложении, постарайтесь избегать их беспричинной автоупаковки. Вы можете использовать инструменты анализа памяти, чтоб найти много объектов представляющих примитивные типы. Также вы можете использовать Просмотр Трассировки и искать Integer.valueOf(), Long.valueOf() и т.д.

HashMap против ArrayMap / Sparse*Array - также как и проблема автоупаковки, использование HashMap требует от нас использовать объекты в качестве ключей. Если мы используем примитивный тип “int” в приложении и он автоупаковыветься в Integer при взаимодействии с HashMap, возможно мы можем просто использовать SparseIntArray. В случае, если нам все же нужны ключи в виде объектов, мы можем использовать класс ArrayMap. Он похож на HashMap, но внутри работает по-другому , более эффективно используя память, но уменьшая скорость работы. Эти два варианта имеют меньший объем памяти, чем HashMap, но для получения элементов или выделения памяти нужно больше времени, чем HashMap. Если у вас меньше 1000 элементов, то при выполнении программы разница не заметна, что делает их не плохим вариантом для ваших нужд использования пар ключ/значение.
Осознание Контекста - как показано ранее, легко создать утечку памяти в активностях. Возможно, вас не удивит что активности наиболее общий случай утечек в Андроид (!). Также эти утечки очень дороги, так как они содержат всю иерархию пользовательского интерфейса, что само по себе может занимать много места. Множество операций на платформе требуют объект Context, и вы обычно посылаете Активность. Убедитесь, что вы понимаете, что происходит с этой Активностью. Если ссылка на нее кэшируется, и этот объект живет дольше самой Активности, без удаления этой ссылки, то у вас появляется утечка памяти.

Избегайте не статических внутренних классов , их инициализация создает неявную ссылку не внешний класс. Если экземпляр объекта внутреннего класса необходим больше времени, чем внешний, то внешний класс останется в памяти, даже если он больше не нужен. Например, создавая не статический класс в классе Активности, наследуемый от AsyncTask, затем запустить AsyncTask, и во время его работы убить активность. Этот AsyncTask будет держать эту активность, пока не закончит свою работу. Решение- не делайте так, объявляйте статический внутренний класс если это необходимо.

Анализ графического процессора(GPU Profiling)

Новое дополнение к Андроид Студии 1.4, инструмент для анализа отрисовки графического процессора.

В окне Андроид, перейдите к вкладе GPU, и вы увидите график показывающий время отрисовки каждого кадра на вашем экране:

Каждая полоска на графике представляет один отрисованный кадр, и цвета представляют различные фазы в процессе отрисовки:

Рисование(синий) - представляет метод View#onDraw(). Эта часть создает/обновляет объекты DisplayList (В Википедии display list (или display file) это последовательность графических команд, которые определяют выходное изображение, преобразовываемые позже в команды OpenGL, которые понимает графический процессор. Большие значения могут быть из-за сложных вьюх, которые требуют больше времени для создания их display lists, или если много вьюх аннулируются в небольшой промежуток времени.

Prepare (purple) - In Lollipop, another thread was added to help the UI Thread render the UI faster. Подготовка (фиолетовый) - в Lollipop добавлен еще один поток, чтоб помочь потоку интерфейса отрисовать интерфейс быстрее. Этот поток называется RenderThread. Он отвечает за преобразование display lists в команды OpenGL и отсылку их в графический процессор. В это время поток UI может продолжить обрабатывать следующий кадр. На этом шаге показывается время за котjрое поток UI передает все ресурсы в поток RenderThread. Если у нас много ресурсов для передачи, например много display lists или они большие, то этот шаг может занять больше времени.

Процесс(красный) - выполнение display lists для создания команд OpenGL. Этот шаг может занять больше времени, если есть на выполнение много display lists либо display lists сложные, из-за того что надо перерисовать много вьюх. Вьюха может быть перерисована из за аннулирования или она появляется при сдвиге наложенной вьюхи.

Выполнение(желтый) - отправка команд OpenGL в графический процессор. Эта часть - блокирующий вызов, так как процессор посылает буфер с этими командами в графический процессор, ожидая получить для следующего кадра чистый буфер. Количество буферов ограничено, и если графический процессор слишком занят, то процессор будет ожидать, когда графический процессор освободится. Поэтому, если на этом шаге мы видим большие значения, возможно это означает, что графический процессор был слишком занят, рисуя наш интерфейс, который возможно слишком сложно отрисовать за небольшое время.

В Marshmallow, было добавлено больше цветов для обозначения новых шагов, таких как Замер/Макет, обработка ввода и другие:

Но перед тем как использовать эту особенность, вы должны включить отрисовку графического процессора из опций разработчика:

Это позволит инструменту использовать команды ADB для получения необходимой информации, таким образом, мы используем:

Adb shell dumpsys gfxinfo Мы можем получить все эти данными и сами сделать график. Команда показывает другую полезную информацию, например количество вьюх в иерархии, размеры всех display lists, и другое. В Marshmallow, мы можем увидеть еще больше статистики.

Если у нас есть автоматическое тестирование интерфейса, мы можем сделать так, чтоб наш билд сервер запускал эту команду после определенных взаимодействий (прокрутка списка, тяжелые анимации и т.д.) и увидеть есть ли изменения в значениях с течением времени, например избыточные кадры. Это может помочь определить проблемы производительности после пуша некоторых коммитов, давая нам время определить проблему перед тем как приложение уйдет в производство. Мы даже можем получить более точную информацию отрисовки, используя ключевое слово “framestats”, как объяснено.

Но это не единственный способ увидеть этот график!

Как вы видели в опциях разработчика в разделе “Profile GPU Rendering”, есть вариант просмотра графика как «Полоски на экране». Его включение, покажет график для каждого окна на нашем экране, вместе с зеленой полосой для определения порога 16 мс.

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

Обозреватель иерархии(Hierarchy Viewer)

Я люблю этот инструмент, и меня печалит, то что многие его вообще не используют!

Используя Обозреватель иерархии, мы можем получить статистику производительности, видеть на экране полную иерархию вьюх и иметь доступ ко всем свойствам вьюх. Вы также можете получить дамп данных темы, увидеть все значения, использованные для каждого атрибута стиля, но это доступно только при запуске Обозревателя иерархии как отдельного приложения, не из Андроид Монитора. Я использую этот инструмент при дизайне моих макетов и когда я хочу их оптимизировать.

В центре мы видим дерево представляющие иерархию вьюх. Иерархия вьюх может быть широкой, но если она слишком глубока(около 10 уровней), ценой могут быть дорогостоящие фазы макета/замера. Каждый раз, когда вьюха замеряется, в View#onMeasure() или когда она располагает все дочерние вьюхи, в View#onLayout(), эти команды распространяются на дочерние вьюхи, которые повторяют те же действия. Некоторые макеты повторяют каждый шаг дважды, например RelativeLayout и некоторые конфигурации LinearLayout, и если они вложенные - количество проходов возрастает по экспоненте.

Внизу справа мы видим «чертеж» нашего макета, отмечающий расположение каждой вьюхи. Мы можем выделить вьюху здесь или в дереве и увидим все её свойства слева.

Когда я создаю макет, иногда я не уверен, почему определенная вьюха закончилась там где она сейчас. Используя этот инструмент, я могу отследить её в дереве, выбрать ее и увидеть где она в окне предосмотра.. Смотря на конечные замеры вьюх на экране, я могу создавать интересные анимации, и использовать эту информацию чтобы аккуратно передвигать вещи. Я могу найти потерянные вьюхи, на которые нечаянно наложились другие вьюхи, и многое другое.

Для каждой вьюхи у нас есть время ее измерения/макета/отрисовки и всех ее дочерних вьюх. Цвета указывают, как представлена данная вьюха в сравнении с другими вьюхами в дереве, это хороший способ найти слабейшую ссылку. Так как мы также видим предосмотр этой вьюхи мы можем обойти дерево по шагам ее создания, находя избыточные шаги, которые мы можем убрать. Одна из таких вещей, которые имеют большое влияние на производительность, называется Наложение(Overdraw).

Наложение(Overdraw)

Как вы видели в разделе Анализ графического процессора(GPU Profiling) - фаза Исполнение, представленная желтым цветом на графике, для завершения может занять больше времени если графическому процессору нужно рисовать много объектов на экране, увеличивая время отрисовки каждого кадра. Наложение происходит, когда мы рисуем одно поверх другого, например желтую кнопку на красном фоне. Графический процессор сначала должен нарисовать красный фон и затем желтую кнопку сверху, делая наложение неизбежным. Если у нас много слоев наложения, это приводит к тому что графический процессор работает больше и будет дальше от цели 16 мс.

Используя пункт «Отладка Наложения GPU» в опциях Разработчика, все наложения будут окрашены для обозначения сложности наложения в этой области. Наложение 1x/2x это нормально, даже некоторые светло красные области это неплохо, но если мы видим слишком много красного на экране, это может быть проблемой. Давайте рассмотрим несколько примеров:

На примере слева список нарисован зеленым, что обычно хорошо, но сверху есть наложение, которое делает его красным и это уже проблема. На примере справа весь список в красном цвете. В обоих случаях есть непрозрачный список с наложением 2x/3x. Эти наложения могут быть в случае, если есть полноэкранный цвет фона в окне содержащем вашу Активность/Фрагмент, список и и каждый элемент списка. Такую проблему можно решить установкой цвета фона только для одного из них.

Примечание: тема по умолчанию объявляет полно экранный фоновый цвет для вашего окна. Если у вашей активности непрозрачный макет, который занимает весь экран, вы можете убрать фон окна, чтобы убрать один слой наложения. Это можно сделать в теме или в коде, вызвав getWindow().setBackgroundDrawable(null) в onCreate().

Используя Обозреватель иерархии, вы может сделать экспорт всех слоев иерархии в PSD файл и открыть его в Photoshop. Рассмотрение различных слоев в Photoshop, раскроет все наложения в макете. Используйте эту информацию для удаления избыточных наложений, и не довольствуйтесь зеленым, добивайтесь синего!

Альфа(Alpha)

Использование прозрачности может повлиять на производительность, чтоб понять почему- посмотрим что происходит при установке значения альфа вьюхе. Рассмотрим следующий макет:

Мы видим макет, содержащий 3 ImageViews, наложенных друг на друга. В прямой/простой реализации, установка альфа, используя setAlpha() команда передается всем дочерним вьюхам, ImageViews в данном случае. Затем, эти ImageViews будут нарисованы с этим значением альфа в буфере кадра. Результат:

Мы не это хотели видеть.

Та как ImageView отрисован с значением альфа, все наложенные изображения смешались. К счастью у операционной системы есть решение этой проблемы. Макет будет скопирован в внеэкранный буфер, альфа будет применена ко всему этому буферу и результат будет скопирован в буфер кадра. Результат:

Но мы заплатили за это цену.

Рисование вьюхи в внеэкранном буфере, перед рисованием ее в буфере кадра, это виртуальное добавление еще одного неопределенного слоя наложения. Операционная система не знает точно когда применять этот подход или прямой подход показанный ранее, таким образом, по умолчанию делает более сложный. Но есть еще способы установить альфа и избежать сложность, добавляемую вне экранным буфером:

  • TextViews - Используйте setTextColor() вместо setAlpha(). Использование альфа канала для цвета текста, приводит к прямому рисованию текста с использованием этого канала.
  • ImageView - Используйте setImageAlpha() вместо setAlpha(). Та же причина, что для TextView.
  • Настраиваемые вьюхи - если настраиваемая вьюха не поддерживает наложенные вюхи, то это сложное поведение нам не подходит. Нет возможности для наших дочерних вьюх смешаться, как показано в примере выше. Переопределяя метод hasOverlappingRendering() вернуть false, мы даем сигнал операционной системе выбрать прямой/простой путь с нашей вьюхой. Таже есть возможность вручную обрабатывать происходящее при установке альфа, переопределяя метод onSetAlpha() возращать true.

Аппаратное ускорение(Hardware Acceleration)

Когда Аппаратное ускорение было представлено в Honeycomb, у нас появилась новая модель рисования для отрисовки нашего приложения на экране. Оно представило структуры DisplayList, которые записывают команды рисования вьюхи для быстрой отрисовки. Но есть еще одна супер особенность, которую разработчики иногда упускают или не используют правильно - слои Вьюхи.

Используя слой вьюхи, мы можем нарисовать вьюхи в внеэкранном буфере(как вы видели ранее, применяя Альфа канал) и обработать как нам необходимо. Эта особенность, в основном, хороша для анимаций, так как мы можем анимировать сложные Вьюхи быстрее. Без слоев анимации вьюхи аннулирует её после изменения анимированного свойства(например, х координаты, масштаб, значение альфа и др.). Для сложных вьюх это аннулирование передается на все дочерние вьюхи, и они затем перерисуют себя, сделав дорогостоящую операцию. Использую слой вьюхи, обеспечиваемый Аппаратными средствами, текстура для нашей вьюхи создается в графическом процессоре. Есть несколько операций, которые мы можем применить к этой текстуре без ее аннулирования, такие как позиция x/y, вращение, альфа и другие. Все это означает, что мы можем анимировать сложную вьюху вообще без ее аннулирования во время анимации! Это делает анимацию более сглаженной. Вот пример кода, как это сделать:
// Используя Object animator view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); objectAnimator.start(); // Используя аниматор Свойства(Property animator) view.animate().translationX(20f).withLayer().start();
Правда, просто?

Да, но нужно помнить несколько вещей при использовании аппаратных слоев:

  • Подчищайте за своей вьюхой - аппаратные слои потребляют место на вашем графическом процессоре, компоненте с ограниченной памятью. Пробуйте и используйте их только в то время, когда они необходимы, например в анимации, и потом очищайте их. В примере с ObjectAnimator выше я применил слушатель для удаления слоя после окончания анимации. В примере аниматора Свойства, я использовал метод withLayers(), который автоматически создает слой вначале и удаляет его в конце анимации.
  • Если вы измените свою вьюху после применения аппаратного слоя, это аннулирует аппаратный слой и отрисует всю вьюху заново в вне экранном буфере. Это произойдет после изменения свойства которое не оптимизировано для аппаратных слоев(сейчас оптимизированы следующие: вращение, масштабирование, x/y, перемещение, точка вращения и альфа. Например, если вы анимируете вьюху с поддержкой аппартного слоя, изменяя цвет фона во время её движения по экрану, приведет к постоянным обновлениям аппаратного слоя. Обновление аппаратного слоя имеет накладные расходы, из за которых возможно его не стоит использовать.

Для второй проблемы, есть способ отобразить эти обновления аппаратного слоя. Используя опции Разработчика, мы можем включить «Показывать обновления аппаратного слоя».

Когда это включено, Вьюха светится зеленым цветом во время обновления аппаратного слоя. Один раз я это использовал, когда мой ViewPager не прокручивался так плавно, как я ожидал. После включения этой опции разработчика, я пошел вперед и прокрутил ViewPager, и вот что я увидел:

Во время всей прокрутки обе страницы были зелеными!

Это значит, что для них был создан аппаратный слой, и страницы были аннулированы во время прокрутки ViewPager. Я обновил прокрутку страниц, используя параллакс эффект на фоне и постепенно анимировал объекты на странице. Однако я не сделал создание аппаратного слоя для страниц ViewPager. После прочтения исходного кода ViewPager, я обнаружил, что после того как пользователь начал прокрутку, аппаратный слой создается для обеих страниц и удаляется после остановки прокрутки.

В то время как имеет смысл создавать аппаратный слой для страниц во время прокрутки, для меня это было плохо. Обычно, эти страницы не меняются во время прокрутки ViewPager, и так как они могут быть достаточно сложны - аппаратные слои помогают отрисовывать их досточно быстро. Это не был кейс для моего приложения, и мне пришлось убрать этот аппаратный уровень, используя тот