Melhorar o Xamarin.Forms desempenho do aplicativo
Evoluir 2016: Otimizando o desempenho do aplicativo com Xamarin.Forms
O baixo desempenho de aplicativo se apresenta de várias maneiras. Ele pode fazer com que o aplicativo pareça não responder, deixar a rolagem lenta e reduzir a vida útil da bateria do dispositivo. No entanto, a otimização do desempenho engloba mais do que apenas a implementação de um código eficiente. A experiência do usuário quanto ao desempenho do aplicativo também deve ser considerada. Por exemplo, garantir que as operações sejam executadas sem impedir o usuário de realizar outras atividades pode ajudar a melhorar a experiência do usuário.
Há muitas técnicas para aumentar o desempenho e o desempenho percebido de Xamarin.Forms aplicativos. Coletivamente, essas técnicas podem reduzir de forma considerável a quantidade de trabalho que está sendo executado por uma CPU e a quantidade de memória consumida por um aplicativo.
Observação
Antes de ler esse artigo, você deve primeiro ler Desempenho de plataforma cruzada, que discute técnicas específicas sem plataforma para melhorar o uso de memória e o desempenho de aplicativos criados usando a plataforma Xamarin.
Habilitar o compilador de XAML
Opcionalmente, XAML pode ser compilado direto na IL (linguagem intermediária) com o compilador XAML (XAMLC). XAMLC oferece vários benefícios:
- Executa verificação de tempo de compilação de XAML, notificando o usuário de quaisquer erros.
- Elimina parte da carga e do tempo de instanciação para elementos XAML.
- Ajuda a reduzir o tamanho do arquivo do assembly final não incluindo mais arquivos .XAML.
O XAMLC está habilitado por padrão em novas Xamarin.Forms soluções. No entanto, talvez seja necessário habilitá-lo em soluções mais antigas. Para saber mais, consulte Compilação de XAML.
Usar associações compiladas
As associações compiladas melhoram o desempenho da associação de dados em Xamarin.Forms aplicativos resolvendo expressões de associação em tempo de compilação, em vez de em runtime com reflexão. A compilação de uma expressão de associação gera código compilado que normalmente resolve uma associação 8 a 20 vezes mais rápido do que ao usar uma associação clássica. Para saber mais, confira Associações compiladas.
Reduzir associações desnecessárias
Não use associações para conteúdo que pode ser facilmente definido estaticamente. Não há nenhuma vantagem em associar dados que não precisam ser associados, pois associações não são econômicas. Por exemplo, a configuração Button.Text = "Accept"
tem menos sobrecarga do que a associação Button.Text
a uma propriedade viewmodel string
com o valor "Accept".
Usar renderizadores rápidos
Renderizadores rápidos reduzem a inflação e renderizam os custos de Xamarin.Forms controles no Android, nivelando a hierarquia de controle nativa resultante. Isso aprimora o desempenho criando menos objetos, o que resulta em uma árvore visual menos complexa e em menos uso de memória.
A partir da Xamarin.Forms 4.0, todos os aplicativos direcionados FormsAppCompatActivity
usam renderizadores rápidos por padrão. Para obter mais informações, veja Renderizadores Rápidos.
Habilitar o rastreamento de inicialização no Android
A compilação AOT (Ahead of Time) no Android minimiza a sobrecarga e o uso de memória da inicialização de aplicativo JIT (Just-in-Time), com o custo de criar um APK muito maior. Uma alternativa é usar o rastreamento de inicialização, que proporciona uma compensação entre o tamanho do APK do Android e o tempo de inicialização, quando comparado à compilação AOT convencional.
Em vez de compilar o máximo possível do aplicativo para código não gerenciado, o rastreamento de inicialização compila apenas o conjunto de métodos gerenciados que representam as partes mais caras da inicialização do aplicativo em um aplicativo em branco Xamarin.Forms . Essa abordagem resulta na redução do tamanho do APK, quando comparado à compilação AOT convencional, enquanto ainda fornece melhorias de inicialização semelhantes.
Habilitar a compactação de layout
A compactação de layout remove os layouts especificados da árvore visual, em uma tentativa de melhorar o desempenho de renderização da página. O benefício de desempenho que isso oferece varia dependendo da complexidade de uma página, da versão do sistema operacional que está sendo usado e do dispositivo no qual o aplicativo está sendo executado. No entanto, os maiores ganhos de desempenho serão observados em versões mais antigas. Para obter mais informações, confira Layout de Compactação.
Escolher o layout correto
Um layout que é capaz de exibir vários filhos, mas que tem apenas um único filho, é um desperdício. Por exemplo, o seguinte exemplo de código mostra um StackLayout
com um único filho:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DisplayImage.HomePage">
<StackLayout>
<Image Source="waterfront.jpg" />
</StackLayout>
</ContentPage>
Isso é um desperdício e o elemento StackLayout
deve ser removido, conforme mostrado no exemplo de código a seguir:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DisplayImage.HomePage">
<Image Source="waterfront.jpg" />
</ContentPage>
Além disso, não tente reproduzir a aparência de um layout específico usando combinações de outros layouts, pois isso resulta na execução de cálculos de layout desnecessários. Por exemplo, não tente reproduzir um layout Grid
usando uma combinação de instâncias StackLayout
. O código a seguir mostra um exemplo dessa má prática:
<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>
Isso é um desperdício porque cálculos de layout desnecessário são executados. Em vez disso, o layout desejado pode ser melhor obtido usando um Grid
, conforme mostra o exemplo de código a seguir:
<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>
Otimizar o desempenho do layout
Para obter o melhor desempenho possível do layout, siga estas diretrizes:
- Reduzir a profundidade das hierarquias de layout especificando os valores de propriedade
Margin
, permitindo a criação de layouts com menos exibições de encapsulamento. Para saber mais, consulte Margens e preenchimento. - Ao usar um
Grid
, tente garantir que o menor número de linhas e colunas possível seja definido para o tamanhoAuto
. Cada linha ou coluna dimensionada automaticamente fará o mecanismo de layout realizar cálculos de layout adicionais. Em vez disso, use linhas e colunas de tamanho fixo, se possível. Como alternativa, defina linhas e colunas para ocupar uma quantidade proporcional de espaço com o valor de enumeraçãoGridUnitType.Star
, desde que a árvore pai siga essas diretrizes de layout. - Não defina as propriedades
VerticalOptions
eHorizontalOptions
de um layout, a menos que necessário. Os valores padrão deLayoutOptions.Fill
eLayoutOptions.FillAndExpand
permitem a melhor otimização de layout. Alterar essas propriedades tem um custo e consome memória, mesmo ao configurá-las como os valores padrão. - Evite usar um
RelativeLayout
sempre que possível. Isso resultará em a CPU precisar realizar significativamente mais trabalho. - Ao usar um
AbsoluteLayout
, evite usar a propriedadeAbsoluteLayout.AutoSize
sempre que possível. - Ao usar um
StackLayout
, garanta que apenas um filho seja definido comoLayoutOptions.Expands
. Essa propriedade garante que o filho especificado ocupe o maior espaço que oStackLayout
pode dar a ele e é um desperdício executar esses cálculos mais de uma vez. - Evite chamar os métodos da classe
Layout
, uma vez que eles resultam na execução de cálculos de layout de alto custo. Em vez disso, é provável que o comportamento de layout desejado possa ser obtido configurando as propriedadesTranslationX
eTranslationY
. Como alternativa, subclasse da classeLayout<View>
para obter o comportamento de layout desejado. - Não atualize nenhuma instância
Label
com mais frequência do que o necessário, pois a alteração do tamanho do rótulo pode resultar no recálculo de todo o layout de tela. - Não defina a propriedade
Label.VerticalTextAlignment
, a menos que necessário. - Defina o
LineBreakMode
de quaisquer instânciasLabel
comoNoWrap
sempre que possível.
usar programação assíncrona
A capacidade de resposta geral do aplicativo pode ser aprimorada e os gargalos de desempenho geralmente são evitados usando programação assíncrona. No .NET, o TAP (Padrão Assíncrono baseado em tarefa) é o padrão de design recomendado para operações assíncronas. No entanto, o uso incorreto do TAP pode resultar em aplicativos nãoperformantes. Portanto, as diretrizes a seguir devem ser seguidas ao usar o TAP.
Conceitos básicos
Entenda o ciclo de vida da tarefa, que é representado pela
TaskStatus
enumeração . Para obter mais informações, consulte O significado de TaskStatus e Task status.Use o
Task.WhenAll
método para aguardar assíncronamente a conclusão de várias operações assíncronas, em vez de individualmenteawait
uma série de operações assíncronas. Para obter mais informações, consulte Task.WhenAll.Use o
Task.WhenAny
método para aguardar assíncronamente a conclusão de uma das várias operações assíncronas. Para obter mais informações, consulte Task.WhenAny.Use o
Task.Delay
método para produzir umTask
objeto que é concluído após a hora especificada. Isso é útil para cenários como sondagem de dados e atraso na manipulação da entrada do usuário por um tempo predeterminado. Para obter mais informações, consulte Task.Delay.Execute operações de CPU síncronas intensivas no pool de threads com o
Task.Run
método . Esse método é um atalho para oTaskFactory.StartNew
método , com os argumentos mais ideais definidos. Para obter mais informações, consulte Task.Run.Evite tentar criar construtores assíncronos. Em vez disso, use eventos de ciclo de vida ou lógica de inicialização separada para fazer a inicialização corretamente
await
. Para obter mais informações, consulte Construtores assíncronos no blog.stephencleary.com.Use o padrão de tarefa lenta para evitar aguardar a conclusão de operações assíncronas durante a inicialização do aplicativo. Para obter mais informações, consulte AsyncLazy.
Crie um wrapper de tarefas para operações assíncronas existentes, que não usam o TAP, criando
TaskCompletionSource<T>
objetos. Esses objetos obtêm os benefícios daTask
programação e permitem que você controle o tempo de vida e a conclusão do associadoTask
. Para obter mais informações, consulte A Natureza de TaskCompletionSource.Retornar um
Task
objeto, em vez de retornar um objeto aguardadoTask
, quando não houver necessidade de processar o resultado de uma operação assíncrona. Isso é mais eficaz devido à menor alternância de contexto que está sendo executada.Use a biblioteca de fluxo de dados TPL (Biblioteca Paralela de Tarefas) em cenários como o processamento de dados conforme eles se tornam disponíveis ou quando você tiver várias operações que devem se comunicar entre si de forma assíncrona. Para obter mais informações, consulte Fluxo de dados (Biblioteca Paralela de Tarefas).
Interface do usuário
Chame uma versão assíncrona de uma API, se ela estiver disponível. Isso manterá o thread de interface do usuário desbloqueado, o que ajudará a melhorar a experiência do usuário com o aplicativo.
Atualize os elementos da interface do usuário com dados de operações assíncronas no thread da interface do usuário para evitar que exceções sejam geradas. No entanto, as atualizações para a
ListView.ItemsSource
propriedade serão automaticamente marshaladas para o thread da interface do usuário. Para obter informações sobre como determinar se o código está em execução no thread da interface do usuário, consulte Xamarin.Essentials: MainThread.Importante
Todas as propriedades de controle atualizadas por meio da associação de dados serão automaticamente marshaladas para o thread da interface do usuário.
Tratamento de erros
- Saiba mais sobre o tratamento de exceções assíncronas. Exceções sem tratamento geradas por código que está sendo executado de forma assíncrona são propagadas de volta para o thread de chamada, exceto em determinados cenários. Para obter mais informações, consulte Tratamento de exceções (Biblioteca Paralela de Tarefas).
- Evite criar
async void
métodos e, em vez disso, crieasync Task
métodos. Elas permitem facilitar o tratamento de erros, a composição e a capacidade de teste. A exceção a essa diretriz são manipuladores de eventos assíncronos, que devem retornarvoid
. Para obter mais informações, consulte Evitar Void Assíncrono. - Não misture o bloqueio e o código assíncrono chamando os
Task.Wait
métodos ,Task.Result
ouGetAwaiter().GetResult
, pois eles podem resultar em deadlock. No entanto, se essa diretriz precisar ser violada, a abordagem preferencial será chamar oGetAwaiter().GetResult
método porque preserva as exceções de tarefa. Para obter mais informações, consulte Async All the Way and Task Exception Handling in .NET 4.5. - Use o
ConfigureAwait
método sempre que possível para criar código sem contexto. O código sem contexto tem melhor desempenho para aplicativos móveis e é uma técnica útil para evitar deadlock ao trabalhar com uma base de código parcialmente assíncrona. Para obter mais informações, consulte Configurar contexto. - Use tarefas de continuação para funcionalidades como lidar com exceções geradas pela operação assíncrona anterior e cancelar uma continuação antes de ser iniciada ou enquanto ela está em execução. Para obter mais informações, consulte Encadeamento de tarefas usando tarefas contínuas.
- Use uma implementação assíncrona quando operações assíncronas
ICommand
forem invocadas doICommand
. Isso garante que quaisquer exceções na lógica de comando assíncrona possam ser tratadas. Para obter mais informações, consulte Programação assíncrona: padrões para aplicativos MVVM assíncronos: comandos.
Escolha o contêiner de injeção de dependência com cuidado
Contêineres de injeção de dependência introduzem restrições de desempenho adicionais em aplicativos móveis. Efetuar o registro e a resolução de tipos usando um contêiner tem um custo de desempenho devido ao uso da reflexão pelo contêiner para criar cada tipo, especialmente se as dependências estiverem sendo reconstruídas para cada navegação de página no aplicativo. Se houver muitas dependências ou se elas forem profundas, o custo da criação poderá aumentar significativamente. Além disso, o registro de tipo, que geralmente ocorre durante a inicialização do aplicativo, pode ter um impacto perceptível sobre o tempo de inicialização dependendo do contêiner que está sendo usado.
Como alternativa, a injeção de dependência pode se tornar mais eficaz por meio da implementação manual usando fábricas.
Criar aplicativos de Shell
Xamarin.Forms Os aplicativos shell fornecem uma experiência de navegação opinativa com base em submenus e guias. Se a experiência do usuário do aplicativo puder ser implementada com Shell, será benéfico fazê-lo. Aplicativos de Shell ajudam a evitar uma experiência de inicialização ruim, pois as páginas são criadas sob demanda em resposta à navegação, e não na inicialização do aplicativo, o que ocorre com aplicativos que usam uma `TabbedPage'. Para obter mais informações, consulte Xamarin.Forms Shell.
Usar CollectionView em vez de ListView
CollectionView
é uma exibição para apresentar listas de dados usando especificações de layout diferentes. Ela fornece uma alternativa mais flexível e de alto desempenho a ListView
. Para obter mais informações, consulte Xamarin.Forms CollectionView.
Otimizar o desempenho da ListView
Ao usar ListView
, há várias experiências de usuário que devem ser otimizadas:
- Inicialização – o intervalo de tempo que começa quando o controle é criado e termina quando itens são mostrados na tela.
- Rolagem – a capacidade de rolar pela lista e garantir que a interface do usuário não fique atrasada com relação a gestos de toque.
- Interação para adicionar, excluir e selecionar itens.
O controle ListView
requer um aplicativo para fornecer dados e modelos de célula. Como isso é feito terá um grande impacto sobre o desempenho do controle. Para obter mais informações, consulte Desempenho da ListView.
Otimizar os recursos de imagem
Exibir recursos de imagem pode aumentar significativamente o volume de memória de um aplicativo. Portanto, eles só devem ser criados quando necessário e devem ser liberados assim que o aplicativo não exigi-los mais. Por exemplo, se um aplicativo estiver exibindo uma imagem lendo seus dados de um fluxo, certifique-se de que esse fluxo seja criado somente quando necessário e liberado quando não for mais necessário. Isso pode ser feito criando o fluxo quando a página é criada ou quando o evento Page.Appearing
é acionado e, em seguida, descartando o fluxo quando o evento Page.Disappearing
é acionado.
Ao fazer o download de uma imagem para exibição com o método ImageSource.FromUri
, armazene em cache a imagem baixada garantindo que a propriedade UriImageSource.CachingEnabled
esteja definida como true
. Para saber mais, consulte Trabalhando com imagens.
Para saber mais, consulte Otimizar recursos de imagem.
Reduzir o tamanho da árvore visual
Reduzir o número de elementos em uma página tornará a renderização da página mais rápida. Há duas técnicas principais para realizar essa tarefa. A primeira é ocultar elementos que não estão visíveis. A propriedade IsVisible
de cada elemento determina se o elemento deve fazer parte da árvore visual ou não. Portanto, se um elemento não estiver visível porque está oculto atrás de outros elementos, remova o elemento ou defina sua propriedade IsVisible
como false
.
A segunda técnica é remover elementos desnecessários. Por exemplo, o código a seguir mostra um layout da página contendo múltiplos objetos Label
:
<StackLayout>
<StackLayout Padding="20,20,0,0">
<Label Text="Hello" />
</StackLayout>
<StackLayout Padding="20,20,0,0">
<Label Text="Welcome to the App!" />
</StackLayout>
<StackLayout Padding="20,20,0,0">
<Label Text="Downloading Data..." />
</StackLayout>
</StackLayout>
O mesmo layout da página poderá ser mantido com uma contagem de elementos reduzida, conforme mostrado no exemplo de código a seguir:
<StackLayout Padding="20,35,20,20" Spacing="25">
<Label Text="Hello" />
<Label Text="Welcome to the App!" />
<Label Text="Downloading Data..." />
</StackLayout>
Reduzir o tamanho do dicionário de recursos do aplicativo
Todos os recursos usados em todo o aplicativo devem ser armazenados no dicionário de recursos do aplicativo para evitar duplicação. Isso ajudará a reduzir a quantidade de XAML que precisa ser analisada em todo o aplicativo. O seguinte exemplo de código mostra o recurso HeadingLabelStyle
, que é usado em todo o aplicativo e então é definido no dicionário de recursos do aplicativo:
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Resources.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="HeadingLabelStyle" TargetType="Label">
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="Red" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
No entanto, o XAML específico de uma página não deve ser incluído no dicionário de recursos do aplicativo, uma vez que os recursos então serão analisados na inicialização do aplicativo, em vez de quando exigido por uma página. Se um recurso for usado por uma página que não seja a página de inicialização, ele deverá ser colocado no dicionário de recursos para essa página, ajudando, assim, a reduzir o XAML analisado quando o aplicativo é iniciado. O seguinte exemplo de código mostra o recurso HeadingLabelStyle
, que está em apenas uma única página e então é definido no dicionário de recursos da página:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.HomePage"
Padding="0,20,0,0">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="HeadingLabelStyle" TargetType="Label">
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="FontSize" Value="Large" />
<Setter Property="TextColor" Value="Red" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
Para saber mais sobre os recursos de aplicativo, consulte Estilos de XAML.
Usar o padrão de renderizador personalizado
A maioria das Xamarin.Forms classes de renderizador expõe o OnElementChanged
método , que é chamado quando um Xamarin.Forms controle personalizado é criado para renderizar o controle nativo correspondente. Então, classes de renderizador personalizadas em cada projeto da plataforma substituem esse método para instanciar e personalizar o controle nativo. O método SetNativeControl
é usado para instanciar o controle nativo e esse método também atribuirá a referência de controle à propriedade Control
.
No entanto, em algumas circunstâncias, o método OnElementChanged
pode ser chamado várias vezes. Portanto, para evitar perdas de memória, que podem ter um impacto no desempenho, é necessário ter cuidado ao instanciar um novo controle nativo. A abordagem a ser usada ao instanciar um novo controle nativo em um renderizador personalizado é mostrada no exemplo de código a seguir:
protected override void OnElementChanged (ElementChangedEventArgs<NativeListView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null)
{
// Unsubscribe from event handlers and cleanup any resources
}
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control with the SetNativeControl method
}
// Configure the control and subscribe to event handlers
}
}
Um novo controle nativo deve ser instanciado apenas uma vez, quando a propriedade Control
é null
. Além disso, o controle só deve ser criado, configurado e manipuladores de eventos inscritos quando o renderizador personalizado é anexado a um novo Xamarin.Forms elemento. Da mesma forma, a inscrição de quaisquer manipuladores de evento inscritos só deve ser cancelada quando o elemento ao qual o renderizador está anexado for alterado. Adotar essa abordagem ajudará a criar um renderizador personalizado de desempenho eficiente que não sofra perdas de memória.
Importante
O método SetNativeControl
só deverá ser invocado se a propriedade e.NewElement
não for null
e a propriedade Control
for null
.
Para obter mais informações sobre renderizadores personalizados, consulte Como personalizar controles em cada plataforma.