Как происходит отрисовка в Android
Что проверяют
Знаете ли вы, что происходит между изменением данных и появлением пикселей на экране, и можете ли по этому знанию найти причину подтормаживаний. Уточнение почти всегда одно: чем invalidate() отличается от requestLayout().
Как отвечать
Опорная точка: кадр запускается сигналом VSYNC. Choreographer получает его и просит ViewRootImpl обойти дерево в три прохода:
- measure: родитель передаёт ребёнку
MeasureSpecс режимомEXACTLY,AT_MOSTилиUNSPECIFIED, ребёнок вonMeasure()считает размер и обязан вызватьsetMeasuredDimension(); - layout:
ViewGroupвonLayout()расставляет детей по координатам. У обычногоViewдетей нет, и этот метод ему почти не нужен; - draw:
onDraw()рисует наCanvas. При аппаратном ускорении (включено по умолчанию) команды записываются вRenderNode,RenderThreadотдаёт их на GPU, аSurfaceFlingerсобирает слои всех окон в итоговый кадр.
Бюджет на кадр при 60 Гц около 16,6 мс, при 120 Гц около 8,3 мс. Не уложились, получили пропущенный кадр.
invalidate(): помечает View на перерисовку, measure и layout не запускаются;requestLayout(): запрашивает measure и layout, поднимаясь по родителям до корня.
В Compose три фазы: композиция, layout и отрисовка. Чтение состояния в поздней фазе, например смещение через лямбду в Modifier.offset { }, перезапускает только эту фазу, без рекомпозиции.
Инструменты: Perfetto и системная трассировка, Layout Inspector, профилирование HWUI в настройках разработчика, JankStats для замеров в продакшене.
Где спотыкаются
Говорят, что invalidate() вызывает повторные measure и layout. Это делает requestLayout(), и лишний его вызов в анимации стоит дорого.
Создают объекты в onDraw(): Paint, Path, Rect. Метод вызывается на каждом кадре, аллокации провоцируют сборку мусора и рывки.
Строят глубокую вложенность из LinearLayout с весами и RelativeLayout. Эти контейнеры могут измерять детей дважды, и на каждом уровне цена умножается. Плоская разметка на ConstraintLayout или Compose снимает проблему.