Начиная с Android 3.0 (уровень API 11), конвейер 2D-отрисовки Android поддерживает аппаратное ускорение, то есть все операции рисования, выполняемые на холсте View, используют графический процессор. Поскольку для аппаратного ускорения требуется больше ресурсов, приложение будет потреблять больше оперативной памяти.
Аппаратное ускорение включено по умолчанию, если целевой уровень API равен 14 или выше, но его также можно включить вручную. Если в вашем приложении используются только стандартные представления и Drawables, включение этой функции не должно привести к проблемам с отрисовкой. Однако аппаратное ускорение поддерживается не для всех операций двумерной графики, поэтому его включение может повлиять на некоторые пользовательские представления или вызовы отрисовки. Проблемы обычно проявляются в виде невидимых элементов, исключений или неправильно отрисованных пикселей. Чтобы устранить эту проблему, в Android можно включить или отключить аппаратное ускорение на нескольких уровнях. Подробнее о том, как управлять аппаратным ускорением…
Если ваше приложение выполняет пользовательскую отрисовку, протестируйте его на реальных устройствах с включенным аппаратным ускорением, чтобы выявить возможные проблемы. В разделе Поддержка операций рисования описаны известные проблемы с аппаратным ускорением и способы их решения.
Также ознакомьтесь с разделами OpenGL с API фреймворка и Renderscript.
Как управлять аппаратным ускорением
Вы можете управлять аппаратным ускорением на следующих уровнях:
- Приложение
- Действия
- Окно
- Открыть
На уровне приложения
В файле манифеста Android добавьте следующий атрибут в тег
<application>, чтобы включить аппаратное ускорение для всего приложения:
<application android:hardwareAccelerated="true" ...>
Уровень активности
Если ваше приложение работает некорректно при включенном глобальном аппаратном ускорении, вы можете управлять им для отдельных действий. Чтобы включить или отключить аппаратное ускорение на уровне действия, можно использовать атрибут android:hardwareAccelerated для элемента
<activity>. В следующем примере аппаратное ускорение включено для всего приложения, но отключено для одного объекта activity:
<application android:hardwareAccelerated="true"> <activity ... /> <activity android:hardwareAccelerated="false" /> </application>
Уровень окна
Если вам нужен ещё более точный контроль, вы можете включить аппаратное ускорение для определенного окна с помощью следующего кода:
Kotlin
window.setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED )
Java
getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
Примечание. В настоящее время нельзя отключить аппаратное ускорение на уровне окна.
На уровне представления
Вы можете отключить аппаратное ускорение для отдельного представления во время выполнения с помощью следующего кода:
Kotlin
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Java
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Примечание. В настоящее время нельзя включить аппаратное ускорение на уровне представления. Слои просмотра выполняют не только функцию отключения аппаратного ускорения. Подробнее о том, как посмотреть слои…
Как определить, используется ли аппаратное ускорение для представления
Иногда приложению полезно знать, используется ли в данный момент аппаратное ускорение, особенно для таких объектов, как специальные представления. Это особенно полезно, если ваше приложение выполняет много операций рисования, и не все из них поддерживаются новым конвейером отрисовки.
Проверить, использует ли приложение аппаратное ускорение, можно двумя способами:
View.isHardwareAccelerated()возвращаетtrue, еслиViewприкреплен к окну с аппаратным ускорением.Canvas.isHardwareAccelerated()возвращаетtrue, еслиCanvasиспользует аппаратное ускорение.
Если вам нужно выполнить эту проверку в коде отрисовки, по возможности используйте Canvas.isHardwareAccelerated() вместо View.isHardwareAccelerated(). Если представление прикреплено к окну с аппаратным ускорением, его можно нарисовать с помощью Canvas без аппаратного ускорения. Например, это происходит при отрисовке представления в растровом изображении для кеширования.
Модели для рисования на устройствах Android
Если аппаратное ускорение включено, фреймворк Android использует новую модель отрисовки, в которой для вывода приложения на экран применяются списки отображения. Чтобы понять, как списки отображения могут повлиять на ваше приложение, полезно знать, как Android отрисовывает представления без аппаратного ускорения. В разделах ниже описаны модели рисования на основе программного обеспечения и с аппаратным ускорением.
Модель рисования на основе ПО
В модели отрисовки программного обеспечения представления создаются в два этапа:
- Как аннулировать иерархию
- Как нарисовать иерархию
Когда приложению нужно обновить часть своего интерфейса, оно вызывает invalidate() (или один из его вариантов) для любого представления, контент которого изменился. Сообщения о недействительности распространяются по всей иерархии View, чтобы определить области экрана, которые нужно перерисовать (грязные области). Затем система Android отрисовывает все представления в иерархии, которые пересекаются с грязной областью. К сожалению, у этой модели рисования есть два недостатка:
- Во-первых, эта модель требует выполнения большого количества кода при каждом проходе отрисовки. Например, если ваше приложение вызывает функцию
invalidate()для кнопки, которая находится поверх другого представления, система Android перерисовывает представление, даже если оно не изменилось. - Вторая проблема заключается в том, что модель рисования может скрывать ошибки в вашем приложении. Поскольку система Android перерисовывает представления, когда они пересекаются с грязной областью, представление, контент которого вы изменили, может быть перерисован, даже если для него не был вызван метод
invalidate(). В этом случае вы полагаетесь на то, что другое представление будет признано недействительным, чтобы получить правильное поведение. Это поведение может меняться при каждом изменении приложения. Поэтому всегда вызывайте методinvalidate()для пользовательских представлений, когда изменяете данные или состояние, влияющее на код отрисовки представления.
Примечание. Представления Android автоматически вызывают invalidate() при изменении их свойств, например цвета фона или текста в TextView.
Модель рисования с аппаратным ускорением
Система Android по-прежнему использует invalidate() и draw() для запроса обновлений экрана и отрисовки представлений, но фактическая отрисовка выполняется по-другому. Вместо того чтобы выполнять команды рисования немедленно, система Android записывает их в списки отображения, которые содержат выходные данные кода рисования иерархии View. Ещё одна оптимизация заключается в том, что системе Android нужно записывать и обновлять списки отображения только для представлений, помеченных как грязные вызовом invalidate(). Представления, которые не были признаны недействительными, можно перерисовать, просто повторно отправив ранее записанный список отображения. Новая модель рисования состоит из трех этапов:
- Как аннулировать иерархию
- Как записывать и обновлять списки для показа
- Отрисовка списков
В этой модели нельзя полагаться на то, что метод draw() будет выполнен для представления, пересекающего грязную область. Чтобы система Android записала список отображения представления, необходимо вызвать метод invalidate(). Если этого не сделать, представление будет выглядеть одинаково, даже если оно было изменено.
Кроме того, использование списков объектов позволяет повысить эффективность анимации, поскольку при изменении определенных свойств, например альфа-канала или поворота, не требуется аннулировать целевое представление (это делается автоматически). Эта оптимизация также применяется к представлениям со списками отображения (любое представление, когда ваше приложение использует аппаратное ускорение). Предположим, что на странице LinearLayout есть элемент ListView, расположенный над элементом Button. Список отображения для LinearLayout выглядит следующим образом:
- DrawDisplayList(ListView)
- DrawDisplayList(Button)
Предположим, что вы хотите изменить непрозрачность элемента ListView. После вызова setAlpha(0.5f) для ListView список отображения будет выглядеть так:
- SaveLayerAlpha(0.5)
- DrawDisplayList(ListView)
- Восстановить
- DrawDisplayList(Button)
Не был выполнен сложный код рисования объекта ListView. Вместо этого система обновила только список отображения гораздо более простого LinearLayout. В приложении без аппаратного ускорения код отрисовки списка и его родительского элемента выполняется повторно.
Поддержка операций рисования
При аппаратном ускорении конвейер двухмерной отрисовки поддерживает наиболее часто используемые операции рисования Canvas, а также многие другие операции. Поддерживаются все операции рисования, которые используются для отрисовки приложений, поставляемых с Android, виджетов и макетов по умолчанию, а также распространенные сложные визуальные эффекты, такие как отражения и мозаичные текстуры.
В таблице ниже указано, какие операции поддерживаются на разных уровнях API.
| Первый поддерживаемый уровень API | ||||
| Canvas | ||||
| drawBitmapMesh() (массив цветов) | 18 | |||
| drawPicture() | 23 | |||
| drawPosText() | 16 | |||
| drawTextOnPath() | 16 | |||
| drawVertices() | 29 | |||
| setDrawFilter() | 16 | |||
| clipPath() | 18 | |||
| clipRegion() | 18 | |||
| clipRect(Region.Op.XOR) | 18 | |||
| clipRect(Region.Op.Difference) | 18 | |||
| clipRect(Region.Op.ReverseDifference) | 18 | |||
| clipRect() с поворотом или перспективой | 18 | |||
| Краска | ||||
| setAntiAlias() (для текста) | 18 | |||
| setAntiAlias() (для линий) | 16 | |||
| setFilterBitmap() | 17 | |||
| setLinearText() | ✗ | |||
| setMaskFilter() | ✗ | |||
| setPathEffect() (для линий) | 28 | |||
| setShadowLayer() (не для текста); | 28 | |||
| setStrokeCap() (для линий) | 18 | |||
| setStrokeCap() (для точек); | 19 | |||
| setSubpixelText() | 28 | |||
| Xfermode | ||||
| PorterDuff.Mode.DARKEN (буфер фреймов) | 28 | |||
| PorterDuff.Mode.LIGHTEN (буфер кадра) | 28 | |||
| PorterDuff.Mode.OVERLAY (буфер кадра) | 28 | |||
| Шейдер | ||||
| ComposeShader внутри ComposeShader | 28 | |||
| Шейдеры одного типа в ComposeShader | 28 | |||
| Локальная матрица в ComposeShader | 18 | |||
Масштабирование холста
Конвейер двухмерной отрисовки с аппаратным ускорением был создан в первую очередь для поддержки рисования без масштабирования, при этом качество некоторых операций рисования значительно снижается при более высоких значениях масштаба. Эти операции реализуются как текстуры, нарисованные в масштабе 1.0 и преобразованные с помощью графического процессора. Начиная с уровня API 28, все операции рисования можно масштабировать без проблем.
В таблице ниже указано, когда были внесены изменения, чтобы обеспечить корректную работу с большими объемами данных.| Операция рисования, которую нужно масштабировать. | Первый поддерживаемый уровень API |
| drawText() | 18 |
| drawPosText() | 28 |
| drawTextOnPath() | 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– изменение ориентации слоя в трехмерном пространстве.pivotX,pivotY– изменение исходной точки преобразования слоя.
Эти свойства используются при анимации представления с помощью ObjectAnimator. Чтобы получить доступ к этим свойствам, вызовите соответствующий метод setter или getter. Например, чтобы изменить свойство alpha, вызовите метод setAlpha(). В следующем фрагменте кода показан наиболее эффективный способ повернуть объект View в трехмерном пространстве вокруг оси Y:
Kotlin
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).start()
Java
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start();
Поскольку аппаратные слои потребляют видеопамять, мы настоятельно рекомендуем включать их только на время анимации, а затем отключать. Для этого можно использовать прослушиватели анимации:
Kotlin
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() }
Java
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каждый раз при вызове метода отрисовки. Это приводит к тому, что сборщик мусора запускается чаще, а также обходит кеши и оптимизации в аппаратном конвейере. - Не меняйте фигуры слишком часто
- Сложные фигуры, контуры и круги, например, отрисовываются с помощью масок текстур. Каждый раз, когда вы создаете или изменяете путь, аппаратный конвейер создает новую маску, что может быть дорогостоящим.
- Не изменяйте растровые изображения слишком часто
- Каждый раз, когда вы меняете содержимое растрового изображения, оно снова загружается как текстура GPU при следующем его отрисовывании.
- Используйте альфа-версию с осторожностью
- Если вы делаете представление полупрозрачным с помощью
setAlpha(),AlphaAnimationилиObjectAnimator, оно отрисовывается в буфере за пределами экрана, что вдвое увеличивает требуемую скорость заполнения. При применении альфа-канала к очень большим представлениям рекомендуется задать для слоя представления типLAYER_TYPE_HARDWARE.