Эффективность findViewById, чтобы найти

вероятно, большинство разработчиков Android знают, что findViewById - это не дешевая операция. Еще одна вещь, которую большинство из нас знает, заключается в том, что вы можете повысить производительность, используя наименьшее поддерево иерархии представлений, чтобы найти представления по их идентификатору, например:

<LinearLayout
    android:id="@+id/some_id_0">
    <LinearLayout
        android:id="@+id/some_id_1">
        <LinearLayout
            android:id="@+id/some_id_2">
            <TextView android:id="@+id/textview" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

в этом случае вы, вероятно, хотите поиска в разделе LinearLayout С id == @id/textview

но что происходит, когда иерархия не каскадная, а скорее ветвящаяся на каждом уровне, и вы хотите найти представления скажем, на "листьях"? Вы выполняете findViewById чтобы добраться до нижней части ветки, найдя родителей, или вы выполняете findViewById в более широком подмножестве? Я думаю, что простой ответ будет заключаться в том, что это зависит от случая, но, может быть, мы могли бы немного обобщить, от чего это действительно зависит?

спасибо

EDIT:

под большим подмножеством я имею в виду что-то вроде этого:

<RelativeLayout android:id="@+id/r0">    
    <RelativeLayout
        android:id="@+id/r1">
        <LinearLayout
            android:id="@+id/some_id_0">
            <LinearLayout
                android:id="@+id/some_id_1">
                <LinearLayout
                    android:id="@+id/some_id_2">
                    <TextView android:id="@+id/textview0" />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
        <LinearLayout
            android:id="@+id/some_id_3">
            <LinearLayout
                android:id="@+id/some_id_4">
                <LinearLayout
                    android:id="@+id/some_id_5">
                    <TextView android:id="@+id/textview1" />
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </RelativeLayout>
    <LinearLayout
        android:id="@+id/some_id_6">
        <LinearLayout
            android:id="@+id/some_id_7">
            <LinearLayout
                android:id="@+id/some_id_8">
                <TextView android:id="@+id/textview2" />
                <TextView android:id="@+id/textview3" />
                <TextView android:id="@+id/textview4" />
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

таким образом, вопрос будет, если я хочу findViewById the TextView взгляды LinearLayout С @+id/some_id_8, должен ли я выполнить эту операцию на всем контейнере, или я должен findViewById на LinearLayout С @+id/some_id_8 и на этот взгляд findViewById все TextViews?

2 ответов


это не имеет абсолютно никакого значения, если вы ищете View непосредственно, или если вы смотрите на родителей, а потом ребенка. Но если вы, например, хотите получить три TextViews на LinearLayout код some_id_8 тогда было бы лучше для производительности, если вы сначала посмотрите на LinearLayout и затем на TextViews. Но разница ничтожна. Реальная проблема сам макет (подробнее об этом дальше).

и вообще findViewById() не является источником всего зла. Это может быть проблемой в ListView если вам нужно позвонить findViewById() возможно даже несколько раз во время каждого getView() вызов, но это то, для чего предназначен шаблон держателя вида.

когда производительность критична, посмотрите, что вы вызываете findViewById() как можно меньше. В Fragment или Activity вы можете искать все Views вы всегда будете нуждаться в onCreateView() или onCreate(). Если вы сохраните ссылки в нескольких переменных-членах, вам никогда не придется вызывать это снова.


теперь объясни, почему findViewById() может быть проблемой производительности, мы должны посмотреть на ее реализацию,эта ссылка приводит к исходному коду Android 4.4.4 View:

public final View findViewById(int id) {
    if (id < 0) {
        return null;
    }
    return findViewTraversal(id);
}

так findViewById() просто проверяет, действителен ли идентификатор, и если это тогда защищенный метод findViewTraversal() называется. В View он реализован следующим образом:

protected View findViewTraversal(int id) {
    if (id == mID) {
        return this;
    }
    return null;
}

он просто проверяет, равен ли переданный id идентификатору View и возвращает this если это так, в противном случае null. Интересной частью является findViewTraversal() реализация ViewGroup, эта ссылка приводит к исходному коду Android 4.4.4 ViewGroup:

protected View findViewTraversal(int id) {
    if (id == mID) {
        return this;
    }

    final View[] where = mChildren;
    final int len = mChildrenCount;

    for (int i = 0; i < len; i++) {
        View v = where[i];

        if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
            v = v.findViewById(id);

            if (v != null) {
                return v;
            }
        }
    }

    return null;
}

первое, если в верхней части этого метода такое же, как в View реализация, она просто проверяет, равен ли переданный id идентификатору ViewGroup и если это произойдет, он вернется сам. После этого он проходит через всех детей и зовет findViewById() на каждого из детей, если возвращаемое значение этого вызова не null тут View мы ищем был найден и будет возвращен.

если вы хотите больше деталей о том, как Views или ViewGroups работа я предлагаю вам изучить исходный код самостоятельно!


так что все это кажется довольно прямо вперед. Иерархия представлений по существу проходит как дерево. И это может сделать его довольно дорогим или довольно быстрым в зависимости от на сколько Views находятся в вашем макете. Не имеет значения, выглядит ли ваш макет следующим образом:

<LinearLayout android:id="@+id/some_id_0">
    <LinearLayout android:id="@+id/some_id_1">
        <LinearLayout android:id="@+id/some_id_2">
            <TextView android:id="@+id/textview0" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

или если это выглядит так:

<LinearLayout android:id="@+id/some_id_0">
    <LinearLayout android:id="@+id/some_id_1" />
    <LinearLayout android:id="@+id/some_id_2" />
    <TextView android:id="@+id/textview0" />
</LinearLayout>

за сумму Views одинакова в обоих случаях, и производительность findViewById() весы с суммы Views.

но общее правило заключается в том, что вы должны попытаться уменьшить сложность макета для повышения производительности и часто использовать RelativeLayout. И это работает только потому, что если вы уменьшите сложность, вы также уменьшите количество Views в макете и RelativeLayouts очень хороши в снижении сложности. Позвольте мне проиллюстрировать это, изображение у вас есть такой макет:

<LinearLayout android:id="@+id/some_id_0">
    <RelativeLayout android:id="@+id/some_id_5">
        <LinearLayout android:id="@+id/some_id_1" />
        <LinearLayout android:id="@+id/some_id_2" />
    </RelativeLayout>
    <RelativeLayout android:id="@+id/some_id_6">
        <LinearLayout android:id="@+id/some_id_3" />
        <LinearLayout android:id="@+id/some_id_4" />
    </RelativeLayout>
</LinearLayout>

представьте, что в этом случае оба RelativeLayouts выше просто есть, чтобы расположить внутренний LinearLayouts каким-то особым образом и внешним LinearLayout просто там позиция RelativeLayouts ниже друг друга. Вы можете очень легко построить тот же макет только с RelativeLayout как корень и четыре LinearLayouts как дети:

<RelativeLayout android:id="@+id/some_id_0">
    <LinearLayout android:id="@+id/some_id_1" />
    <LinearLayout android:id="@+id/some_id_2" />
    <LinearLayout android:id="@+id/some_id_3" />
    <LinearLayout android:id="@+id/some_id_4" />
</RelativeLayout>

и производительность этого макета будет лучше, чем макет выше не потому, что RelativeLayout как-то производительность лучше, чем LinearLayout и не потому, что макет более плоский, а просто потому, что количество Views в макете ниже. То же самое относится почти ко всем другим процессам, связанным с представлением, таким как рисование, разметка, измерение. Все будет быстрее только потому, что количество Views в макете ниже.


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

<RelativeLayout android:id="@+id/r0">  
    <TextView android:id="@+id/textview0" />
    <TextView android:id="@+id/textview1" />
    <TextView android:id="@+id/textview2" />
    <TextView android:id="@+id/textview3" />
    <TextView android:id="@+id/textview4" />
</RelativeLayout>

и такой макет, безусловно, даст большой прирост производительности.


глядя на этот исходный код android, я просто не вижу, как findViewById стоит дорого. Это в основном простой цикл, я знаю, что он рекурсивный, поэтому вы заплатите штраф за постоянное переключение стека, но даже самый сложный экран будет в порядке десятков просмотров, а не тысяч, за исключением переработанных списков. Я думаю, нам нужны тесты на низких устройствах, чтобы знать, действительно ли это проблема.