Tips and Tricks to Boost Performance in Your Xamarin Apps

Alan Grgic

@spellgrgicright

small steps for big gains

A Little About Me

- Software Development

- Board Games

- Metal Music

- Corgis

- Beverages

Native Apps are Fast, Right?

Machines do as they're told...

Unnecessary or misused Abstractions

Machines do as they're told!

Performance Win Opportunities

- HttpClient

- async/await

- Compiler settings

- Image Optimizations

- Binding Optimizations

- Layout Optimizations

HttpClient: avoiding socket exhaustion

Understand Connection: keep-alive

HttpClient: Don't do this!

- you miss out on the benefits of keep-alive even though it is being used

- a new socket is opened each time a request is made

- each of those sockets stays open because of keep-alive

- executing lots of requests quickly makes the server run out of sockets

- your app is doing unnecessary work and your server is weeping in agony
 

using (var client = new HttpClient())
{
    // do stuff with the http client
}

HttpClient: Instead, do this


public class MyClient
{
    private static HttpClient _client = new HttpClient();

    public Task<HttpResponseMessage> Get(string uri)
    {
        return _client.GetAsync(uri);
    }
}
  • HttpClient is thread safe and designed to be created once and re-used
  • Your app will do less network work by leveraging keep-alive
     
  • Your app will do less work by not newing up clients all the time
     
  • The server will be thankful for this completely normal experience

HttpClient: Use gzip


public class MyClient
{
    private static HttpClient _client = BuildClient();
    
    public Task<HttpResponseMessage> Get(string uri)
    {
        return _client.GetAsync(uri);
    }
    
    private static HttpClient BuildClient(){
    {
        var httpClient = new HttpClient();
        var header = new StringWithQualityHeaderValue("gzip");
        httpClient.DefaultRequestHeaders.AcceptEncoding.Add(header);
        return httpClient;
    }
}
  • gzip is a compression format supported by many servers
     
  • responses will be smaller and thus download faster
     
  • HttpClient decompresses gzipped responses automatically

HttpClient: Don't do this


public class MyClient
{
    private static HttpClient _client = new HttpClient();
    
    public async Task<Stream> GetStream(string uri)
    {
        var response = await _httpClient.GetAsync("http://abigfile.com");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStreamAsync();
    }
}
  • GetAsync will block until the entire big file is downloaded
     
  • Depending on how big that file is:
    • ​This could time out (not unlikely, default timeout is 100 sec)
    • OutOfMemoryException could occur

HttpClient: Instead, do this


public class MyClient
{
    private static HttpClient _client = new HttpClient();
    
    public async Task<Stream> GetStream(string uri)
    {
        var response = await _httpClient.GetAsync("http://abigfile.com",
                                                  HttpCompletionOption.ResponseHeadersRead);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStreamAsync();
    }
}
  • Response will return once just the headers are downloaded
    • ​The response content will be streamed as we intended
       
  • Size of the file will no longer cause timeouts
     
  • OutOfMemoryException not an issue either
    • ​Unless we do something silly like stream into memory

HttpClient: Similarly, do this


public class MyClient
{
    private static HttpClient _client = new HttpClient();
    private static JsonSerializer _serializer = new JsonSerializer();
    
    public async Task<T> Get(string uri)
    {
        var response = await _httpClient.GetAsync("http://somejson.com",
                                                  HttpCompletionOption.ResponseHeadersRead);
        response.EnsureSuccessStatusCode();
        
        using (var stream = await response.Content.ReadAsStreamAsync())
        using (var reader = new StreamReader(stream))
        using (var json = new JsonTextReader(reader))
        {
            return _serializer.Deserialize<T>(json);
        }
    }
}
  • With JsonConvert.DeserializeObject<T>(obj)  (the typical approach) :
    • ​full JSON string itself would be in memory
    • at the same time, properties would be duplicated on the POCO
  • Streaming allows only one JSON property to be in memory at a time
  • Also consider excluding properties you don't use from POCOs

HttpClient: Use the Native Handlers

  • On by default now, but older projects might still not be using them
     
  • Makes the message handler use the OS's native SDK instead of Mono
     
  • Significantly faster, but not all features of HttpClient are implemented

Async/Await

Getting multiple things done at a time

Async/Await: Don't do this

protected override async void OnAppearing()
{
    await _viewModel.LoadOrderInfoAsync();
}
  • async void is a DEATHTRAP of USELESSNESS and PAIN
    • ​void methods cannot be awaited
       
    • wrapping calls to them in a try/catch doesn't do anything
       
    • unhandled Exceptions that happen in them will crash your app
       
    • the call stack will show that the exception occurred somewhere in the void method, even though the actual exception may have been thrown several levels deeper.

Async/Await: Instead, do this

protected override void OnAppearing()
{   // "fire and forget"
    _viewModel.LoadOrderInfoAsync().WithErrorHandling();
}

public static class TaskExtensions
{
    // one async void to rule them all
    public static async void WithErrorHandling(this Task task)
    {
        try
        {
            await task;
        }
        catch (Exception ex)
        {
            // log and show a sytem dialog or something
        }
    }
}
  • Your app won't crash
     
  • Logs will contain information that is actually helpful
     
  • Your code will not be littered with try/catch blocks everywhere

Async/Await: Don't do this

public async Task LoadOrderAsync()
{
    await _viewModel.LoadBasicOrderInfoAsync();
    await _viewModel.LoadOrderItemsAsync();
}
  • Order items will not begin being retrieved until basic info is done!
public void LoadOrderAsync()
{
    _viewModel.LoadBasicOrderInfoAsync();
    _viewModel.LoadOrderItemsAsync();
}
  • Orders won't be done loading after this finishes execution!
public void LoadOrderAsync()
{
    _viewModel.LoadBasicOrderInfoAsync().Wait();
    _viewModel.LoadOrderItemsAsync().Wait();
}
  • Calling thread will be blocked! If it's the UI thread, this is e​ven worse.

Async/Await: Instead, do this

public async Task LoadOrderAsync()
{
    var order = _viewModel.LoadBasicOrderInfoAsync();
    var items = _viewModel.LoadOrderItemsAsync();
    await Task.WhenAll(order, items);
}
  • Order info and items will be requested at the same time
     
  • Execution doesn't finish until both info and items are done
     
  • Calling thread is not blocked

Async/Await: avoid "return await"


// instead of this
public async Task GetOrderAsync()
{
    return await _myClient.GetAsync<Order>("https://getymyorder.com");
}

// try this
public Task GetOrderAsync()
{
    return _myClient.GetAsync<Order>("https://getymyorder.com");
}
  • Unnecessary use of "async" causes:
    • the compiler to generate unneeded state machine code
       
    • your app size to increase
       
    • tasks that are already to done to be treated as if they are not
       
  • Always use await inside "using" and "try" blocks

Async/Await: Use .ConfigureAwait(false)

public async Task LoadOrderAsync()
{
    var order = _viewModel.LoadBasicOrderInfoAsync();
    var items = _viewModel.LoadOrderItemsAsync();
    await Task.WhenAll(order, items).ConfigureAwait(false);
}
  • EXCEPT when calling methods from the UI thread
    • ​e.g. OnAppearing or in Device.BeginInvokeOnMainThread
       
  • Does the thread that does everything after the await have to be the same as the one that started the await?
    • ​If not, use ConfigureAwait(false)
    • Usually, the only time we don't want this is when calling from/returning to the UI thread

Compiler Optimizations

Understanding the Linker

  • Tree-Shaking
     
  • Mono and Xamarin are like, really big
     
  • Not all of their code will be used
     
  • The Linker:
    • Detects what parts of code are actually used
       
    • Makes sure only the parts that are actually needed get in
       
    • Decreases app size and startup time

Linker Settings

Setting Behavior Ideal For
Don't Link Include all of Mono and Xamarin Debugging
Link SDK Assemblies Remove unused Xamarin and Mono code "safe/lazy" release builds
Link All Assemblies Remove all unused code (from all 3rd party libraries, plus your own code)* optimized release builds

* Including stuff you might not want removed. Use Preserve attributes!

Android: Use d8 and r8

  • Android is JIT compiled by default
     
  • We will usually end up shipping some Java jars with our app
    • Typically this is the Android support libraries
    • If you don't include those, you're gonna have a bad time
       
  • You can think of:
    • r8 as the Java "minifier"
    • d8 as the Java/dex "linker"

Android: AOT compilation

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> 
    <AndroidEnableProfiledAot>true</AndroidEnableProfiledAot> 
</PropertyGroup>

Image Optimizations

Use Vector Images

  • Smaller app sizes
     
  • Less memory usage
     
  • Scale with no loss in quality
     
  • Monochromatic flexibility
    • ​One image, different colors!

Use Vector Images: Android

In Android Studio...

Use Vector Images: Android

Use Vector Images: iOS

  • Asset catalog accepts pdfs in the Vector field
     
  • Several utilities to convert svgs to pdfs are available
    • ImageMagick
    • Inkscape

Cache Images Fetched Over Http

<Image 
	HorizontalOptions="CenterAndExpand"
	VerticalOptions ="CenterAndExpand">
	<Image.Source>
		<UriImageSource Uri="{Binding Image}" 
			CacheValidity="14" 
			CachingEnabled="true"/>
	</Image.Source>
</Image>

Binding Optimizations

Use Compiled Bindings

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.CompiledColorSelectorPage"
             Title="Compiled Color Selector">
    <StackLayout x:DataType="local:HslColorViewModel">
        <StackLayout.BindingContext>
            <local:HslColorViewModel Color="Sienna" />
        </StackLayout.BindingContext>
        <StackLayout Margin="10, 0">
            <Label Text="{Binding Name}" />
            <Slider Value="{Binding Hue}" />
            <Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
            <Slider Value="{Binding Saturation}" />
            <Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />
            <Slider Value="{Binding Luminosity}" />
            <Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
        </StackLayout>
    </StackLayout>    
</ContentPage>

8-20x Performance improvement, depending on Binding Mode!

Select the appropriate Binding Mode

Binding Mode Behavior
TwoWay data goes both ways
OneWay data goes from source to target
OneWayToSource data goes from target to source
OneTime data goes from source to target, but only when the BindingContext changes 

...but the fastest binding is no binding!

Default Binding Modes of Controls

  • Most properties (including Label.Text) default to OneWay
     
  • Notable Two-Way Default Properties:
    • DatePicker.Date
    • Text property of Editor, Entry, SearchBar
    • ListView.IsRefreshing
    • SelectedIndex and SelectedItem properties of Picker
    • Value property of Slider and Stepper
    • Switch.IsToggled
    • TimePicker.Time

Layout Optimizations

Minimize View Count!

Minimize Binding Usage!

Layouts: Don't Do This

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Details.HomePage"
             Padding="0,20,0,0">
    <StackLayout>
        <StackLayout Orientation="Horizontal">
            <Label Text="Name:" />
            <Entry Placeholder="Enter your name" />
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <Label Text="Age:" />
            <Entry Placeholder="Enter your age" />
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <Label Text="Occupation:" />
            <Entry Placeholder="Enter your occupation" />
        </StackLayout>
        <StackLayout Orientation="Horizontal">
            <Label Text="Address:" />
            <Entry Placeholder="Enter your address" />
        </StackLayout>
    </StackLayout>
</ContentPage>

Layouts: Instead, Do This

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Details.HomePage"
             Padding="0,20,0,0">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>
        <Label Text="Name:" />
        <Entry Grid.Column="1" Placeholder="Enter your name" />
        <Label Grid.Row="1" Text="Age:" />
        <Entry Grid.Row="1" Grid.Column="1" Placeholder="Enter your age" />
        <Label Grid.Row="2" Text="Occupation:" />
        <Entry Grid.Row="2" Grid.Column="1" Placeholder="Enter your occupation" />
        <Label Grid.Row="3" Text="Address:" />
        <Entry Grid.Row="3" Grid.Column="1" Placeholder="Enter your address" />
    </Grid>
</ContentPage>

Additional General Tips

  • Use Dependency Injection containers carefully
     
  • Minimize the use of static resources, especially at the App level
     
  • Avoid LINQ (favor simple foreach loops)
     
  • Use CollectionView over ListView
     
  • Use DataTemplateSelectors
     
  • Avoid RelativeLayout
     
  • Use Layout compression
     
  • Use "LineBreakMode=NoWrap" on Labels

Tips and Tricks to Boost Performance in Your Xamarin Apps

Alan Grgic

@spellgrgicright

Thanks!

Tips and Tricks to Boost Performance in Your Xamarin Apps

By Alan Grgic

Tips and Tricks to Boost Performance in Your Xamarin Apps

small steps for big gains

  • 1,271