Концепции и реализация Jetpack Compose
Начиная с Android 3.0 (уровень API 11), конвейер 2D-рендеринга Android поддерживает аппаратное ускорение, что означает, что все операции рисования, выполняемые на холсте View , используют графический процессор. Из-за увеличения ресурсов, необходимых для включения аппаратного ускорения, ваше приложение будет потреблять больше оперативной памяти.
Аппаратное ускорение включено по умолчанию, если ваш целевой уровень API >= 14, но его также можно включить явно. Если ваше приложение использует только стандартные представления и объекты Drawable , включение его глобально не должно вызывать каких-либо негативных последствий для отрисовки. Однако, поскольку аппаратное ускорение поддерживается не для всех операций 2D-рисования, его включение может повлиять на некоторые ваши пользовательские представления или вызовы отрисовки. Проблемы обычно проявляются в виде невидимых элементов, исключений или неправильно отображаемых пикселей. Для решения этой проблемы Android предоставляет вам возможность включать или отключать аппаратное ускорение на нескольких уровнях. См. раздел «Управление аппаратным ускорением» .
Если ваше приложение выполняет пользовательскую отрисовку, протестируйте его на реальных аппаратных устройствах с включенным аппаратным ускорением, чтобы выявить любые проблемы. В разделе «Поддержка операций отрисовки» описаны известные проблемы с аппаратным ускорением и способы их решения.
См. также OpenGL с использованием API фреймворка и 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 )
Java
getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
Уровень просмотра
Отключить аппаратное ускорение для отдельного представления во время выполнения можно с помощью следующего кода:
Котлин
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() (или один из его вариантов) для любого представления, содержимое которого изменилось. Сообщения об ошибке распространяются по всей иерархии представлений для вычисления областей экрана, которые необходимо перерисовать (область изменения). Затем система Android отрисовывает любое представление в иерархии, которое пересекается с областью изменения. К сожалению, у этой модели отрисовки есть два недостатка:
Во-первых, эта модель требует выполнения большого количества кода при каждом проходе отрисовки. Например, если ваше приложение вызывает
invalidateдля кнопки, и эта кнопка находится поверх другого представления, система Android перерисует представление, даже если оно не изменилось.Вторая проблема заключается в том, что модель отрисовки может скрывать ошибки в вашем приложении. Поскольку система Android перерисовывает представления при пересечении с областью изменения, представление, содержимое которого вы изменили, может быть перерисовано, даже если
invalidateдля него не был вызван. В этом случае вы полагаетесь на то, что другое представление будет признано недействительным для получения правильного поведения. Это поведение может меняться каждый раз, когда вы изменяете свое приложение. Поэтому вам всегда следует вызыватьinvalidateдля ваших пользовательских представлений всякий раз, когда вы изменяете данные или состояние, влияющие на код отрисовки представления.
Модель аппаратного ускорения рисования
Система Android по-прежнему использует команды invalidate и draw для запроса обновлений экрана и отрисовки представлений, но обрабатывает фактическую отрисовку иначе. Вместо немедленного выполнения команд отрисовки система Android записывает их в списки отображения (display lists), которые содержат результат работы кода отрисовки иерархии представлений. Еще одна оптимизация заключается в том, что системе Android нужно записывать и обновлять списки отображения только для представлений, помеченных как измененные вызовом invalidate . Представления, которые не были аннулированы, могут быть перерисованы путем повторной отправки ранее записанного списка отображения. Новая модель отрисовки включает три этапа:
Упразднить иерархию
Запись и обновление списков отображения
Нарисуйте списки отображения
В этой модели нельзя полагаться на то, что метод draw элемента, пересекающего «грязную» область, будет выполнен. Чтобы гарантировать, что система Android запишет список отображаемых элементов элемента, необходимо вызвать invalidate . Если этого не сделать, элемент будет выглядеть так же, как и раньше, даже после внесения изменений.
Использование списков отображения также улучшает производительность анимации, поскольку установка определенных свойств, таких как альфа-канал или вращение, не требует аннулирования целевого представления (это делается автоматически). Эта оптимизация также применима к представлениям со списками отображения (любому представлению, если ваше приложение использует аппаратное ускорение). Например, предположим, что есть LinearLayout , содержащий ListView над Button . Список отображения для LinearLayout выглядит следующим образом:
-
DrawDisplayList(ListView) -
DrawDisplayList(Button)
Предположим, вы хотите изменить прозрачность ListView . После вызова setAlpha(0.5f) для ListView , отображаемый список теперь будет содержать следующее:
SaveLayerAlpha(0.5)DrawDisplayList(ListView)RestoreDrawDisplayList(Button)
Сложный код отрисовки ListView не выполнялся. Вместо этого система обновляла только отображаемый список гораздо более простого LinearLayout . В приложении без включенного аппаратного ускорения код отрисовки как списка, так и его родительского элемента выполняется повторно.
Поддержка операций черчения
При аппаратном ускорении конвейер 2D-рендеринга поддерживает наиболее часто используемые операции рисования Canvas , а также множество менее распространенных операций. Поддерживаются все операции рисования, используемые для рендеринга приложений, поставляемых с Android, виджетов и макетов по умолчанию, а также распространенные расширенные визуальные эффекты, такие как отражения и мозаичные текстуры.
В таблице ниже описан уровень поддержки различных операций на разных уровнях API:
| Первый поддерживаемый уровень API | ||||
| Холст | ||||
| 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 (framebuffer) | 28 | |||
| Шейдер | ||||
| ComposeShader внутри ComposeShader | 28 | |||
| Шейдеры одного типа внутри ComposeShader | 28 | |||
| Локальная матрица в ComposeShader | 18 | |||
Масштабирование холста
Аппаратное ускорение 2D-рендеринга изначально было разработано для поддержки отрисовки без масштабирования, при этом некоторые операции отрисовки значительно ухудшают качество при больших значениях масштаба. Эти операции реализованы в виде текстур, отрисованных в масштабе 1.0 и преобразованных графическим процессором. Начиная с API уровня 28, все операции отрисовки могут масштабироваться без проблем.
В следующей таблице показано, когда были внесены изменения в реализацию для корректной обработки больших масштабов:
| Операция чертежа масштабируется. | Первый поддерживаемый уровень API |
| drawText() | 18 |
| drawPosText() | 28 |
| drawTextOnPath() | 28 |
| Простые формы | 17 |
| Сложные формы | 28 |
| drawPath() | 28 |
| слой тени | 28 |
Если ваше приложение страдает от отсутствия каких-либо из этих функций или ограничений, вы можете отключить аппаратное ускорение только для затронутой части приложения, вызвав 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 . Чтобы получить доступ к этим свойствам, вызовите соответствующий сеттер или геттер. Например, чтобы изменить свойство alpha, вызовите setAlpha . Следующий фрагмент кода показывает наиболее эффективный способ вращения представления в 3D вокруг оси Y:
Котлин
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();
Поскольку аппаратные слои потребляют видеопамять, настоятельно рекомендуется включать их только на время анимации, а затем отключать после её завершения. Этого можно добиться с помощью слушателей анимации:
Котлин
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каждый раз при вызове метода рендеринга. Это заставляет сборщик мусора запускаться чаще, а также обходит кэширование и оптимизации в аппаратном конвейере. - Не следует слишком часто изменять формы.
- Сложные фигуры, контуры и окружности, например, отрисовываются с помощью текстурных масок. Каждый раз, когда вы создаете или изменяете контур, аппаратный конвейер создает новую маску, что может быть затратным процессом.
- Не следует слишком часто изменять растровые изображения.
- При каждом изменении содержимого растрового изображения оно повторно загружается в виде текстуры на графический процессор при следующем отрисовке.
- Используйте альфа-канал с осторожностью.
- При создании полупрозрачного элемента с помощью
setAlpha,AlphaAnimationилиObjectAnimator, он отображается в буфере за пределами экрана, что удваивает необходимую скорость заполнения. При применении альфа-канала к очень большим элементам рекомендуется установить тип слоя элемента вLAYER_TYPE_HARDWARE.