Most of the time, we software developers focus on optimizing our code. We want it to be fast, easy to understand, and easy to maintain. Many of those codes are related to data handling, API queries, record updates, etc. But how many times do we worry about optimizing the user interface?
When we navigate to a screen in any application, we usually load a certain amount of information. If the load is slow, the first culprit is the process that retrieves the information that should be displayed on that page, but this is not always the case. Sometimes the slowness is due to the view not being programmed properly and doing something called “overdraw”.
“Overdrawing” occurs when your app draws the same pixel more than once within the same frame. Your app might be doing more rendering work than necessary, which can be a performance problem due to extra GPU effort to render pixels that won’t be visible to the user.
Fortunately, Android has some tools to identify and correct those scenarios.
The first one is the GPU Overdrawing Debug tool.
- To enable it, go to Settings and select Developer Options.
- Look for the Hardware accelerated rendering section, and select Debug GPU Overdraw.
- In the Debug GPU overdraw dialog, select Show overdraw areas.
With this, you can recognize where overdrawing is occurring in your application.
The second tool is the GPU Rendering Profiler
- You can enable it by going to Settings and Developer Options.
- In the Monitoring section, select Profile GPU Rendering or Profile HWUI rendering, depending on the version of Android running on the device.
- In the Profile GPU Rendering dialog, choose On screen as bars to overlay the graphs on the screen of your device.
Now we will show you an example with a custom application we wrote in Xamarin.Forms
This application shows a list of some employees here at Trailhead. As you can see, it shows their profile picture, name, position, and a brief summary of their expertise.
Despite being a simple application, the Overdrawing tool is showing almost everything as red. This is because we used a non-optimized layout to draw the view.
Let’s take a look at the Rendering Speed.
As we can see, it’s quite high. A bit over half of the available graphic processing power just to load and update this screen.
Now, let’s optimize the code from this:
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="https://xamarin.com/schemas/2014/forms" xmlns:local="clr-namespace:Demo.ViewModels" xmlns:model="clr-namespace:Demo.Models" xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml" x:Class="Demo.Views.ItemsPage" Title="{Binding Title}" x:Name="BrowseItemsPage"> <ContentPage.ToolbarItems> <ToolbarItem Text="Add" Command="{Binding AddItemCommand}" /> </ContentPage.ToolbarItems> <ContentPage.Content> <ListView ItemsSource="{Binding Items}" SelectionMode="None" HasUnevenRows="True"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Frame Padding="10" BackgroundColor="WhiteSmoke"> <StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand" BackgroundColor="WhiteSmoke"> <StackLayout Orientation="Horizontal"> <Frame CornerRadius="100" Padding="0" IsClippedToBounds="True" HorizontalOptions="Center"> <Image Source="{Binding AvatarUrl}" HeightRequest="100" /> </Frame> <StackLayout Padding="10"> <StackLayout Orientation="Horizontal"> <Label Text="{Binding Name}" BackgroundColor="WhiteSmoke" FontSize="21" /> <Label Text="{Binding LastName}" BackgroundColor="WhiteSmoke" FontSize="21" /> </StackLayout> <Label Text="{Binding Position}" BackgroundColor="WhiteSmoke" LineBreakMode="NoWrap" FontSize="21" Padding="5" /> </StackLayout> </StackLayout> <Label Text="{Binding Description}" BackgroundColor="WhiteSmoke" FontSize="18" Padding="5" /> </StackLayout> </Frame> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage.Content> </ContentPage>
To this:
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="https://xamarin.com/schemas/2014/forms" xmlns:local="clr-namespace:Demo.ViewModels" xmlns:model="clr-namespace:Demo.Models" xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml" x:Class="Demo.Views.ItemsPage" Title="{Binding Title}" x:Name="BrowseItemsPage"> <ContentPage.ToolbarItems> <ToolbarItem Text="Add" Command="{Binding AddItemCommand}" /> </ContentPage.ToolbarItems> <RefreshView x:DataType="local:ItemsViewModel" Command="{Binding LoadItemsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}"> <CollectionView x:Name="ItemsListView" ItemsSource="{Binding Items}" SelectionMode="None"> <CollectionView.ItemTemplate> <DataTemplate> <Grid x:DataType="model:Item" Padding="10"> <Grid.RowDefinitions> <RowDefinition Height="25" /> <RowDefinition Height="75" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="120" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" HorizontalOptions="Center" Source="{Binding AvatarUrl}"> <Image.Clip> <EllipseGeometry Center="50,50" RadiusX="50" RadiusY="50" /> </Image.Clip> </Image> <Label Grid.Row="0" Grid.Column="1" Text="{Binding FullName}" FontSize="21" /> <Label Grid.Row="1" Grid.Column="1" Text="{Binding Position}" FontSize="21" /> <Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Description}" FontSize="18" /> </Grid> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </RefreshView> </ContentPage>
In this simple example, we’ve changed only the view. Now let’s take a look at the results.
As you can see, we don’t have any red overlays now. It means we’re using the right amount of code needed to draw this page. How about the rendering speed?
We’ve cut in half the amount of required GPU time needed for our app. Even if you have high-end devices, this means our screen will load twice as fast compared to our previous version.
Why is it so important anyway?
- UX Response Time: You want your app to behave as smoothly as possible for a positive user experience. This translates to fast screen loading times. A “Loading …” tooltip can only get you so far.
- Scarce Resources: Memory, CPU time, and battery are scarce resources on mobile devices. Good handling of the rendering work can reduce how much of those your app uses. Most OSes alert users when an app is consuming a lot of resources so we should strive to stay away from that list.
- Benchmarking: This is a competitive market. Having a positive benchmark score due to the rendering optimization gives you an edge.
Thanks for reading!