
Introduction
Welcome to the third and final article of the Android Performance Optimization series! In the first article, we explored the fundamentals of performance optimization, focusing on CPU and battery. In the second article, we deep-dived into the crucial topic of RAM memory optimization and memory leaks. Now, it’s time to focus on UI optimization and rendering efficiency.
Traditionally, Android used the XML-based way to build UIs; in recent years Google followed the trend of declarative UI creation and released Jetpack Compose. Even though new projects tend to follow the Jetpack Compose approach there is still an extensive amount of Android apps that are based on the XML approach. Therefore, in this article we will include optimization techniques for both.
We will start with techniques that are applicable to both approaches, then we will continue with the XML, and finally, we will focus on optimizing Jetpack Compose UIs.
By implementing the practical techniques presented here, you can ensure your app delivers a smooth, responsive user experience.
Common techniques
Avoid UI Overdraw
Overdraw happens when the same pixel on the screen is drawn multiple times. This is common with overlapping UI elements. While the system needs to render elements in a specific order for transparency effects, excessive overdraw wastes GPU power and may cause a slow rendering time and responsiveness.
With XML, we can introduce UI overdraw when using FrameLayout and with Jetpack Compose when adding views on a Surface on top of each other.
To identify and fix overdraw, enable developer options and use the overdraw visualization tool. This will highlight areas where pixels are being drawn unnecessarily, allowing you to optimize your UI layout and element usage for better performance.
In order to enable the overdraw visualization tool, open up your device or emulator and navigate to the developer options. Enable the option Debug GPU overdraw.

Now, you can run your application, and you will notice all the overdraw areas based on the color code. For example, in the screenshot below, the app bar is drawn on top of the screen, and we can see there is an overdraw. The same is happening with the app bar options.

Furthermore, if we drag the pull-to-refresh component, we will see the emulator indicating an overdrawn element.

Obviously, you can't avoid all overdraw cases, some, like this one, exist by design. But you can identify and fix the unintended ones.
Use animations sparingly
Animations are resource intensive. And while they can add polish to your app, it's crucial to use them sparingly. Excessive animations can overwhelm users and strain system resources. Think of them as sprinkles on a cupcake - a little adds delight, but too much can overpower the taste. Use animations strategically to highlight key actions or guide users through a process, but prioritize clarity and performance over constant movement.
Avoid processing in the UI thread.
This is probably the most important technique for building a responsive application and avoiding ANRs. An ANR (Application not responsive) occurs when you keep the Main thread busy for too long. In those cases, the OS prompts the user to kill the application. This is the worst possible UX other than an application crash.
Heavy data processing, as well as tasks like HTTP requests and database queries, must always happen on background threads. There are several techniques for performing those tasks in background threads, like using background services, async tasks, and more modern techniques like Reactive Programming or Kotlin Coroutines.
Regardless of which one you choose to perform your background work, the important thing is to avoid doing it in the Main thread.
Profile the Hardware UI rendering.
Unlike the GPU, RAM and battery consumption, we cannot monitor the UI performance from Android Studio. Instead we need to go to the developer options of our device or emulator and enable the Profile HWUI Rendering option.

Here I prefer to use the option On screen as bars. Once you click on this option, you will start seeing a bar chart on your screen that looks like this:
You can interpret the bar chart as follows:
- For each visible application, the tool displays a graph.
- Each vertical bar along the horizontal axis represents a frame, and the height of each vertical bar represents the amount of time the frame took to render (in milliseconds).
- The horizontal green line represents 16.67 milliseconds. To achieve 60 frames per second which assures an optimal UX, the vertical bar for each frame needs to stay below this line. Any time a bar surpasses this line, there may be pauses in the animations.
- The tool highlights frames that exceed the 16.67 millisecond threshold by making the corresponding bar wider and less transparent.
- Each bar has colored components that map to a stage in the rendering pipeline. The number of components vary depending on the API level of the device.
For example:

In this application, the first line, the longest one, represents the application startup. The second big line in the middle occurred when I navigated from one screen to another and this caused a rendering overload.
Using this tool, you can identify the most GPU resource-heavy screens and transitions and start focusing on optimizing those.
For more info regarding the HWUI profiling, you can visit the official documentation here: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering
