Время измерения макета Android удваивается с каждым шагом вверх по иерархии
Я анализирую пользовательский интерфейс планшета в иерархическом средстве просмотра и заметил следующий пример измерения времени (начиная с основания дерева и продвигаясь вверх):
... ~ 40 мс... ~80 мс... ~160 мс... ~320 мс... ~640 мс... ~1280 мс...
Я предположил, что проблема была в LinearLayouts с вложенными весами, поэтому я удалил ВСЕ LinearLayouts и веса во всей иерархии. Теперь у меня есть это:
... ~ 40 мс... ~80 мс... ~160 мс... ~160 мс... ~160 мс... ~310 мс...
Лучше, но он все еще удваивается каждые несколько уровней. Что может быть причиной этого?
Вот полная иерархия для этого пути (извините за длину... не стесняйтесь бросать мне свои лучшие советы по оптимизации):
[generated layouts]
*RelativeLayout [309 ms]
FrameLayout [164 ms]
NoSaveStateFrameLayout [160 ms]
*RelativeLayout [151 ms]
*RelativeLayout [77 ms]
ListView [45 ms]
GridLayout [46 ms]
Spinner [4.4 ms]
TextView [0.1 ms]
* время просмотра удваивается
Любые советы будут с благодарностью! Заранее спасибо.
TL; DR
Помимо вложенных весов, что вызывает экспоненциальное увеличение времени измерения?
2 ответа
Предыдущий ответ от Саймона не совсем правильный. Правда, есть проход меры и проход макета, но один этот факт не вызывает экспоненциальный взрыв.
Если вы поместите трассировки внутри onMeasure() и onLayout() в различных представлениях в иерархии, вы обнаружите, что передача макета не является проблемой: onLayout() вызывается только один раз для каждого представления, что делает его линейным временем ВТП. Передача меры является проблемой - для некоторых подклассов ViewGroup, с некоторыми параметрами, onMeasure() заканчивает тем, что вызывает onMeasure() каждого дочернего элемента дважды, что, конечно, приводит именно к поведению, которое вы видите.
RelativeLayout - один из этих "плохих граждан", который измеряет каждого ребенка дважды, соглашаясь с вашим наблюдением.
Еще один плохой гражданин (как вы знаете) - LinearLayout, когда у детей есть MATCH_PARENT и ненулевые веса. Я посмотрел на реализацию и я примерно следую за тем, что она делает - сначала она рекурсивно измеряет детей, чтобы увидеть, насколько большими они хотели бы быть, затем, если есть какая-то слабость (или усадка), она измеряет детей с ненулевыми весами снова, чтобы распределить слабину.
Обратите внимание, что RelativeLayout часто упоминается в качестве решения экспоненциального взрыва, даже если это один из плохих граждан, вызывающих его. Я полагаю, что причина в том, что RelativeLayout достаточно выразителен, чтобы глубокие иерархии LinearLayout могли быть повторно выражены как один RelativeLayout. Это очень важный момент - если вы просто замените LinearLayouts на RelativeLayouts без выравнивания иерархии, у вас все равно будет экспоненциальное время измерения.
Для меня неясно, является ли экспоненциальный выброс тем, что в конечном итоге может быть просто оптимизировано в основных библиотеках (возможно, путем разделения существующего прохода измерения на проход сбора предпочтительных размеров и проход распределения-ослабления, каждый из которых может быть сделано в линейное время и которые не должны вызывать друг друга) или есть ли в контрактах LinearLayout и RelativeLayout что-то, что делает рекурсивное двойное измерение детей по своей сути необходимым.
Android строит макет за 2 прохода.
Первый - это пропуск меры, когда всех детей спрашивают, насколько они хотят быть большими. Затем проходит макет, когда родитель сообщает детям, насколько они велики, и просит их нарисовать.
Поэтому добавление дополнительного уровня ViewGroup примерно удваивает время. Это соответствует приведенным вами примерам.