Rendering — Phase By Phase
Creating a great UI experience is a main goal for us as developers. In order to do so, first step is to understand how does the system work, so we can better coordinate with it, benefit from its advantages, and avoid its flaws.
After overviewing how does the system draws on screen, let’s break down the rendering process into phases:
Before explaining the phases, in order to see it in action (and profile our app :) ), we can use the GPU Profiling Tool:
Launching GPU Profiling Tool
Settings → Developer Options → Profile GPU Rendering → On Screen as bars.
Outputs this to the screen :
Each bar represents a frame, and its height indicate render time.
The horizontal green line represents the 16 ms benchmark.
Crossing it means skipping frames! Which means hurting our users’ experience. (which is the last thing we want!)
A bar which crossed it will appear bolder to better detect its details.
Each color on the bar represents a different phase on the rendering process, as shown above, and will be explained below.
The higher the color — the more time spent on the phase.
Overviewing The Rendering Phases
1. Vsync / Misc Time
Misc = (vsync intended timestamp) — (vsync actual received timestamp)
It represents work on UI thread between 2 frames.
If it is not equal 0, it means that there was work on the UI thread that caused delay in vsync, and therefore skipped frames.
Then, we’ll see the famous Choreographer log : “Missed vsync by XX ms skipping XX frames”
High? Check for work that can be moved off UI Thread
2. Input Handling
Execution of our application code inside input event callbacks. (onClick()s, onTouch()s, etc…)
High? Check the work done on those callbacks, to optimize / offloaded to another thread.
Evaluate all running animators, so they know what is the view’s state on current frame.
Often we use: object animator, view property animator and transitions
High? (2 ms and up)
Check if unintended work happens as a result of updating view properties.
Check your custom animators
4. Measure / Layout
Executing measure (understand size of views) and layout (understand position of views) callbacks for all needed views.
More on that later :)
High? In few words:
Try to flatten view hierarchy or reduce its complexity
Beware of double layout taxation and use view object to reduce it.
Create or update all display lists, for all views that needs an update.
Remember — we don’t actually see anything on screen yet. OnDraw() prepares the canvas, meaning collects the commands which will compose the display list that will be displayed on screen only later.
Complex onDraw() logic
Many views invalidated
Check out our last slides on improving this phase
6. Sync & upload
Time to upload bitmap information to the GPU.
The UI thread passes all the resources to the RenderThread.
RenderThread (added in Android L) is a thread helping the busy UI thread with the conversion of display lists to OpenGL commands, and sends them to the GPU. During which, the UI thread can start processing next frame.
There are many resources to pass on. Usually it is due to too many images used or using too big of an image
Reduce visible images
Resize large images before uploading
Check out our last slides on bitmap abuse
7. Command Issue
The execution phase: Android’s 2D renderer issues commands to OpenGL to actually draw all display lists on screen.
Check some complex view (most likely a custom view)
Maybe many views redrawing, due to:
Animation (for example, if a view is rotated or translated, we might need to redraw other views underneath it)
8. Swap Buffers
The CPU tells the GPU that it’s done rendering a frame.
Then, the CPU blocks the UI thread until the GPU finishes its work and acknowledges that the command was received.
GPU is working hard! Could be due to many complex views that require a lot of OpenGL rendering commands processing.
We overviewed the rendering phases, noted reasons for delays and met a very colorful profiling tool.