Partilhar via


Melhore o Xamarin.Forms desempenho do aplicativo

Evolve 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.

Existem muitas técnicas para aumentar o desempenho e o desempenho percebido dos 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 que não são específicas a uma 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). O 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 é 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 tempo de execução com reflexão. Quando você compila uma expressão de associação, é gerado um código compilado que, normalmente, resolve uma associação de 8 a 20 vezes mais rápido do que uma associação clássica. Para obter mais informações, confira Associações compiladas do Xamarin.Forms.

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 associar Button.Text a uma propriedade string viewmodel com o valor “Accept”.

Usar renderizadores rápidos

Os renderizadores rápidos reduzem a inflação e os custos de renderização dos 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 versão 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 o uso de memória e a sobrecarga de inicialização de aplicativos JIT (Just-in-Time), mas cria um APK (pacote de aplicativo Android) muito maior. Uma alternativa é usar o rastreamento de inicialização, que proporciona uma compensação entre o tamanho do APK 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 tamanho Auto. 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ção GridUnitType.Star, desde que a árvore pai siga essas diretrizes de layout.
  • Não defina as propriedades VerticalOptions e HorizontalOptions de um layout, a menos que necessário. Os valores padrão de LayoutOptions.Fill e LayoutOptions.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 propriedade AbsoluteLayout.AutoSize sempre que possível.
  • Ao usar um StackLayout, garanta que apenas um filho seja definido como LayoutOptions.Expands. Essa propriedade garante que o filho especificado ocupe o maior espaço que o StackLayout 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 propriedades TranslationX e TranslationY. Como alternativa, subclasse da classe Layout<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âncias Label como NoWrap sempre que possível.

usar programação assíncrona

A capacidade de resposta geral do seu aplicativo pode ser aprimorada e os gargalos de desempenho geralmente evitados usando a 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 ineficientes. Portanto, as seguintes diretrizes devem ser seguidas ao usar o TAP.

Conceitos básicos

  • Compreenda o ciclo de vida da tarefa, que é representado pela enumeração TaskStatus. Para obter mais informações, confira O significado de TaskStatus e Status da tarefa.

  • Use o método Task.WhenAll para aguardar de maneira assíncrona a conclusão de várias operações assíncronas, em vez de await individualmente uma série de operações assíncronas. Para obter mais informações, confira Task.WhenAll.

  • Use o método Task.WhenAny para aguardar de maneira assíncrona a conclusão de uma das várias operações assíncronas. Para obter mais informações, confira Task.WhenAny.

  • Use o método Task.Delay para produzir um objeto Task que seja concluído após o horário especificado. Isso é útil para cenários como sondagem de dados e atraso no tratamento da entrada do usuário por um tempo predeterminado. Para obter mais informações, confira Task.Delay.

  • Execute operações síncronas intensivas de CPU no pool de threads com o método Task.Run. Esse método é um atalho para o método TaskFactory.StartNew, com os argumentos mais otimizados definidos. Para obter mais informações, confira Task.Run.

  • Evite tentar criar construtores assíncronos. Em vez disso, use eventos de ciclo de vida ou uma lógica de inicialização separada para await corretamente qualquer inicialização. Para obter mais informações, confira Construtores assíncronos em blog.stephencleary.com.

  • Use o padrão de tarefa lenta para evitar esperar que as operações assíncronas sejam concluídas durante a inicialização do aplicativo. Para obter mais informações, confira AsyncLazy.

  • Crie um wrapper de tarefas para as operações assíncronas existentes que não usam o TAP, criando objetos TaskCompletionSource<T>. Esses objetos obtêm os benefícios da programação do Task e permitem que você controle o tempo de vida e a conclusão da Task. Para obter mais informações, confira A natureza de TaskCompletionSource.

  • Retorne um objeto Task, em vez de retornar um objeto Task aguardado, quando não houver a necessidade de processar o resultado de uma operação assíncrona. Isso é mais eficiente devido à menor alternância de contexto executada.

  • Use a biblioteca de fluxo de dados TPL (biblioteca de paralelismo de tarefas) em cenários como processamento de dados, à medida que eles se tornarem disponíveis ou quando você tiver várias operações que devem se comunicar entre si de maneira assíncrona. Para obter mais informações, confira Fluxo de dados (biblioteca de paralelismo de tarefas).

UI

  • 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 os dados de operações assíncronas no thread da interface do usuário, a fim de evitar que exceções sejam geradas. No entanto, as atualizações na propriedade ListView.ItemsSource serão automaticamente empacotadas 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 que são atualizadas por meio da associação de dados serão automaticamente empacotadas para o thread da interface do usuário.

Tratamento de erros

  • Saiba mais sobre o tratamento de exceções assíncronas. As exceções sem tratamento geradas pelo código que está sendo executado de maneira assíncrona são propagadas de volta para o thread de chamada, exceto em alguns cenários. Para obter mais informações, confira Tratamento de exceções (biblioteca de paralelismo de tarefas).
  • Evite criar métodos async void e, em vez disso, crie métodos async Task. Isso facilita o tratamento de erro, a capacidade de composição e a estabilidade. A exceção a essa diretriz são os manipuladores de eventos assíncronos, que precisam retornar void. Para obter mais informações, confira Evitar o nulo assíncrono.
  • Não combine o bloqueio e o código assíncrono chamando os métodos Task.Wait, Task.Result ou GetAwaiter().GetResult, pois eles podem resultar na ocorrência de deadlock. No entanto, se essa diretriz precisar ser violada, a abordagem preferencial será chamar o método GetAwaiter().GetResult, porque ele preserva as exceções de tarefa. Para obter mais informações, confira Assíncrono todo o tempo e Tratamento de exceções de tarefa no .NET 4.5.
  • Use o método ConfigureAwait sempre que possível, para criar um código sem contexto . O código livre de 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, confira Configurar o contexto.
  • Use tarefas de continuação para funcionalidades, como tratar exceções geradas pela operação assíncrona anterior e cancelar uma continuação antes que ela seja iniciada ou enquanto ela estiver em execução. Para obter mais informações, confira Encadeamento de tarefas com tarefas contínuas.
  • Use uma implementação ICommand assíncrona quando operações assíncronas forem invocadas por meio do ICommand. Isso garante que qualquer exceção na lógica de comando assíncrona possa ser manipulada. Para obter mais informações, confira Programação assíncrona: padrões para aplicativos MVVM assíncronos: comandos.

Escolher bem um contêiner de injeção de dependência

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, já que os recursos serão analisados na inicialização do aplicativo, e não 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 obter mais informações sobre os recursos de aplicativo, confira Aplicação de estilos de XAML a aplicativos Xamarin.Forms.

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 os manipuladores de eventos assinados quando o renderizador personalizado for 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.