Android Application Performance — Step 1: Rendering
Application performance is always a problem for the everyone developing Android application. Because, if we realize that our application is slow or worse someone comes and says that our application is too slow, firstly we have to gain insight using profiling tools and then as examining this data, we have to find the problem and finally fix it. Nevertheless the process is bothering, sometimes while trying to improve our visuality, we can miss some important points. And this causes the some devices that are not so powerful ones sometimes lock the screen and even the device.
I will divided the issues into four major pipelines as using Udacity Performance class and I’ll try to explain performance issues as starting with rendering, than compute, memory and lastly the battery.
Step 1: Rendering
Rendering is the most common performance problem. Because what the the designers want from us and what we did could not be same and trying to do the best visuality, we can fail in development. This may cause, at some devices that are not so strong, app’s locking the screen or device, etc.
Firstly, the system will try to attempt redrawn your activity at every 16 ms. Actually, this means that, your app have to do all the logic to update the screen in that 16 ms window, so you can hit 60 fps. Fps is about your device’s hardware and it defines how fast the screen update itself in 1 second. Most devices update about 60 hertz and it means the drawing logic that will be executed for every frame needs to be completed in 60 ms.
But what happens, if you can’t complete the logic, in that 16 ms?
It’s called dropped frame. For example, if your calculation takes 24 ms, this case happens. System tried to draw a new picture to the screen, but it wasn’t ready. So it did not refresh anything. And this caused, user’s seeing the refreshed picture after 32 ms instead of 16ms. If even there is one dropped frame, the animation will start not to seen smooth. If there are lots of dropped frames, it will be seen like your app does not working normally. It is a poor experience and lastly users will start to complain about our app.
We can think that, rendering is broken up into two key sections: CPU and GPU. They work together in order to draw the images to the screen.
Measure, Layout, Record and Execute is done on CPU, on the other hand, rasterisation is done on GPU.
Measure –> Layout –> Record –> Execute
On the CPU side, the most common performance problem comes from using unnecessary layouts and invalidations which is part of your view hierarchy having to be measured, torn down and rebuilt. This generally comes in 2 problematic ways. The first one is display lists’ rebuilt so much in your frame time and the second one is spending too much time with unnecessary parts of your view hierarchy invalidation and redrawing.
On the GPU side, the most common problem is overdraw. It is wasting your GPU time with redrawing the pixels.
And then, how does our activity get drawn on the screen, or in another way, how do the xmls and markup language turn into the pixels so the users can see and understand?
This is called rasterization. This process is actually, the objects like button, String turning into pixels in a texture on your screen. GPU provides this process being faster.
GPU is designed the usage of some specific primitives like polygons, texture, image etc. These primitives are provided by CPU, before GPU draw anything to the screen. This is done by OpenGL ES API on Android. At every time, a checkbox, path or UI objects are needed to be drawn to the screen, firstly they are turned into polygons and textures on CPU and passed to the GPU for rasterization.
For both CPU and GPU, these operations are not fast processes. Thanks to OpenGL ES, ones the content is uploaded to GPU, if there will be no updates, such as a button, it could be reached from GPU. If we want our app to be faster, we should not do visual changes as long as possible. Because when each time we update the resources on GPU, we lost the execution time.
And one more thing, since Honeycomb(Android 3.0), whole rendering process works on GPU. Actually, even if we do not do anything, lots of the work is done by Android system. For example, the resources, bitmaps and drawables that are provided by our theme are grouped into a single texture and uploaded to GPU. When we need to work with these resources, we can reach them without any conversion. They will be displayed really fast. On the other hand, drawing Texts is a completely different process. Firstly, it is drawn to the CPU as image and then uploaded to GPU and each character on a string is drawn to the rectangle on your screen. Actually, if we do not do something extraordinary, the whole process is held by Android system itself.
Overdraw: Overdraw is a term used to describe how many times a pixel on the screen has been redrawn in a single frame.
This is a large problem because each time we’re rendering the pixels that don’t contribute the final scene and waste GPU time. To maximize the app performance, we should minimize the overdraw.
At this point, Debug GPU Overdraw option which is lying on the Developer Options menu helps us.
When we open it, the screen will start to look like just this one.
- True color: No overdraw
- Blue: One times overdrawn
- Green: 2 times overdrawn
- Pink: 3 timesoverdrawn
- Red: 4 or more overdrawn
Actually, the blue one is acceptable. And there are two ways for how we pass to the blues from red ones.
Firstly, we can remove the backgrounds and drawables that have no affect to the final scene. And secondly, if we know the views that hide other views, we can define them. This will get back to us as decrement at CPU and GPU overhead.
Clipping: Android system knows that overdrawn is a problem and avoids to draw the invisible views to UI. This operation is defined as clipping and it is the most important performance optimisation of Android.
But if we customise the views and override onDraw method than this technique won’t work. Because at the situations like this, the system can not know how we draw the view.
For example, if we look at the cards on the picture, actually only the one at the top is seen completely. At this kind of situations, it is a total loss of time to draw all the cards. But here, as using Canvas class, we can say which parts are hidden and unnecessary to draw.
At this situation, the most usable method is Canvas clipRect. This method provides system knowing the drawable’s boundaries in the given view.
And another important method is quickReject. It controls if the given area outside of the drawing are or not.
All the thins that I talked to now were about GPU performance. Now it’s time for CPU.
As I said before, firstly CPU works and then GPU uses its output.
To draw anything to screen, first of all CPU should generate the display lists. Basically, display lists keeps whole the information to render a view at GPU. There are all the assets that GPU will need, instructions to execute with OpenGL ES to render at GPU.
When a view will be rendered for the first time, a display list is created for it. If we need this view in the future, all we need is to execute this display list. For example, its position can be changed. But, if the change is done on visual parts, at this time to be able to update the view firstly, we should update display list. At this point, display list is created again and executed again.
The important point is here at every visual change, display list is recreated and re-executed. And the performance will changed according to view’s complexity.
According to the type of visual change, rendering operations may effect other views. For example, you see on the image. The button size doubled and this caused textview’s relocating and parent view’s size changing.
When your view’s size change, measure step is triggered and at the hierarchy, it will be asked to every view their new size. On the other hand, view position changes or request layout is called or views are repositioned in a view group, layout step is triggered.
Actually, Android system is very effective at executing these processes. But when we do something extraordinary at custom view or tons of views are tried to draw at the same time, system could not count the frame time.
At this point, Hierarchy Viewer can help us. It provides to visualize our UI structure and at the same time, helps us to understand different view’s performances.
Hierarchy Viewer is a very useful tool. It gives bird’s eye view of your app’s complete View Hierarchy. By moving the gray airport at Tree Overview, you can change the section you see in Tree View. To see the details of the selected view, you can click view properties tab. And by double clicking to node, you can see how it is seen on popup. You can’t see the source code by clicking at Hierarchy Viewer, because the view you see is active page. But you can see the view’s id and type at node, and by using them you can examine your source code.
Hierarchy Viewer provides you to see if there are unnecessary layouts, flatten your structure and improve your performance.
Profiling with Hierarchy Viewer:
Another very useful and important property of hierarchy viewer is its profiling relative view performance.
When selecting a root node, we can examine the relative view performance of child nodes.
When we click the profiling tool, there will be three dots at each child node and each of them will be in different colors - green, red and yellow.
The left dot represents the Draw Process of rendering pipeline.
Middle Dot represents the Layout phase.
And right dot represent the Execute phase.
The color of the dots indicates the relative performance of this node in respect to all other profiled nodes.
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.
It is normal to see red dots at the diagram. Because it gives us the relative performance of views. But of course, we should control red nodes. If there are 20+ red dot, there would be a problem.
- android:background:”@null” // xml
- Be flat
- Avoid unnecessary layouts
- Use RelativeLayout instead of LinearLayout. Rendering is faster at RelativeLayout according to LinearLayout