То, как вы управляете иерархией объектов View, может значительно повлиять на производительность вашего приложения. В этой статье рассказывается, как определить, замедляет ли иерархия View работу приложения, и предлагаются некоторые стратегии решения возможных проблем.
В этой статье рассказывается, как улучшить макеты на основе View. Информацию о том, как повысить производительность Jetpack Compose, можно найти в этой статье.
Эффективность макета и измерений
Конвейер рендеринга включает этап макета и измерения, на котором система соответствующим образом позиционирует соответствующие элементы в иерархии View. На этапе измерения определяются размеры и границы объектов View. Макет определяет, где на экране будут расположены объекты View.
Оба этих этапа конвейера требуют небольших затрат на каждое представление или макет, которые они обрабатывают. В большинстве случаев эти затраты минимальны и не оказывают заметного влияния на производительность. Однако оно может быть больше, если приложение добавляет или удаляет объекты View, например когда объект RecyclerView использует их повторно. Стоимость также может быть выше, если объекту View необходимо учитывать изменение размера, чтобы соответствовать ограничениям. Например, если приложение вызывает SetText() для объекта View, который переносит текст, может потребоваться изменить размер View.
Если такие случаи занимают слишком много времени, они могут помешать рендерингу кадра в течение разрешенных 16 мс, что может привести к выпадению кадров и сделать анимацию прерывистой.
Поскольку эти операции нельзя перенести в рабочий поток (приложение должно обрабатывать их в основном потоке), лучше всего оптимизировать их так, чтобы они занимали как можно меньше времени.
Управление сложными макетами
Макеты Android позволяют вкладывать объекты интерфейса в иерархию View. Такое вложение также может привести к затратам на макет. Когда приложение обрабатывает объект для макета, оно также выполняет тот же процесс для всех дочерних элементов макета.
Для сложного макета иногда затраты возникают только при первом вычислении макета системой. Например, когда приложение повторно использует сложный элемент списка в объекте RecyclerView, системе нужно разместить все объекты. В другом примере незначительные изменения могут распространяться по цепочке к родителю, пока не достигнут объекта, который не влияет на размер родителя.
Часто макет долго загружается из-за того, что иерархии объектов View вложены друг в друга. Каждый вложенный объект макета увеличивает стоимость этапа макета. Чем проще иерархия, тем быстрее завершится этап макета.
Для создания ConstraintLayout рекомендуется использовать редактор макетов, а не RelativeLayout или LinearLayout, поскольку это обычно эффективнее и позволяет уменьшить вложенность макетов. Однако для простых макетов, которые можно создать с помощью FrameLayout, мы рекомендуем использовать FrameLayout.
Если вы используете класс RelativeLayout, то можете добиться того же эффекта с меньшими затратами, используя вместо него вложенные невзвешенные представления LinearLayout. Однако если вы используете вложенные взвешенные представления LinearLayout, стоимость макета будет намного выше, поскольку потребуется несколько проходов макета, как описано в следующем разделе.
Мы также рекомендуем использовать RecyclerView вместо ListView, поскольку он может повторно использовать макеты отдельных элементов списка, что более эффективно и может улучшить производительность прокрутки.
Двойное налогообложение
Обычно фреймворк выполняет этап макета или измерения за один проход. Однако в некоторых сложных случаях макета фреймворку может потребоваться несколько раз выполнить итерацию по частям иерархии, для разрешения которых требуется несколько проходов, прежде чем окончательно позиционировать элементы. Необходимость выполнять более одной итерации макета и измерения называется двойным налогообложением.
Например, если вы используете контейнер RelativeLayout, который позволяет размещать объекты View относительно других объектов View, фреймворк выполняет следующую последовательность действий:
- Выполняет проход макета и измерения, во время которого фреймворк вычисляет положение и размер каждого дочернего объекта на основе запроса каждого дочернего объекта.
- Использует эти данные с учетом веса объектов, чтобы определить правильное положение связанных представлений.
- Выполняет второй проход макета, чтобы окончательно определить позиции объектов.
- Переход к следующему этапу процесса отрисовки.
Чем больше уровней в иерархии View, тем выше вероятность снижения производительности.
Как упоминалось ранее, ConstraintLayout обычно эффективнее других макетов, за исключением FrameLayout. Он менее подвержен многократным проходам макета и во многих случаях устраняет необходимость вложенных макетов.
Контейнеры, отличные от RelativeLayout, также могут увеличить двойное налогообложение. Пример:
- Если вы сделаете
LinearLayoutгоризонтальным, это может привести к двойному проходу макета и измерения. Двойной проход макета и измерения также может произойти в вертикальной ориентации, если вы добавитеmeasureWithLargestChild. В этом случае фреймворку может потребоваться выполнить второй проход, чтобы определить правильные размеры объектов. GridLayoutтакже позволяет использовать относительное позиционирование, но обычно избегает двойного налогообложения за счет предварительной обработки позиционных отношений между дочерними представлениями. Однако если в макете используются веса или заполнение с помощью классаGravity, то преимущества предварительной обработки теряются, и фреймворку может потребоваться выполнить несколько проходов, если контейнер являетсяRelativeLayout.
Несколько проходов макета и измерения не обязательно являются нагрузкой на производительность. Однако они могут стать обузой, если находятся в неправильном месте. Будьте осторожны, если для вашего контейнера выполняется одно из следующих условий:
- Это корневой элемент в иерархии View.
- У него глубокая иерархия View.
- На экране может быть много экземпляров этого объекта, как и дочерних элементов в объекте
ListView.
Как диагностировать проблемы с иерархией View
Производительность макета – это сложная проблема со множеством аспектов. Следующие инструменты помогут вам определить, где возникают узкие места в производительности. Некоторые инструменты предоставляют менее точную информацию, но могут давать полезные подсказки.
Perfetto
Perfetto – это инструмент, который предоставляет данные о производительности. Файлы трассировки Android можно открыть в Perfetto UI.
Запись времени работы GPU
Инструмент Профилирование отрисовки GPU, доступный на устройствах с Android 6.0 (уровень API 23) и более поздними версиями, позволяет получить точную информацию о проблемах с производительностью. Этот инструмент позволяет увидеть, сколько времени занимает этап макета и измерения для каждого кадра рендеринга. Эти данные помогут вам диагностировать проблемы производительности во время выполнения и определить, какие проблемы с макетом и измерением необходимо решить.
На графике, который создается при профилировании отрисовки графическим процессором, время макета обозначается синим цветом. Подробнее о том, как использовать этот инструмент…
Lint
Инструмент Lint в Android Studio поможет вам выявить неэффективные элементы в иерархии View. Чтобы использовать этот инструмент, выберите Analyze (Анализ) > Inspect Code (Проверить код), как показано на рисунке 1.
Информация о различных элементах макета появится в разделе Android > Lint > Performance (Android > Lint > Производительность). Чтобы посмотреть подробные сведения, нажмите на элемент. Информация появится на панели в правой части экрана. На рисунке 2 показан пример развернутой информации.
Если нажать на элемент, на панели справа появятся связанные с ним проблемы.
Чтобы узнать больше о конкретных темах и проблемах в этой области, ознакомьтесь с документацией по Lint.
Инспектор макетов
Инспектор макетов в Android Studio позволяет визуализировать иерархию View в приложении. Это хороший способ навигации по иерархии вашего приложения, обеспечивающий четкое визуальное представление родительской цепочки конкретного представления и позволяющий вам проверять макеты, которые создает ваше приложение.
Представления, которые предлагает инспектор макетов, также могут помочь выявить проблемы с производительностью, возникающие из-за двойного налогообложения. Он также может помочь вам выявить глубокие цепочки вложенных макетов или области макетов с большим количеством вложенных дочерних элементов, что может быть источником затрат на производительность. В таких случаях этапы макета и измерения могут быть дорогостоящими и привести к проблемам производительности.
Подробнее о том, как отлаживать макет с помощью инспектора макетов и проверки макета…
Как устранить проблемы с иерархией View
Основная концепция решения проблем с производительностью, возникающих из-за иерархий View, может быть сложной на практике. Чтобы иерархии View не снижали производительность, нужно сделать их более плоскими и уменьшить двойное налогообложение. В этом разделе обсуждаются стратегии для достижения этих целей.
Удалите лишние вложенные макеты
ConstraintLayout
– это библиотека Jetpack с большим количеством различных механизмов для позиционирования представлений в макете. Это позволяет не вкладывать один ConstaintLayout в другой и упрощает иерархию View. С помощью ConstraintLayout обычно проще выравнивать иерархии, чем с помощью других типов макетов.
Разработчики часто используют больше вложенных макетов, чем необходимо. Например, контейнер RelativeLayout может содержать один дочерний элемент, который также является контейнером RelativeLayout. Такое вложение является избыточным и добавляет ненужные затраты в иерархию View. Lint может отметить эту проблему, сократив время отладки.
Принять, объединить или включить
Частая причина избыточных вложенных макетов – тег <include>. Например, вы можете определить макет многократного использования следующим образом:
<LinearLayout> <!-- some stuff here --> </LinearLayout>
Затем вы можете добавить тег <include>, чтобы добавить в родительский контейнер следующий элемент:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/app_bg" android:gravity="center_horizontal"> <include layout="@layout/titlebar"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello" android:padding="10dp" /> ... </LinearLayout>
Предыдущий include без необходимости вкладывает первый макет во второй макет.
Чтобы избежать этой проблемы, используйте тег <merge>. Подробнее о теге <merge>…
Используйте более дешевый макет
Возможно, вам не удастся скорректировать существующую схему макета так, чтобы она не содержала лишних макетов. В некоторых случаях единственным решением может быть упрощение иерархии за счет перехода на совершенно другой тип макета.
Например, вы можете обнаружить, что TableLayout предоставляет те же функциональные возможности, что и более сложный макет со многими позиционными зависимостями. Библиотека Jetpack ConstraintLayout предоставляет функции, аналогичные RelativeLayout, а также дополнительные возможности для создания более плоских и эффективных макетов.