Performance in Android — Part 2
In the last post I introduced you to the basis about rendering problems and showed you the key to avoid overdraws, applying background property just when is needed. There is “nothing” wrong having some overdraw, sometimes it’s unavoidable, but we should always have in mind why this is happening.
As you might notice if you have read the previous entry, I mentioned that there are GPU-side and CPU-side problems but I only talked about the first one. This post will cover the missing chapter and finish with the rendering issues.
Welcome to the CPU side of the rendering pipeline
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.
Display list — life cycle
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.
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.
How do I know if I need to restructure my layouts?
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).
Hierarchy Viewer, I choose you!
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:
- Green means the view renders faster than at least half of the other views.
- Yellow means the view renders faster than the bottom half of the other views.
- Red means the view is among the slowest half of views.
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?
Facing nasty layouts
To visualise an example, I have created a GitHub repository where we can easily see the implications of having complex layouts and how we can identify and redefine them.
The screen that we are going to analyse is ‘Compare Layout Activity’. In this screen we will see two items representing two dummies colours entities. This layouts are created with an ImageView and three TextViews. The reason why I chose this kind layout is because it’s very common for displaying a list of data where, for example, we want to show an icon or image, the date/name/whatever of the item and a short description. If this item layout is not optimised, imagine the lack of performance that our app will suffer when the list is populated. The code for this screen is under the package comparelayouts.
Coming back to Hierarchy Viewer, the first thing that we will do is analyse the Activity CompareLayoutsActivity. Remember: to use this tool we need to run the app on the device. On the Tree View we should find something like this:
This tree represents all the Views which compose the screen, including the ones that Android uses internally. With this view we can easily identify some of the screen elements. For example, all the nodes inside the yellow circle are associated with the toolbar (ActionBarContainer, Toolbar, TextView, ActionMenuView…). Under the green circle we can find the layout fragment_compare_layouts.xml and, rounded with red and blue, the red and blue previously displayed. Just taking a look to the red and blue circle we can see that one of them is flatter than the other. Now, my friend, is when we will see the real difference between have or haven’t a flat layout.
By clicking on the button that appear on the image we can get the layout times for the tree at the selected node. To make it simple, we are just going to take the times from the parent LinearLayout by selecting it before click the button. After that, the three dots should appear in the nodes and should looks like the image below. Don’t be scare if it’s not exactly the same, remember that every execution might be slightly different, but the overall should be similar.
The difference is clear, the first LinearLayout is slower than the second when both represent the same information. In absolute terms, the difference is around 0.5ms slower (for my execution, remember it will always be different). We can think that this 0.5ms are not going to make any difference, but again, imagine this 0.5ms on each item in a list.
In this example, the difference between the two items on the screen in terms of layout is that the second one uses a RelativeLayout as parent, giving us the option to place all the Views on the screen without creating other complementary layouts.
But don’t think that replacing everything with RelativeLayouts will make your app faster! This is just one way to reorganise the layout for this example and it might not work for every app. I can’t tell you the magic words that will make your app faster. What I can do is help you to find out if you need this words and give you some tips.
But, I’m still confusing with all these dots
I know, all these relatives values can make you crazy. Thankfully, in the Android Developers site 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.
You will probably need some practice analysing this information, and this is fine. The more you check your layout, the more you will know about them, its weakness and strength.