Efficiency of findViewById

你。 提交于 2019-12-31 10:39:12

问题


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?


回答1:


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.




回答2:


Looking at that android source code I just don't see how findViewById is expensive at all. It's basically a simple loop, I know it's recursive so you'll pay the penalty of constantly switching the stack, but even the most complicated screen is going to be in the order of dozens of Views not thousands, except for recycled lists. I guess we need benchmarks on low end devices to know if it's really an issue.



来源:https://stackoverflow.com/questions/26037744/efficiency-of-findviewbyid

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!