Изменение TextField запускает полный цикл макета - PullRequest
5 голосов
/ 28 июня 2011

Рассматривая проблемы с производительностью в моем приложении, я обнаружил, что каждое нажатие кнопки вызывало полный цикл onMeasure () / layout (). Я не вижу причин пытаться выложить все приложение заново; ничего не было добавлено или удалено, и ничто не изменило размер, который я вижу.

Проблема, как правило, возникает, когда макет очень переполнен, и возможно, что нижний ряд кнопок вышел за край экрана на один или два пикселя.

Кто-нибудь имеет опыт работы с этим? Есть ли способ определить , почему цикл макета был запущен?

Похоже, что макет не будет запущен, если ни одно из текстовых полей на экране не будет изменено (см. Поиск причины запроса макета в ViewGroup ). Всегда ли изменение TextField вызывает изменение макета? Можно ли как-то это заблокировать, чтобы предотвратить это? Расстраивает мысль, что изменение любого TextField в любом месте экрана может привести к полному каскаду цикла измерений / макета для всего приложения; это убивает мое выступление.

Контейнер - это пользовательский класс ViewGroup, но я не думаю, что это проблема. Я не понимаю, что я мог бы сделать по-другому, чтобы не дать ему позвонить.

Я рассматриваю возможность добавления метода «блокировки» в мой виджет, чтобы предотвратить дальнейшие изменения макета после первоначального макета. Это улучшит производительность, но я бы лучше решил основную проблему.

Вот стек во время вызова моего метода onMeasure ():

Строка Gridbox.onMeasure (int, int): 217
Gridbox (View) .measure (int, int) строка: 8171
FrameLayout (ViewGroup) .measureChildWithMargins (View, int, int, int, int) строка: 3132 Строка FrameLayout.onMeasure (int, int): 245
FrameLayout (View) .measure (int, int) строка: 8171
PhoneWindow $ DecorView (ViewGroup) .measureChildWithMargins (View, int, int, int, int) строка: 3132
PhoneWindow $ DecorView (FrameLayout) .onMeasure (int, int) строка: 245
PhoneWindow $ DecorView (View) .measure (int, int) строка: 8171
Строка ViewRoot.performTraversals (): 801
ViewRoot.handleMessage (Сообщение) строка: 1727
ViewRoot (Обработчик) .dispatchMessage (Сообщение) строка: 99 Looper.loop () строка: 123 Строка ActivityThread.main (String []): 4627
Строка Method.invokeNative (Object, Object [], Class, Class [], Class, int, boolean): недоступно [собственный метод]
Строка Method.invoke (Object, Object ...): 521
ZygoteInit $ MethodAndArgsCaller.run () строка: 858
ZygoteInit.main (String []) строка: 616 NativeStart.main (String []) строка: недоступно [собственный метод]

Ответы [ 3 ]

5 голосов
/ 15 января 2015

Кто-нибудь имеет опыт работы с этим?Есть ли способ определить, почему был запущен цикл макета?

Есть способ точно определить, почему цикл макета запущен.
Перейти к макету экрана, который вы хотите отладитьи замените верхний контейнер пользовательским контейнером, который переопределяет только один метод: requestLayout ().

Так, например, если ваш макет выглядит следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- omitted for brevity -->

</android.support.v4.widget.DrawerLayout>

Сначала необходимо создать новый пользовательский класс, который является подклассом вашего класса контейнера:

public class RootView extends DrawerLayout {

    public RootView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void requestLayout() {
        super.requestLayout();
    }

}

И тогда вам нужно будет изменить ваш xml:

<?xml version="1.0" encoding="utf-8"?>
<com.example.RootView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- omitted for brevity -->

</com.example.RootView>

Теперь, способ работы Android состоит в том, что, когда любая группа просмотра получает запрос макета, она также вызывает requestLayout () своегопрямой родитель.Это означает, что всякий раз, когда любой вызов requestLayout () выполняется внутри вашего экрана, также будет вызываться requestLayout () вашего RootView.

Так что начните сеанс отладки, поместите точку останова в метод RootView.requestLayout () и выполнитедействие, которое вы думаете, вызывает цикл макета.Посмотрите на трассировку стека.Это всегда будет выглядеть так:

RootView.requestLayout() line: 15   
RelativeLayout(View).requestLayout() line: 17364    
RelativeLayout.requestLayout() line: 360    
FrameLayout(View).requestLayout() line: 17364   
... a dozen of other calls to requestLayout() ...
TimeCell(View).requestLayout() line: 17364  
TextViewPlus(View).requestLayout() line: 17364  
TextViewPlus(TextView).setTypeface(Typeface) line: 2713 
... more methods ...

Первый метод, который не является requestLayout (), - это то, что вызывает цикл макета.
В приведенном выше примере это TextViewPlus.setTypeface (Гарнитура).

Возобновив программу и подождав, пока точка останова снова сработает, вы сможете быстро определить все методы, которые запускают ретрансляцию в вашем конкретном случае.

Однако обратите внимание, что интерфейсЛаги почти всегда вызваны вызовами нескольких измерений, а не несколькими циклами компоновки!

В сложной компоновке (представления с прокруткой, линейные компоновки с весами и т. д.) для одного прохода компоновки может потребоваться, чтобы onMeasure быловызывается несколько раз в одном представлении, иногда до 500 раз за цикл макета и более.Чтобы проверить, является ли это вашей проблемой, переопределите методы onMeasure и onLayout одного из нижних представлений (одно из представлений, удаленных от rootView; обратите внимание, что отношение onLayout к onMeasure будет различным для разных представлений на экране).Может быть трудно точно определить, какое представление имеет наихудшее отношение onLayout к onMeasure, но любые представления, которые находятся внутри LinearLayout внутри LinearLayout внутри LinearLayout внутри LinearLayout ... - хорошее место для начала.

Если onMeasure называется moreчем 16 раз за onLayout, то у вас есть проблемы.Попробуйте сделать иерархию представлений более плоской, удалите LinearLayouts с атрибутом weigthSum и ScrollViews.

3 голосов
/ 28 июня 2011

Изменение содержимого TextView (его текста) вызовет ретрансляцию. Это, однако, будет вызывать только меру / макет части дерева. Если это происходит только время от времени (например, когда пользователь нажимает кнопку), не беспокойтесь об этом.

1 голос
/ 29 августа 2014

Согласно комментарию Эдварда, ему удалось предотвратить изменение макета всего дерева макетов путем изоляции TextViews в их собственном LinearLayout. Это НЕ работает для меня. Ниже я изложил, что сработало для меня, если у кого-то возникла такая же проблема.

Я столкнулся с аналогичной проблемой изменения макета, когда добавил в свой макет TextClock (который является производным от TextView): это будет приводить к обновлению всего макета каждую минуту в минуту.

Изоляция TextClock путем помещения его в собственный LinearLayout или RelativeLayout как с фиксированным layout_width / height , так и с фиксированным позиционированием с использованием layout_alignParent , установленного в этом макете, не изменило полное повторное макет каждую минуту. Что помогло, так это просто установил фиксированный размер TextClock , т. Е. Не зависит от его фактического содержимого:

    <TextClock
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:ems="4"
    android:lines="1"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true"
    />

Где ems = "x" и lines = "y" исправить ширину и высоту x"самыми широкими" символами и y строки, когда layout_width / height установлены в "wrap_content" . Конечно, вы также можете использовать фиксированные размеры в dp (или sp, я бы предположил).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...