Основные понятия и реализация Jetpack Compose
Начиная с Android 3.0 (уровень API 11) конвейер двухмерного рендеринга Android поддерживает аппаратное ускорение. Это означает, что все операции рисования, выполняемые на холсте View, используют графический процессор.
Поскольку для аппаратного ускорения требуется больше ресурсов, ваше приложение будет потреблять больше оперативной памяти.
Аппаратное ускорение включено по умолчанию, если целевой уровень API >=14, но его также можно включить вручную. Если в вашем приложении используются только стандартные представления и объекты Drawable, включение аппаратного ускорения не должно привести к проблемам с отрисовкой. Однако аппаратное ускорение поддерживается не для всех операций двухмерного рисования, поэтому его включение может повлиять на некоторые пользовательские представления или вызовы отрисовки. Проблемы обычно проявляются в виде невидимых элементов, исключений или неправильно отображаемых пикселей. Чтобы решить эту проблему, в Android можно включить или отключить аппаратное ускорение на нескольких уровнях. Подробнее об управлении аппаратным ускорением…
Если в вашем приложении используется пользовательская отрисовка, протестируйте его на реальных устройствах с включенным аппаратным ускорением, чтобы выявить возможные проблемы. В разделе Поддержка операций рисования описаны известные проблемы с аппаратным ускорением и способы их решения.
Также ознакомьтесь со статьями OpenGL with the Framework APIs (OpenGL с API фреймворка) и Renderscript.
Управление аппаратным ускорением
Управлять аппаратным ускорением можно на следующих уровнях:
- Приложение
- Действия
- Окно
- Открыть
Уровень приложения
Чтобы включить аппаратное ускорение для всего приложения, добавьте в файл манифеста Android следующий атрибут в тег <application>:
<application android:hardwareAccelerated="true" ...>
Уровень активности
Если приложение работает некорректно, когда аппаратное ускорение включено на уровне приложения, вы можете управлять им на уровне отдельных действий. Чтобы включить или отключить аппаратное ускорение на уровне действия, используйте атрибут android:hardwareAccelerated для элемента <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 и 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)RestoreDrawDisplayList(Button)
Сложный код рисования ListView не был выполнен. Вместо этого система обновила только список отображения для более простого элемента LinearLayout.
В приложении без аппаратного ускорения код отрисовки списка и его родительского элемента выполняется заново.
Поддержка операций рисования
При аппаратном ускорении конвейер двухмерной отрисовки поддерживает наиболее часто используемые операции рисования 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 (буфер кадра) | 28 | |||
| Шейдер | ||||
| ComposeShader внутри ComposeShader | 28 | |||
| Шейдеры одного типа в ComposeShader | 28 | |||
| Локальная матрица в ComposeShader | 18 | |||
Масштабирование холста
Конвейер двухмерного рендеринга с аппаратным ускорением изначально был создан для поддержки рисования без масштабирования. При более высоких значениях масштаба качество некоторых операций рисования значительно снижалось. Эти операции реализованы как текстуры, нарисованные в масштабе 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:
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при каждом вызове метода отрисовки. Это заставляет сборщик мусора работать чаще, а также обходит кэши и оптимизации в аппаратном конвейере. - Не изменяйте фигуры слишком часто
- Сложные фигуры, контуры и круги отрисовываются с помощью масок текстур. Каждый раз, когда вы создаете или изменяете контур, аппаратный конвейер создает новую маску, что может быть ресурсоемким.
- Не изменяйте битовые карты слишком часто
- Каждый раз, когда вы меняете содержимое растрового изображения, оно снова загружается как текстура графического процессора при следующем его рисовании.
- Используйте альфа-канал с осторожностью
- Если вы делаете представление полупрозрачным с помощью
setAlpha,AlphaAnimationилиObjectAnimator, оно отрисовывается в закадровом буфере, что удваивает требуемую скорость заполнения. При применении альфа-канала к очень большим представлениям рекомендуется задать для типа слоя представления значениеLAYER_TYPE_HARDWARE.