Partilhar via


Otimizar o carregamento de XAML para WinUI e Windows App SDK

Analisar a marcação XAML para construir objetos em memória é demorado para uma interface complexa. Aqui estão algumas coisas que pode fazer para melhorar o tempo de análise e carregamento da sua marcação XAML e a eficiência de memória da sua aplicação WinUI.

No arranque da aplicação, limite a marcação XAML carregada apenas ao que precisa para a sua interface inicial. Verifique a marcação na sua página inicial, incluindo os recursos da página, e confirme que não está a carregar elementos extra que não são necessários imediatamente. Estes elementos podem provir de várias fontes, como dicionários de recursos, elementos inicialmente recolhidos e elementos sobrepostos a outros elementos.

Otimizar o seu XAML para eficiência exige fazer concessões; nem sempre há uma solução única para todas as situações. Aqui, analisamos alguns problemas comuns e fornecemos orientações que pode usar para fazer as trocas certas para a sua aplicação WinUI.

Minimizar o número de elementos

Embora a plataforma XAML seja capaz de exibir um grande número de elementos, pode fazer com que a sua aplicação seja disposta e renderizada mais rapidamente usando o menor número possível de elementos necessários para alcançar os visuais desejados.

As escolhas que faz na forma como dispõe os controlos da interface afetam o número de elementos criados quando a aplicação começa. Para informações mais detalhadas sobre otimização do layout, consulte Otimizar o seu layout XAML.

A contagem de elementos é extremamente importante nos templates de dados porque cada elemento é criado novamente para cada elemento de dados. Para informações sobre como reduzir o número de elementos numa lista ou grelha, veja Redução de elementos por item no artigo Otimizar ListView e desempenho do GridView para o WinUI .

Aqui, analisamos outras formas de reduzir o número de elementos que a sua aplicação tem de carregar no arranque.

Adiar a criação de itens

Se a sua marcação XAML contiver elementos que não mostra de imediato, pode adiar o carregamento desses elementos até serem mostrados. Por exemplo, pode atrasar a criação de conteúdo não visível, como um separador secundário numa interface do tipo separador. Ou, pode mostrar os itens numa vista em grelha como padrão, mas oferecer uma opção para o utilizador visualizar os dados numa lista em vez disso. Podes adiar o carregamento da lista até ser necessário.

Use o atributo x:Load em vez da propriedade Visibilidade para controlar quando um elemento é mostrado. Quando a visibilidade de um elemento está definida como Colapsado, é ignorado durante o ciclo de renderização, mas ainda assim paga-se os custos de instância do objeto em memória. Quando usas x:Load em vez disso, a framework não cria a instância do objeto até ser necessária, pelo que os custos de memória são ainda mais baixos. A desvantagem é que se paga uma pequena sobrecarga de memória (aproximadamente 600 bytes) quando a interface não está carregada.

Observação

No Windows App SDK, x:Load é o padrão recomendado de carregamento diferido para conteúdo XAML que não é necessário imediatamente.

Os exemplos seguintes mostram a diferença no número de elementos e no uso de memória quando diferentes técnicas são usadas para ocultar elementos da interface. Uma Vista de Lista e uma Vista de Grade contendo itens idênticos são colocadas na Grelha raiz da página. O ListView não é visível, mas o GridView é mostrado. O XAML em cada um destes exemplos produz a mesma interface no ecrã. Usa ferramentas de perfilagem e desempenho para verificar o número de elementos e o uso de memória na tua aplicação.

Opção 1 - Ineficiente

Aqui, o ListView é carregado, mas não é visível porque o seu Width é 0. O ListView e cada um dos seus elementos filhos são criados na árvore visual e carregados na memória.

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <ListView x:Name="List1" Width="0">
        <ListViewItem>Item 1</ListViewItem>
        <ListViewItem>Item 2</ListViewItem>
        <ListViewItem>Item 3</ListViewItem>
        <ListViewItem>Item 4</ListViewItem>
        <ListViewItem>Item 5</ListViewItem>
        <ListViewItem>Item 6</ListViewItem>
        <ListViewItem>Item 7</ListViewItem>
        <ListViewItem>Item 8</ListViewItem>
        <ListViewItem>Item 9</ListViewItem>
        <ListViewItem>Item 10</ListViewItem>
    </ListView>

    <GridView x:Name="Grid1">
        <GridViewItem>Item 1</GridViewItem>
        <GridViewItem>Item 2</GridViewItem>
        <GridViewItem>Item 3</GridViewItem>
        <GridViewItem>Item 4</GridViewItem>
        <GridViewItem>Item 5</GridViewItem>
        <GridViewItem>Item 6</GridViewItem>
        <GridViewItem>Item 7</GridViewItem>
        <GridViewItem>Item 8</GridViewItem>
        <GridViewItem>Item 9</GridViewItem>
        <GridViewItem>Item 10</GridViewItem>
    </GridView>
</Grid>

Árvore visual ao vivo com o ListView carregado. A contagem total de elementos para a página é 89.

Captura de ecrã da árvore visual com vista de lista.

O ListView e os seus filhos são carregados na memória.

Captura de tela do aplicativo de teste de memória gerida 1.exe, mostrando que o ListView e os seus filhos são carregados na memória.

Opção 2 - Melhor

Aqui, o ListView's Visibility está definido para Collapsed (o outro XAML é idêntico ao original). O ListView é criado na árvore visual, mas os seus elementos subordinados não são. No entanto, continuam a ser carregados na memória, pelo que o uso da memória é idêntico ao exemplo anterior.

<ListView x:Name="List1" Visibility="Collapsed">

Árvore visual ao vivo com o ListView colapsado. O total de elementos da página é 46.

Captura de ecrã da árvore visual com vista de lista colapsada.

O ListView e os seus filhos são carregados na memória.

Uma captura de ecrã atualizada da tabela Managed Memory Test App 1 dot E X E, mostrando o ListView e os seus filhos, é carregada na memória.

Opção 3 - Mais eficiente

Aqui, o ListView tem o atributo x:Load definido para False (o outro XAML é idêntico ao original). O ListView não é criado na árvore visual nem carregado na memória no arranque.

<ListView x:Name="List1" Visibility="Collapsed" x:Load="False">

Árvore visual ao vivo com o ListView não carregado. O número total de elementos para a página é de 45.

Árvore visual com exibição de lista que não foi carregada

O ListView e os seus componentes não são carregados na memória.

Árvore visual com vista de lista

Observação

A contagem de elementos e o uso de memória nestes exemplos são muito pequenos e são mostrados apenas para demonstrar o conceito. Nestes exemplos, a sobrecarga de usar x:Load é maior do que a poupança de memória, pelo que a aplicação não beneficiaria. Deves usar ferramentas de perfil na tua aplicação para perceber se o carregamento diferido ajuda.

Usar propriedades do painel de layout

Os painéis de layout têm uma propriedade Background, por isso não há necessidade de colocar um Retângulo à frente do painel apenas para colorir o painel.

Ineficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid>
    <Rectangle Fill="Black"/>
</Grid>

Eficiente

<Grid Background="Black"/>

Os painéis de layout também têm propriedades de borda incorporadas, por isso não precisas de colocar um elemento de borda à volta de um painel de layout. Consulte Otimizar o seu layout XAML para mais informações e exemplos.

Use imagens em vez de elementos baseados em vetores

Se reutilizares o mesmo elemento baseado em vetores vezes suficientes, torna-se mais eficiente usar um elemento Imagem . Os elementos baseados em vetores podem ser mais caros porque a CPU tem de criar cada elemento individualmente. O ficheiro de imagem só precisa de ser decodificado uma vez.

Otimizar recursos e dicionários de recursos

Normalmente usas dicionários de recursos para armazenar, a um nível algo global, recursos que queres consultar em vários locais da tua aplicação. Por exemplo, estilos, pincéis, moldes, e assim por diante.

Em geral, o ResourceDictionary está otimizado para evitar instanciar recursos até que sejam solicitados. Mas há situações que deves evitar para que os recursos não sejam instanciados desnecessariamente.

Recursos com x:Nome

Utilize o atributo x:Key para referenciar os seus recursos. Qualquer recurso com o atributo x:Name não beneficiará da otimização da plataforma; em vez disso, é instanciado assim que o ResourceDictionary é criado. Isto acontece porque o x:Name indica à plataforma que a sua aplicação precisa de acesso em campo a esse recurso, por isso a plataforma precisa de criar algo para manter uma referência a ele.

Dicionário de Recursos num Controle de Utilizador

Um Dicionário de Recursos definido dentro de um Controlo de Utilizador acarreta uma penalização. A plataforma cria uma cópia desse ResourceDictionary para cada instância do UserControl. Se tiveres um UserControl que é muito usado, então move o ResourceDictionary para fora do UserControl e coloca-o ao nível da página.

Recursos e Âmbito do Dicionário de Recursos

Se uma página faz referência a um controlo de utilizador ou a um recurso definido num ficheiro diferente, então o framework analisa esse ficheiro também.

Aqui, como o InitialPage.xaml usa um recurso do ExampleResourceDictionary.xaml, todo o ExampleResourceDictionary.xaml deve ser analisado no arranque.

InitialPage.xaml

<Page x:Class="ExampleNamespace.InitialPage" ...>
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ExampleResourceDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>

    <Grid>
        <TextBox Foreground="{StaticResource TextBrush}"/>
    </Grid>
</Page>

DicionárioDeRecursosExemplo.xaml

<ResourceDictionary>
    <SolidColorBrush x:Key="TextBrush" Color="#FF3F42CC"/>

    <!--This ResourceDictionary contains many other resources that
        are used in the app, but are not needed during startup.-->
</ResourceDictionary>

Se usares um recurso em muitas páginas da tua aplicação, então guardá-lo no App.xaml é uma boa prática e evita duplicações. Mas o App.xaml é analisado no arranque da aplicação, por isso qualquer recurso que seja usado apenas numa página, a menos que essa página seja a página inicial, deve ser colocado nos recursos locais da página. Este exemplo mostra o App.xaml contendo recursos que são usados apenas por uma página que não é a página inicial. Isto aumenta desnecessariamente o tempo de arranque.

App.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Application ...>
     <Application.Resources>
        <SolidColorBrush x:Key="DefaultAppTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="InitialPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="SecondPageTextBrush" Color="#FF3F42CC"/>
        <SolidColorBrush x:Key="ThirdPageTextBrush" Color="#FF3F42CC"/>
    </Application.Resources>
</Application>

InitialPage.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.InitialPage" ...>
    <StackPanel>
        <TextBox Foreground="{StaticResource InitialPageTextBrush}"/>
    </StackPanel>
</Page>

SecondPage.xaml

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page x:Class="ExampleNamespace.SecondPage" ...>
    <StackPanel>
        <Button Content="Submit" Foreground="{StaticResource SecondPageTextBrush}"/>
    </StackPanel>
</Page>

Para tornar este exemplo mais eficiente, passe SecondPageTextBrush para SecondPage.xaml e depois ThirdPageTextBrush para ThirdPage.xaml. InitialPageTextBrush pode permanecer em App.xaml porque os recursos da aplicação têm de ser analisados no arranque da aplicação em qualquer caso.

Consolide vários pincéis que tenham o mesmo aspeto num único recurso

A plataforma XAML tenta armazenar em cache objetos frequentemente usados para que possam ser reutilizados o mais frequentemente possível. Mas o XAML não consegue perceber facilmente se um pincel declarado numa parte de marcação é igual a um pincel declarado noutra. O exemplo aqui usa o SolidColorBrush para demonstrar, mas o caso é mais provável e mais importante com o GradientBrush. Verifique também pincéis que usem cores pré-definidas; por exemplo, "Orange" e "#FFFFA500" são da mesma cor.

Ineficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Page ... >
    <StackPanel>
        <TextBlock>
            <TextBlock.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </TextBlock.Foreground>
        </TextBlock>
        <Button Content="Submit">
            <Button.Foreground>
                <SolidColorBrush Color="#FFFFA500"/>
            </Button.Foreground>
        </Button>
    </StackPanel>
</Page>

Para corrigir a duplicação, defina o pincel como um recurso. Se os controlos noutras páginas usarem o mesmo pincel, move-o para App.xaml.

Eficiente

<Page ... >
    <Page.Resources>
        <SolidColorBrush x:Key="BrandBrush" Color="#FFFFA500"/>
    </Page.Resources>

    <StackPanel>
        <TextBlock Foreground="{StaticResource BrandBrush}" />
        <Button Content="Submit" Foreground="{StaticResource BrandBrush}" />
    </StackPanel>
</Page>

Minimizar o desenho excessivo

O sobredesenho ocorre quando mais do que um objeto é desenhado nos mesmos píxeis do ecrã. Note-se que por vezes existe um conflito entre esta orientação e o desejo de minimizar o número de elementos.

Utilize DebugSettings.IsOverdrawHeatMapEnabled como uma ferramenta de diagnóstico visual. Podes encontrar objetos a serem desenhados que não sabias que estavam na cena.

Elementos transparentes ou ocultos

Se um elemento não for visível porque é transparente ou está escondido atrás de outros elementos, e não estiver a contribuir para o layout, então apague-o. Se o elemento não for visível no estado visual inicial, mas for visível noutros estados visuais, então use x:Load para controlar o seu estado ou defina a Visibilidade para Colapsado no próprio elemento e altere o valor para Visível nos estados apropriados. Haverá exceções a esta heurística: em geral, o valor que uma propriedade tem na maioria dos estados visuais é melhor definido localmente no elemento.

Elementos compostos

Use um elemento composto em vez de sobrepor vários elementos para criar um efeito. Neste exemplo, o resultado é uma forma de duas tonalidades onde a metade superior é preta devido ao fundo da Grelha e a metade inferior é cinzenta derivada do Retângulo branco semi-transparente fundido em alfa sobre o fundo preto da Grelha. Aqui, 150% dos píxeis necessários para alcançar o resultado estão a ser preenchidos.

Ineficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Black">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Grid.Row="1" Fill="White" Opacity=".5"/>
</Grid>

Eficiente

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Rectangle Fill="Black"/>
    <Rectangle Grid.Row="1" Fill="#FF7F7F7F"/>
</Grid>

Painéis de layout

Um painel de disposição pode ter dois propósitos: colorir uma área e organizar elementos filhos. Se um elemento mais atrás na ordem z já estiver a colorir uma área, então um painel de layout à frente não precisa de pintar essa área; em vez disso, pode focar-se em apresentar os seus filhos. Eis um exemplo.

Ineficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Background="Blue"/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Eficiente

<GridView Background="Blue">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid/>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Se a Grelha tiver de ser detectável ao toque, então defina o valor de fundo como Transparent.

Fronteiras

Usa um elemento Borda para desenhar uma borda à volta de um objeto. Neste exemplo, uma Grelha é usada como uma borda improvisada em torno de uma Caixa de Texto. Mas todos os píxeis na célula central estão sobredesenhados.

Ineficiente

<!-- NOTE: EXAMPLE OF INEFFICIENT CODE; DO NOT COPY-PASTE. -->
<Grid Background="Blue" Width="300" Height="45">
    <Grid.RowDefinitions>
        <RowDefinition Height="5"/>
        <RowDefinition/>
        <RowDefinition Height="5"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition/>
        <ColumnDefinition Width="5"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Row="1" Grid.Column="1"></TextBox>
</Grid>

Eficiente

<Border BorderBrush="Blue" BorderThickness="5" Width="300" Height="45">
    <TextBox/>
</Border>

Margens

Esteja atento às margens. Dois elementos vizinhos irão sobrepor-se, possivelmente acidentalmente, se as margens negativas se estenderem dentro dos limites de renderização de um elemento adjacente e causarem sobreposição indesejada.

Cache de conteúdo estático

Outra fonte de overdrawing é uma forma composta por muitos elementos sobrepostos. Se definires o Modo de Cache para BitmapCache no UIElement que contém a forma composta, então a plataforma renderiza o elemento para um bitmap uma vez e usa esse bitmap em cada frame em vez de fazer um sobreesboço.

Ineficiente

<Canvas Background="White">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Diagrama de Venn com três círculos sólidos

A imagem acima é o resultado, mas aqui está um mapa das regiões sobredesenhadas. Vermelho mais escuro indica uma maior sobrecarga de renderização.

Diagrama de Venn que mostra áreas sobrepostas

Eficiente

<Canvas Background="White" CacheMode="BitmapCache">
    <Ellipse Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Left="21" Height="40" Width="40" Fill="Blue"/>
    <Ellipse Canvas.Top="13" Canvas.Left="10" Height="40" Width="40" Fill="Blue"/>
</Canvas>

Note o uso do Modo Cache. Não uses esta técnica se alguma das sub-formas animar, porque o cache bitmap provavelmente terá de ser regenerado a cada frame, anulando o propósito.

Usar saída XAML compilada

O Windows App SDK compila XAML numa representação binária como parte da compilação, o que evita custos de análise de texto em tempo de execução. O formato compilado também otimiza a criação de cargas e árvores para tipos XAML comuns, como estados visuais, dicionários de recursos e estilos.

Os controlos e dicionários incorporados do WinUI já beneficiam deste pipeline. Para a sua própria aplicação WinUI, mantenha ativados os passos normais de compilação XAML para que o resultado da compilação do markup gerado esteja disponível em tempo de execução.