gjackal gjackal - 5 months ago 19
Android Question

Efficiency of findViewById

Probably most Android devs know that

findViewById
is not a cheap operation. Another thing that most of us know, is that you can boost the performance by using the smallest sub-tree of the view hierarchy to find views by their id, example:

<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>


In this case you probably want to search in the
LinearLayout
with the
id == @id/textview


But what is the case when the hierarchy is not cascading, but rather branching on every level, and you want to find the views on the "leaves" let's say? Do you perform the
findViewById
to get to the bottom of the branch by finding parents, OR do you perform the
findViewById
on a larger subset? I think a simple answer would be that it depends on the case, but maybe we could generalize a bit on what it really depends?

Thanks

EDIT:



By a larger subset I mean something like this:

<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>


So the question would be if I want to
findViewById
the
TextView
views in
LinearLayout
with
@+id/some_id_8
, should I perform this operation on the whole container, or I should
findViewById
the
LinearLayout
with
@+id/some_id_8
and on this view
findViewById
all the
TextViews
?

Answer

It makes absolutely no difference if you look for the View directly or if you look for a parent first and then the child. But if you for example want to retrieve the three TextViews in the LinearLayout with the id some_id_8 then it would be better for performance if you first look for the LinearLayout and then for the TextViews. But the difference is miniscule. The real problem is the layout itself (more on that further down).

And generally findViewById() is not the source of all evil. It can be a problem in a ListView if you have to call findViewById() possibly even several times during each getView() call, but that's what the view holder pattern is for.

When performance is critical see to it that you call findViewById() as little as possible. In a Fragment or Activity you can look for all the Views you will ever need in onCreateView() or onCreate(). If you save the references in a few member variables you will never have to call it again.


Now to explain why findViewById() can be a performance problem we have to look at its implementation, this link leads to the Android 4.4.4 View source code:

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

So findViewById() just checks if the id is valid, and if it is then the protected method findViewTraversal() is called. In a View it is implemented like this:

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

It just checks if the passed in id is equal to the id of the View and returns this if it does, otherwise null. The interesting part is the findViewTraversal() implementation of ViewGroup, this links leads to the Android 4.4.4 ViewGroup source code:

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;
}

The first if at the top of this method is the same as in the View implementation, it just checks if the passed in id equals the id of the ViewGroup and if it does it returns itself. After that it loops through all the children and calls findViewById() on each of the children, if the return value of this call is not null then the View we are looking for has been found and will be returned.

If you want more details about how Views or ViewGroups work I suggest you study the source code yourself!


So this all seems pretty straight forward. The view hierarchy is essentially traversed like a tree. And that can make it pretty expensive or pretty fast depending on how many Views are in your layout. It doesn't matter if your layout looks like this:

<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>

Or if it looks like this:

<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>

Because the amount of Views is the same in both cases, and the performance of findViewById() scales with the amount of the Views.

BUT the general rule is that you should try to reduce the complexity of the layout to increase performance and that you should often use a RelativeLayout. And that works just because if you reduce the complexity you also reduce the amount of Views in the layout and RelativeLayouts are very good at reducing complexity. Let me illustrate that, image you have a layout like this:

<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>

Imagine that in this case both of the RelativeLayouts above are just there to position the inner LinearLayouts in some special way and the outer LinearLayout is just there to position the RelativeLayouts below each other. You can very easily build the same layout with just a RelativeLayout as a root and the four LinearLayouts as children:

<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>

And the performance of that layout will be better than the layout above not because the RelativeLayout is somehow performance-wise better than a LinearLayout and not because the layout is flatter, but simply because the amount of Views in the layout is lower. The same applies for almost all other view-related processes like drawing, layouting, measuring. Everything will be faster just because the amount of Views in the layout is lower.


And to return to your original question: If you want a performance increase than reduce the complexity of your layout. There is absolutely no reason to have so many nested LinearLayouts. Your "large subset" can almost certainly be reduced to this:

<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>

And such a layout would definitely yield a big performance boost.