Аппаратное ускорение

Начиная с Android 3.0 (уровень API 11), конвейер 2D-рендеринга Android поддерживает аппаратное ускорение. Это означает, что все операции рисования, выполняемые на холсте View , используют графический процессор. Из-за увеличения ресурсов, необходимых для включения аппаратного ускорения, ваше приложение будет потреблять больше оперативной памяти.

Аппаратное ускорение включено по умолчанию, если уровень целевого API >=14, но его также можно включить явно. Если ваше приложение использует только стандартные представления и Drawable , его глобальное включение не должно вызвать каких-либо неблагоприятных эффектов при отрисовке. Однако, поскольку аппаратное ускорение поддерживается не для всех операций 2D-рисования, его включение может повлиять на некоторые пользовательские виды или вызовы рисования. Проблемы обычно проявляются в виде невидимых элементов, исключений или неправильно отображаемых пикселей. Чтобы исправить это, Android предоставляет вам возможность включать или отключать аппаратное ускорение на нескольких уровнях. См. Управление аппаратным ускорением .

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

Также см. OpenGL с API-интерфейсами Framework и Renderscript.

Управление аппаратным ускорением

Вы можете управлять аппаратным ускорением на следующих уровнях:

  • Приложение
  • Активность
  • Окно
  • Вид

Уровень приложения

В файле манифеста Android добавьте следующий атрибут в тег <application> , чтобы включить аппаратное ускорение для всего приложения:

<application android:hardwareAccelerated="true" ...>

Уровень активности

Если ваше приложение не работает должным образом при глобальном включении аппаратного ускорения, вы также можете контролировать его для отдельных действий. Чтобы включить или отключить аппаратное ускорение на уровне активности, вы можете использовать атрибут android:hardwareAccelerated для элемента <activity> . В следующем примере аппаратное ускорение включается для всего приложения, но отключается для одного действия:

<application android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

Уровень окна

Если вам нужен еще более детальный контроль, вы можете включить аппаратное ускорение для данного окна с помощью следующего кода:

Котлин

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Ява

getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Примечание . В настоящее время вы не можете отключить аппаратное ускорение на уровне окна.

Уровень просмотра

Вы можете отключить аппаратное ускорение для отдельного представления во время выполнения с помощью следующего кода:

Котлин

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Ява

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

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

Определите, имеет ли представление аппаратное ускорение

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

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

Если вам необходимо выполнить эту проверку в коде рисования, используйте Canvas.isHardwareAccelerated() вместо View.isHardwareAccelerated() когда это возможно. Когда представление прикреплено к окну с аппаратным ускорением, его все равно можно отрисовать с помощью холста с неаппаратным ускорением. Это происходит, например, при преобразовании представления в растровое изображение для целей кэширования.

Модели рисования Android

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

Программная модель чертежа

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

  1. Аннулировать иерархию
  2. Нарисуйте иерархию

Всякий раз, когда приложению необходимо обновить часть своего пользовательского интерфейса, оно вызывает invalidate() (или один из его вариантов) для любого представления, в котором изменилось содержимое. Сообщения о недействительности распространяются по всей иерархии представлений для вычисления областей экрана, которые необходимо перерисовать (грязная область). Затем система Android рисует любое представление в иерархии, которое пересекается с «грязной» областью. К сожалению, у этой модели рисования есть два недостатка:

  • Во-первых, эта модель требует выполнения большого количества кода на каждом проходе отрисовки. Например, если ваше приложение вызывает invalidate() для кнопки, и эта кнопка находится поверх другого представления, система Android перерисовывает представление, даже если оно не изменилось.
  • Вторая проблема заключается в том, что модель рисования может скрывать ошибки в вашем приложении. Поскольку система Android перерисовывает представления, когда они пересекают «грязную» область, представление, содержимое которого вы изменили, может быть перерисовано, даже если для него не была вызвана invalidate() . Когда это происходит, вы полагаетесь на то, что другое представление станет недействительным, чтобы получить правильное поведение. Это поведение может меняться каждый раз, когда вы изменяете свое приложение. По этой причине вам всегда следует вызывать invalidate() в ваших пользовательских представлениях всякий раз, когда вы изменяете данные или состояние, которые влияют на код рисования представления.

Примечание . Представления Android автоматически вызывают invalidate() при изменении их свойств, таких как цвет фона или текст в TextView .

Модель рисования с аппаратным ускорением

Система Android по-прежнему использует invalidate() и draw() для запроса обновлений экрана и рендеринга представлений, но фактическое рисование обрабатывается по-другому. Вместо немедленного выполнения команд рисования система Android записывает их в списки отображения, которые содержат выходные данные кода рисования иерархии представлений. Другая оптимизация заключается в том, что системе Android необходимо записывать и обновлять списки отображения только для представлений, помеченных как «грязные» вызовом invalidate() . Представления, которые не были признаны недействительными, можно перерисовать, просто перевыпустив ранее записанный список отображения. Новая модель чертежа содержит три этапа:

  1. Аннулировать иерархию
  2. Запись и обновление списков отображения
  3. Нарисуйте списки отображения

С помощью этой модели вы не можете полагаться на то, что представление, пересекающее «грязную» область, выполнит метод draw() . Чтобы гарантировать, что система Android записывает список отображения представления, вы должны вызвать invalidate() . Если вы забудете это сделать, представление будет выглядеть одинаково даже после его изменения.

Использование списков отображения также повышает производительность анимации, поскольку установка определенных свойств, таких как альфа или вращение, не требует отмены целевого представления (это делается автоматически). Эта оптимизация также применима к представлениям со списками отображения (любым представлениям, если ваше приложение имеет аппаратное ускорение). Например, предположим, что существует LinearLayout , который содержит ListView над Button . Список отображения LinearLayout выглядит следующим образом:

  • DrawDisplayList(ListView)
  • DrawDisplayList (Кнопка)

Предположим теперь, что вы хотите изменить непрозрачность ListView . После вызова setAlpha(0.5f) в ListView список отображения теперь содержит следующее:

  • СохранитьСлойАльфа(0.5)
  • DrawDisplayList(ListView)
  • Восстановить
  • DrawDisplayList (Кнопка)

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

Поддержка операций рисования

При аппаратном ускорении конвейер 2D-рендеринга поддерживает наиболее часто используемые операции рисования Canvas , а также многие менее используемые операции. Поддерживаются все операции рисования, используемые для рендеринга приложений, поставляемых с Android, виджеты и макеты по умолчанию, а также распространенные расширенные визуальные эффекты, такие как отражения и мозаичные текстуры.

В следующей таблице описан уровень поддержки различных операций на уровнях API:

Первый поддерживаемый уровень API
Холст
drawBitmapMesh() (массив цветов) 18
рисоватьPicture() 23
рисоватьПостекст() 16
рисоватьТекстОнПат() 16
drawVertices() 29
установитьDrawFilter() 16
клипПат() 18
клипРегион() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() с вращением/перспективой 18
Краска
setAntiAlias() (для текста) 18
setAntiAlias() (для строк) 16
установитьФильтрБитмап() 17
установитьЛинейныйТекст()
setMaskFilter()
setPathEffect() (для строк) 28
setShadowLayer() (кроме текста) 28
setStrokeCap() (для строк) 18
setStrokeCap() (для очков) 19
setSubpixelText() 28
Xфермод
PorterDuff.Mode.DARKEN (кадровый буфер) 28
PorterDuff.Mode.LIGHTEN (кадровый буфер) 28
PorterDuff.Mode.OVERLAY (буфер кадров) 28
Шейдер
ComposeShader внутри ComposeShader 28
Шейдеры того же типа внутри ComposeShader 28
Локальная матрица в ComposeShader 18

Масштабирование холста

Конвейер 2D-рендеринга с аппаратным ускорением был сначала создан для поддержки немасштабированного рисования, при этом некоторые операции рисования значительно ухудшают качество при более высоких значениях масштаба. Эти операции реализованы в виде текстур, нарисованных в масштабе 1.0, преобразованных графическим процессором. Начиная с уровня API 28, все операции рисования можно без проблем масштабировать.

В следующей таблице показано, когда реализация была изменена для правильной обработки больших масштабов:
Операция рисования для масштабирования Первый поддерживаемый уровень API
рисоватьТекст() 18
рисоватьПостекст() 28
нарисоватьТекстОнПат() 28
Простые формы* 17
Сложные формы* 28
DrawPath() 28
Слой тени 28

Примечание . «Простыми» фигурами являются drawRect() , drawCircle() , drawOval() , drawRoundRect() и drawArc() (с useCenter=false), выдаваемые с помощью Paint, который не имеет PathEffect и не имеет содержат соединения не по умолчанию (через setStrokeJoin() / setStrokeMiter() ). Другие экземпляры этих команд рисования относятся к категории «Сложные» на приведенной выше диаграмме.

Если на ваше приложение влияет какая-либо из этих недостающих функций или ограничений, вы можете отключить аппаратное ускорение только для затронутой части вашего приложения, вызвав setLayerType(View.LAYER_TYPE_SOFTWARE, null) . Таким образом, вы по-прежнему сможете использовать преимущества аппаратного ускорения повсюду. См. раздел Управление аппаратным ускорением для получения дополнительной информации о том, как включать и отключать аппаратное ускорение на разных уровнях вашего приложения.

Просмотр слоев

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

Начиная с Android 3.0 (уровень API 11), у вас есть больше возможностей контролировать, как и когда использовать слои с помощью метода View.setLayerType() . Этот API принимает два параметра: тип слоя, который вы хотите использовать, и необязательный объект Paint , который описывает, как следует скомпоновать слой. Параметр Paint можно использовать для применения к слою цветовых фильтров, специальных режимов наложения или непрозрачности. Представление может использовать один из трех типов слоев:

  • LAYER_TYPE_NONE : представление отображается нормально и не поддерживается внеэкранным буфером. Это поведение по умолчанию.
  • LAYER_TYPE_HARDWARE : представление аппаратно визуализируется в аппаратную текстуру, если приложение имеет аппаратное ускорение. Если приложение не имеет аппаратного ускорения, этот тип уровня ведет себя так же, как LAYER_TYPE_SOFTWARE .
  • LAYER_TYPE_SOFTWARE : вид программно преобразуется в растровое изображение.

Тип слоя, который вы используете, зависит от вашей цели:

  • Производительность : используйте тип аппаратного слоя для преобразования представления в аппаратную текстуру. После того как представление визуализируется в слой, его код рисования не нужно выполнять до тех пор, пока представление не вызовет invalidate() . Некоторые анимации, такие как альфа-анимация, можно затем применить непосредственно к слою, что очень эффективно для графического процессора.
  • Визуальные эффекты . Используйте тип слоя аппаратного или программного обеспечения и Paint , чтобы применить к виду специальные визуальные эффекты. Например, вы можете нарисовать черно-белое представление, используя ColorMatrixColorFilter .
  • Совместимость : используйте тип слоя программного обеспечения, чтобы принудительно отображать представление в программном обеспечении. Если у представления с аппаратным ускорением (например, если все ваше приложение аппаратно ускорено) возникают проблемы с рендерингом, это простой способ обойти ограничения конвейера аппаратного рендеринга.

Просмотр слоев и анимации

Аппаратные уровни могут обеспечить более быструю и плавную анимацию, если ваше приложение оснащено аппаратным ускорением. Запуск анимации со скоростью 60 кадров в секунду не всегда возможен при анимации сложных видов, требующих большого количества операций рисования. Эту проблему можно облегчить, используя аппаратные слои для рендеринга вида в аппаратную текстуру. Аппаратную текстуру затем можно использовать для анимации представления, устраняя необходимость постоянного перерисования представления во время анимации. Представление не перерисовывается, пока вы не измените свойства представления, вызывающие invalidate() , или если вы не вызовете invalidate() вручную. Если вы запускаете анимацию в своем приложении и не получаете желаемых плавных результатов, рассмотрите возможность включения аппаратных слоев в ваших анимированных представлениях.

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

  • alpha : изменяет непрозрачность слоя.
  • x , y , translationX , translationY : Изменяет положение слоя.
  • scaleX , scaleY : Изменяет размер слоя.
  • rotation , rotationX , rotationY : изменяет ориентацию слоя в 3D-пространстве.
  • pivotX , pivotY : Изменяет начало преобразований слоя.

Эти свойства — это имена, используемые при анимации представления с помощью ObjectAnimator . Если вы хотите получить доступ к этим свойствам, вызовите соответствующий метод установки или получения. Например, чтобы изменить свойство альфа, вызовите setAlpha() . В следующем фрагменте кода показан наиболее эффективный способ поворота трехмерного изображения вокруг оси Y:

Котлин

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).start()

Ява

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

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

Котлин

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).apply {
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            view.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

Ява

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

Дополнительные сведения об анимации свойств см. в разделе Анимация свойств .

Советы и рекомендации

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

Уменьшите количество просмотров вашего приложения
Чем больше просмотров должна получить система, тем медленнее она будет работать. Это также относится и к конвейеру программного рендеринга. Сокращение просмотров — один из самых простых способов оптимизировать пользовательский интерфейс.
Избегайте перерисовки
Не рисуйте слишком много слоев друг на друге. Удалите все виды, которые полностью закрыты другими непрозрачными представлениями поверх них. Если вам нужно нарисовать несколько слоев, наложенных друг на друга, рассмотрите возможность объединения их в один слой. Хорошее эмпирическое правило при использовании современного оборудования — не рисовать более чем в 2,5 раза больше пикселей на экране за кадр (прозрачных пикселей в растровом изображении!).
Не создавайте объекты рендеринга в методах рисования.
Распространенной ошибкой является создание новой Paint или нового Path каждый раз, когда вызывается метод рендеринга. Это заставляет сборщик мусора запускаться чаще, а также обходит кэши и оптимизации в аппаратном конвейере.
Не изменяйте фигуры слишком часто
Например, сложные формы, пути и круги визуализируются с использованием текстурных масок. Каждый раз, когда вы создаете или изменяете путь, аппаратный конвейер создает новую маску, что может быть дорогостоящим.
Не изменяйте растровые изображения слишком часто
Каждый раз, когда вы меняете содержимое растрового изображения, оно снова загружается как текстура графического процессора при следующем его рисовании.
Используйте альфу с осторожностью
Когда вы делаете представление полупрозрачным с помощью setAlpha() , AlphaAnimation или ObjectAnimator , оно отображается во внеэкранном буфере, что удваивает требуемую скорость заполнения. При применении альфа-канала к очень большим видам рассмотрите возможность установки типа слоя вида на LAYER_TYPE_HARDWARE .