Nested weights are bad for performance
You’ve almost certainly seen the lint error before “Nested weights are bad for performance.” How bad are they, though? My plan is to create a sample application with a nested view hierarchy using Linear Layouts and compare it to the same layout using the Percent Layout included in the support library.
The Android Framework creates the layout made in your XML file in three steps: Measure, Layout, and Draw. Within these three steps, the XML file will go from code to being drawn on the screen for the user to interact with. It is also within these three steps that you as a developer will have access to modify what is happening when creating a custom view in the three methods onMeasure(), onLayout(), and onDraw(). Let’s look at each step a little more closely.
The very high-level picture of what’s happening during onMeasure() is beginning at the root node of the view tree, each parent node will give instructions to their children saying “This is how big you can be.” The child will store this information to use during the onLayout() method call.
In onLayout(), the parent node will use the values their children stored in onMeasure() to place them where they will be drawn in the final step, onDraw().
In onDraw(), the views are finally placed on the screen. It starts by the root node completing its onDraw() method, and then will traverse the tree to its children to ask them to complete their onDraw() method as well. This will continue to happen until all views in the view tree are finally placed on the screen.
Now, let’s take a look at why exactly nested weights are bad for performance. Let’s say we have this layout structure in our XML file:
<RelativeLayout>
<TextView>
</RelativeLayout>
We know from our introduction what steps will happen, but let’s also take a minute to talk about the number of times each one of those steps happen with this simple layout. When you have a relatively flat structure like this, each one of the onMeasure(), onLayout(), and onDraw() methods will be called twice for RelativeLayout and once for the TextView. You can take a look at this video given at Google IO 2013 to have a better idea of why a RelativeLayout will always require two passes of these methods.
So let’s take a look at a layout that has a nested LinearLayout with non-zero weights associated with each of the LinearLayouts.
This is a common layout you might see if you’re looking to equally divide the screen into four equal segments, similar to this image
The issue arises when you take into account the amount of passes this layout will take in order to draw itself on the screen. (It should be noted that the same amount of passes are required when talking about nested Relative Layouts. This can be referenced in that same Google IO 2013 video linked earlier.)
The Problem
For every nested LinearLayout in this previous example, the amount of passes required will increase exponentially. So in the previous example, four passes will be required to layout, measure, and draw that XML file. If you add another set of Linear Layouts, the number of passes will be 8. This is taken directly from the lint message you will see in Android Studio with nested Linear Layouts:
“Layout weights require a widget to be measured twice. When a LinearLayout with non-zero weights is nested inside another LinearLayout with non-zero weights, then the number of measurements increase exponentially.”
Alternative
So now that we know why nested Linear Layouts are bad for performance, let’s talk about an alternative using our current example, now using a Percent Layout. Note you can include the Percent Layout by adding this line to your build.gradle file:
compile ‘com.android.support:percent:<latest support version>’
You can now apply these fields to your views within a view in your Percent Layout:
- layout_widthPercent
- layout_heightPercent
- layout_marginPercent
- layout_marginLeftPercent
- layout_marginTopPercent
- layout_marginRightPercent
- layout_marginBottomPercent
- layout_marginStartPercent
- layout_marginEndPercent
- layout_aspectRatio
So we can now do something like this in our XML:
<PercentLayout>
<Button
app:layout_widthPercent=”25%”
app:layout_heightPercent=”25%”/>
</PercentLayout>
The full source code that recreates the original example with nested Linear Layouts is found here.
By inspecting the source code of the Percent Relative Layout, we can see that the layout is just an extension of Relative Layout, so we will still need to take into consideration the same things when considering using a Percent Relative Layout as we would when using a regular Relative Layout. However, when comparing Percent Relative Layout to a layout using nested Linear Layouts, performance is better when using Percent Relative Layout. Here are some screenshots I took using the Hierarchy Viewer from the Android SDK.
Percent Layout:
Full Tree here
The branch that we are most concerned with here
A close up inspection of the base node of our layout here
Linear Layout:
Full Tree here
The branch that we are most concerned with here
A close up inspection of the base node of our layout here
When profiling the main Content Frame Layout node in the view tree for our sample application, we can see a comparable Measure and Layout times between the two, but when comparing the Draw times, the Percent Layout was around 67% faster when actually drawing the views on screen. When we compare the the Measure, Layout, and Draw times for each of the four buttons that make up our layout, we can see the Hierarchy Viewer gives a green circle for all four buttons for the Measure, Layout, and Draw times while some of the buttons in the Linear Layout has yellow for its Measure, Layout, and Draw times. Taken from the developer document for the Hierarchy Viewer, the colors of the button represent the following:
- Green: For this part of the render time, this View is in the faster 50% of all the View objects in the tree. For example, a green dot for the measure time means that this View has a faster measure time than 50% of the View objects in the tree.
- Yellow: For this part of the render time, this View is in the slower 50% of all the View objects in the tree. For example, a yellow dot for the layout time means that this View has a slower layout time than 50% of the View objects in the tree.
- Red: For this part of the render time, this View is the slowest one in the tree. For example, a red dot for the draw time means that this View takes the most time to draw of all the View objects in the tree.
Conclusion
In conclusion, while nested Linear Layouts won’t completely interrupt the UI thread, especially when we consider a simple layout like in our example, performance loss will become a lot more obvious when having a much deeper nesting structure and should be avoided if possible. As an alternative, the Percent Layout offers a more performance friendly way to achieve a layout structure that most people would commonly use nested Linear Layouts to achieve.