From the rendering pipeline point of view, the CPU is in charge of getting the XML files and turning them into something that the GPU can process in order to draw on the screen. This middle-object is a display list, an Android internal object that holds a list of GPU resident assets that might be needed, as well as a list of commands to execute with OpenGL in order to render it.
The first time a View needs to be rendered, Android creates a display list for this View. This display list is used to draw the View on the screen so whenever we want to draw it again, we just need to send this display list to the GPU.
But sometimes the Views change on runtime so, is the same display list still valid if we modify some attributes? It depends. The answer is yes if we are modifying a property that doesn’t change the drawn content of the View, for example if we modify the position we can still use the same display list. However, if some visual part of this View changes like the background, the text or the size, the display list will be recreated in order to apply these changes to the final rendered object.
Is that all? Not really, the real problem starts to appear when we introduce complex layouts. You will understand it easier after this example.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
...
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
...
android:text="BUTTON"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
...
android:enabled="false"
android:text="DISABLED"/>
</LinearLayout>
This layout roughly corresponds with image 1 (below). Imagine that we want to change the size of the enabled button to make it look like image 4 during the execution of the app.
Due to this change, we will need to recreate the display list associated with this button. Image 2 is how the enabled button looks after applying the new display list but as you can see, this change has “broken” our beautiful layout so now we would execute the display list for the disabled button after calculating the new position (we didn’t change the visual aspect of the button, so we can reuse it). After the display list is executed, we are in the image 3 and it seems that there is something wrong. The LinearLayout which is wrapping the buttons needs to be resized because its children don’t fit in the previous layout’s size. And yes, if you’re wondering if this means recreating the display list for the LinearLayout.
This is a dummy example of the implications of changing a View but imagine a change like that in a very complex layout where there are lots of nodes and the hierarchy is too high. All these changes would be applied on cascade and we might move out of the 16ms window. The Android rendering pipeline is really fast and it’s optimised to recreate and execute the display list, but even with that we should be careful with how we create the layouts.
To avoid this kind of problems we should create flat layouts without unnecessary layouts that don’t contribute to the final UI. This way we won’t loose time recreating and executing unnecessary display lists. Don´t misunderstand me, sometimes it will be needed and there is nothing wrong with that, but we should understand what it’s going on under the hood and try to optimise the process as much as possible.
The best way to start is analysing our layouts with Hierarchy Viewer. This tool checks our app’s view hierarchy and it gives us relevant information about how long the measure, layout and draw steps take in relation with other views.
For those who have never used this tool before or want to go a bit further, there is a walkthrough on the Android developers site that I recommend before continuing reading this post as I will focus more on the information that we can gather and I’m not going to explain how to use the tool in details (please leave me a comment if you think I should). And before start, I just want to remember that for using this tool is needed to setup some configuration depending on the device version (you can find how here).
There are a lot of useful information that we can gather from Hierarchy Viewer about our views, but because we are trying to solve a very specific problem we just need to focus on two things: the Tree View (or Tree Overview) and the information displayed when when we click on a node.
The Tree View represents all the hierarchy for our screen in a tree form, where each node is a View on our layout. This view is really useful because it gives us an open perspective of how complex is our layout. Our goal is to have this tree as shallow and flat as possible, so we can easily identify where the problems can be just with a quick look.
Focusing now on a node, when we click on one of them we will see information about a view in particular like the class, the ID, a preview, etc. But the relevant information for us are the ones marked in the red square on the image. This two components give us the absolute (numbers) and the relative (represented with dots) time spent on each of the Measure, Layout and Draw phases for this specific node.
As you know, we need to connect our device in order to run the Hierarchy Viewer. This is because Hierarchy Viewer takes all the information from a running app. So the absolute time is actually the absolute time that the device needed to perform theses actions for this particular node. This information probably will change if we reanalyse the screen and don’t be scare if this happens. The reason for that is because the devices’s CPU might be doing some other stuff and be more or less overloaded, so it can take more o less longer.
But what should be interesting for us are the relative indicators. These three dots tell us the relative performance of this node in respect to all other profiled nodes. The colours of the dots can change between:
A red node is a potential problem in any situation where we wouldn’t expect slow performance, for example in a leaf node. Now, you might find confusing this assertion but, we are always going to have red indicators on our layouts, and this is not necessarily bad. In our layout something has to be the slowest node and because this indicators use relative time, the slowest node will be marked as red. So the question is, is it the node that we are expect to be the slowest one?
we can find some advices that will help us to know if we should refactor our layouts or when we should take a look at them:
- Look for red dots in leaf nodes or view groups with only a few children. This might point to a problem. Your app may not be slow, or it may not be slow on your device, but you need to be aware of why that dot is red. Systrace or Traceview can give you additional information.
- If you have a view group with many children and a red measure phase, take a look at the children to see how they are performing.
- A view with yellow or even red dots might not be performing slowly on the device. That’s where the actual numbers are helpful. Systrace or Traceview can give you additional information.
- If the root view of a hierarchy has a red measure phase, red layout phase, and yellow draw phase, this is somewhat typical, because it’s the parent of all the other views.
- If a leaf node in a tree with 20+ views has a red draw phase, this is a problem. Check your OnDraw method for code that shouldn’t be there.