В помощь мобильному разработчику
GPU перерисовка

Улучшение качества разметки на Android

Александр Алексеев

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

В последние время я работаю над повышением производительности некоторых из моих Xamarin-приложений для Android. Одной из преследуемых мною целей является улучшение GPU Overdraw. Проще говоря, речь идёт о том, сколько раз одни и те же пиксели отрисовываются за один кадр. Минимизация этого показателя улучшит продуктивность графического представления на Android, что в итоге обеспечит плавный скроллинг, быструю отрисовку отображаемых данных и в целом будет способствовать тому, чтобы Ваше предложение работало более стабильно. Следующая задача — отслеживать вложенные Layout и выравнивать их, чтобы улучшить качество размещения самого View на экране.

Сейчас действительно имеется немало возможностей для внесения усовершенствований в приложения путём уменьшения GPU Overdraw и повышения скорости отрисовки Views. В этой статье я постараюсь спустить завесу тайны с некоторых из этих возможностей.

 

Выравнивание Layout

Чтобы повысить скорость размещения Views на экране устройства, следует сделать кое-что очень важное: выровнять Layout. Что под этим подразумевается? Давайте обратимся к примеру!

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

screen-shot-2016-09-25-at-12-38-13

 

Конкретная задача с вложенным Layout, который представлен выше, осложняется тем количеством передач метрик, которые необходимо сделать, чтобы выровнить его. Каждый дочерний тег layout_weight должен измерять себя дважды. Другие лейауты также должны себя масштабировать. Представьте, что Вы работайте с более комплексным макетом, где присутствует множество вложенных элементов; это приведёт к чрезмерному количественному показателю передач метрик и замедлит отображение вашего Layout. Особенно в тех случаях, когда вы используете в качестве макета строчный Layout. Это довольно сильно ударит по производительности Вашего приложения и вызовет заметные спады в его работе.

Расположенный выше лейаут может быть выровнен с помощью тега RelativeLayout. Вы заметите, что layout_weight является достаточно мощным инструментом для выравнивания равноразмерных Views, так что некоторые из приёмов работы с ним следует также применять и к RelativeLayout, чтобы добиться схожих результатов. Поскольку производительность в итоге будет гораздо лучше.

 

Как Вы можете видеть, в представленном выше макете используется два дополнительных лейаута, которые применяются для центрирования других views. В качестве альтернативного варианта, вместо RelativeLayout для создания размеров views с процентным отношением можно использовать PercentRelativeLayout из пакета Android.Support.Percent. С его помощью вы можете задавать соотношение сторон, используя этот тег для установки процентов ширины и высоты, а также многого другого. Я рекомендую всегда сохранять ваши лейауты максимально простыми.

Если Вам просто хочется зафиксировать views на верхних частях друг друга, то Вы можете использовать тег FrameLayout, что также отлично скажется на производительности.

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

 

GPU Overdraw

GPU Overdraw — это ещё одна проблема, с которой вы можете столкнуться. Что такое этот Overdraw? Это значение, сообщающее нам о том, сколько раз один и тот же пиксель отрисовывается в течение кадра. Почему это считается плохим? Чем большее число раз одни и те же пиксели отрисовываются, тем большее количество времени тратится понапрасну. Для того чтобы у Вашего приложения была плавная анимации и прокрутка, а также ряд других положительных качеств, оно должно обладать максимально высокой частотой кадров. Наилучшие результаты будут в том случае, если фреймрейт приложения всё время станет держаться на уровне 60 FPS (кадров в секунду). Для этого нам нужно максимально сократить время, затрачивающееся на отрисовку кадра, и сделать этот показатель меньше 16мс. Это не долго! Давайте узнаем, что может предпринять разработчик для улучшения этого показателя.

 

Включение функции демонстрации GPU Overdraw

Вы можете включить на устройстве или эмуляторе специальный прозрачный экран, накладывающий дополнительную информацию на основную, который будет отображать производимый Вашим приложением GPU Overdraw. Вы можете найти эту опцию в настройках в разделе «для разработчиков».

Опции разработчикаОпции разработчика включение GPU OverdrawОпции разработчика функция GPU Overdraw включена

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

Пурпурно-голубой цвет означает, что пиксели образовали Overdraw один раз, если цвет зеленый, то — дважды, когда светло-красный — трижды и если темно-красный, то — 4 раза или больше. Вы также увидите участки, отображающиеся в своем первоначальном цвете, и это означает, что данные конкретные пиксели не образовывали Overdraw. Цель состоит в том, чтобы Overdraw не было вообще. Однако, достичь этого может быть очень трудно, если у Вас на экране не будет отрисовывающегося фона.

 

Удаление бэкграунда из views

Самый простой способ уменьшить Overdraw — это удалить из views фон. Рассмотрим предложенный мною ранее лейаут на этот раз с полным бэкграундом.

И если функция демонстрации GPU Overdraw запущена в отладочном режиме, то мы увидим следующее:

device-2016-09-25-153930

Этот Layout стал красным только потому, что мы добавили бэкграунды к нашим макетам. Удаление этих фонов значительно снизит Overdraw и, таким образом, повысит производительность Вашего приложения.

Простое удаление самого ближнего внешнего фона уменьшает Overdraw, но в этом Layout изменения фона в любом случае не будут заметны.

device-2016-09-25-154045

Два вложенных LinearLayouts используют один и тот же цвет, но что если мы выберем его в качестве темы для фона и удалим из макетов?

device-2016-09-25-154142

Итак, Overdraw снова уменьшился. Вот, как выглядит view компонент без включенного GPU Overdraw.

device-2016-09-25-154241

В этом случае, поскольку сами кнопки обладают фоном, появится некоторый Overdraw и здесь мы с ним ничего не сможем поделать. Тем не менее уже то, что лейаут перестал быть полностью красным и что его цвет сменился на зелёный или светло-синий, имеет весомое значение для вопроса производительности. Особенно в тех случаях, когда лейаут используется в ListView или RecyclerView, или в других view компонентах подобного адаптерного типа, поскольку тогда меньше времени расходуется на строчную отрисовку.

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

С помощью тега android:background:”@null” Вы также можете эффективно удалять фон из тех views компонентов, бэкграунд которых Вас не особенно заботит.

Если Вам действительно необходимы тени, границы и тому подобные элементы, которые могут выступать в качестве фона, то воспользуйтесь в таком случае 9-patches с прозрачностью в областях, которые в любом случае остаются невидимыми. Система Android оптимизирует их отрисовку, и при этом Overdraw удастся избежать.

 

Уменьшение GPU Overdraw в компонентах собственных представлений

У Вас могут быть views компоненты, которые переопределяют метод OnDraw туда, где Вы отрисовываете содержимое для Canvas, которое им выполняется. Здесь Overdraw также имеет значение. Под применением OnDraw подразумевается то, что обычные views компоненты фактически прекращают выполнять при завершении самоотрисовки. Так что здесь следует быть поосторожнее.

Один из способов полностью ликвидировать Overdraw состоит в том, чтобы сперва передать отрисовку к Bitmap, а затем к canvas. Обычно, это называют двойной буферизацией. Используйте этот способ с осторожностью, ведь при этом ресурсопотребление первой отрисовки передаётся к Bitmap, а затем к canvas, и уже он выполняет отрисовку на дисплее.

В приведенном выше коде демонстрируется упрощенная версия этого решения. В результате действительно получается всего 1x Overdraw, если отрисовка производится на бэкграунде. Тем не менее, если у Вас медленный код отрисовки, то вы можете столкнуться с мерцанием, когда вам будет необходимо многократно перерисовать ваш view компонент.

SurfaceView в Android позволяет осуществлять это несколько иначе. Ведь всю буферизацию отрисовки он выполняет в отдельном потоке. Такое решение является допустимым. Когда Вам потребуется отобразить буфер на экране, вызовите Invalidate() или PostInvalidate()

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

 

Сейчас, по-видимому, это можно сделать немного проще. Но тем не менее мне удаётся достичь того, что всякий раз, когда я вручную подаю сигнал с Refresh(), я отменяю все текущие операции направления отрисовки в буфер, ведь я не заинтересован в том, чтобы они выполнялись, поскольку эти данные устарели… PostInvalidate() запускает метод OnDraw всякий раз, когда GPU готов. Далее, я запускаю новую задачу, которая направляет графическое представление в буфер. Когда эта задача выполнена она вызывает PostInvalidate(), сигнализируя тем самым о том, что буфер изменился и что отрисовка произведена. Это является разновидностью двойной буферизации, которая позволяет операциям отрисовки занимать много времени. В результате этого получается плавная прокрутка RecyclerView, которую я использую в этих графиках в виде строк, и Overdraw не наблюдается.

device-2016-09-23-132916 device-2016-09-23-130956

 

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

В общем, сделать надо следующие:

  • Выравнивать Layout для уменьшения циклов обращений
    • Применять RelativeLayout вместо LinearLayout с использованием weights
    • А с компонентами stacked views ещё лучше использовать FrameLayout
  • Удалять бэкграунды, которые в любом случае не показываются
    • Использовать темы бэкграундов там, где это приемлемо
    • android:background:”@null”
  • Использовать 9-patch для границ и теней
  • Уменьшить Overdraw в Ваших собственных OnDraw обращениях

Автор: Tomasz Cielecki (Cheesebaron)
ИсточникОфициальный блог автора
Twitter@Cheesebaron
GitHubCheesebaron

Немного об авторе:
Имеет статус  Xamarin MVP и является членом сообщества Xamarin.

Советую посмотреть его GitHub проекты среди них есть реально полезные компоненты и примеры.

Александр Алексеев
Александр Алексеев

Xamarin – разработчик. Работаю с .NET платформой с 2012 года, программирую в основном с использованием C#.
За это время успел поработать с ASP.NET, Entity Framework, MSSQL, Git

Написать ответ