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