Jetpack Compose — современный фреймворк для разработки интерфейсов приложений для операционной системы Android. Он позволяет гибко и интуитивно понятно создавать компоненты, которые могут использоваться одновременно в нескольких экранах и даже проектах.
Так как фреймворк достаточно новый и не является индустриальным стандартом, перед внедрением необходимо знать о таких вещах в Compose, как производительность, тестирование, архитектурные решения и время сборки.
В этой работе был проведен анализ производительности композиции экрана в Jetpack Compose. Основным вопросом, на который необходимо дать ответ в данной работе является вопрос «Какое влияние оказывает использование функций Compose вместо XML на время загрузки и производительность?»
В этой статье будет рассмотрено время с момента запуска экрана до момента, когда содержимое становится видимым пользователю.
Существует множество вариантов сборки с помощью Jetpack Compose:
- Создать интерфейс целиком с помощью Compose;
- В качестве простых элементов использовать XML, а сложные заменить на составные функции;
- Заменить отдельные элементы экрана составными функциями;
- Используя режим отладки и R8 или отключив их.
Было создано тестовое приложение, которое отображало список. Для этого начального раунда мы сосредоточились исключительно на отображении списка из 50 элементов. Элементы включают в себя кнопку и случайный текст.
Чтобы получить четкое представление о влиянии всех вариантов, были проведены замеры для различных конфигураций. В 4 конфигурациях, перечисленных ниже, R8 включен, а отладка отключена:
- Интерфейс с помощью Compose через setContent{};
- XML с ComposeView;
- XML c RecyclerView, который содержит элементы Compose;
- Чистый XML (Без Compose).
Также были исследованы такие конфигурации, как:
- Интерфейс с помощью Compose с отключенным R8 и включенной отладкой;
- Интерфейс с помощью Compose с отключенным R8 и отключенной отладкой;
- Чистый XML с отключенным R8 и включенной отладкой.
Точкой отсчета необходимо считать, когда интерфейс начинает рендериться. Для тестового приложения этой точкой является метод onCreate() непосредственно перед вызовом super.onCreate(). После этого есть две точки, где необходимо сделать замеры — когда onResume завершится, и после того, как экран полностью отрисуется.
Жизненный цикл активности может быть переопределен напрямую, но для измерения визуализации представления потребуется добавить глобальный ViewTreeObserver в базовое представление нашего фрагмента и отслеживать время до последнего вызова onDraw. Android Profiler показывает, как это выглядит на рисунках 1-2.
Рисунок 1. Отображение методов при использовании XML
Рисунок 2. Отображение методов при использовании Compose
Каждый из этих тестов был выполнен 10 раз, чтобы дать представление не только о начальном времени рендеринга, но и о последующем времени рендеринга.
В качестве первого эксперименты были проведены многократные замеры рендера экрана. Результат отображен на рисунке 3.
Рисунок 3. Диаграмма времени рендера
R8 и режим отладки существенно влияют на время рендеринга Jetpack Compose. В каждом эксперименте сборкам с отключенным R8 и включенной отладкой требовалось более чем в два раза больше времени для визуализации, чем сборкам без них. На самом медленном устройстве R8 ускорил рендеринг более чем на полсекунды, а отключение возможности отладки ускорило его еще на полсекунды.
ComposeView и setContent{} заняли почти одинаковое количество времени. Разница между ними была незначительной и, по-видимому, указывает на тот факт, что производительность ComposeView внутри корневого объекта XML ничем не хуже, чем в ComposeView, используемый в качестве корневого объекта.
ComposeView внутри RecyclerView был самым медленным. Это произошло по той причине, что переход к ComposeView в XML имеет свои проблемы, поэтому чем меньше составных элементов используется на XML экране, тем лучше.
XML был быстрее при рендеринге, чем Compose. На разных устройствах в каждом сценарии для визуализации Compose требовалось примерно на 33% больше времени, чем XML.
Первый запуск всегда занимал больше времени, чем последующие запуски. На отображение первого экрана всегда уходило почти в два раза больше времени, чем на последующие экраны.
Необходимо обратить внимание на еще один важный момент. Рендеринг ComposeView при холодном (первом) запуске был медленнее, чем рендеринг ComposeView при горячем запуске.
Рисунок 4. Отображение при отрисовках интерфейса в Compose
Из графика на рисунке 4 видно, что первый рендеринг производит гораздо больше действий, чем последующие рендеры. С одной стороны, это говорит о том, что Compose хорошо справляется с рендерингом и обновлением контента. Однако это также говорит нам о том, что перед первоначальным рендерингом необходимо выполнить некоторую дополнительную работу, и часть этой работы занимает значительное количество времени.
Итак, это подводит к следующему вопросу: можно ли сократить начальное время рендеринга? Чтобы ответить на этот вопрос, был проведен еще один эксперимент - на этот раз был добавлен промежуточный экран и сначала совершен переход к нему. Эксперимент, изображенный на рисунке 5, проведен как с экраном, использующим Compose, так и с экраном без Compose, чтобы убедиться, что улучшение было вызвано не только его наличием.
Рисунок 5. Диаграмма времени рендера для различных сценариев
Оба промежуточных экрана оказали положительное влияние на экран. Похоже, что первое действие, запущенное в приложении, несет с собой некоторые временные затраты, в данном случае приводящие к дополнительной задержке около 250 миллисекунд. А наличие промежуточного Compose экрана значительно сокращает время запуска последующих Compose экранов.
Первый раз, когда мы запускаем композицию, фреймворк требует определенных временных затрат, даже если этим запуском является не запуск приложения, а переход на экран с Compose. Библиотека имеет область, которая инициализируется при первом рендеринге, что значительно увеличивает время отрисовки.
Повторный рендеринг одних и тех же компонентов выполняется намного быстрее, чем отрисовка новых компонентов. Даже при изменении содержимого списка, Compose выполнял гораздо более быструю повторную визуализацию, чем начальная визуализация. Фреймворк использует один и тот же контекст во всем приложении и разумно расходует ресурсы как при повторном использовании компонентов, так и при повторной инициализации своих представлений.
Даже с этим небольшим недостатком Compose по-прежнему остается отличным выбором для большинства команд Android по причине производительности разработчиков, повторного использования кода и декларативного подхода. Проведенный анализ позволяет лучше осознать некоторые незначительные недостатки использования Compose и помогает настроить приложение так, чтобы оно было быстрее и производительнее.
Список литературы
- Концепции Jetpack Compose, которые должен знать каждый разработчик. AppTractor // [Электронный ресурс]. — Режим доступа: https://apptractor.ru/info/articles/kontseptsii-jetpack-compose-kotorye-dolzhen-znat-kazhdyy-razrabotchik.html (дата обращения 08.01.2023).
- Обзор Android Jetpack Compose. Русские Блоги //: [Электронный ресурс]. — Режим доступа: https://russianblogs.com/article/75801402833/ (дата обращения 09.01.2023).
- Jetpack Compose. Хабр // [Режим доступа]. — Режим доступа: https://habr.com/ru/post/451112/ (дата обращения 10.01.2023).