Condividi tramite


Ottimizzare il caricamento XAML per WinUI e Windows App SDK

L'analisi del markup XAML per costruire oggetti in memoria richiede molto tempo per un'interfaccia utente complessa. Ecco alcune operazioni che è possibile eseguire per migliorare l'analisi e il tempo di caricamento del markup XAML e l'efficienza della memoria dell'app WinUI.

All'avvio dell'app, limitare il markup XAML caricato solo a ciò che serve per l'interfaccia utente iniziale. Esaminare il markup nella pagina iniziale, incluse le risorse della pagina e verificare che non si stia caricando elementi aggiuntivi non necessari immediatamente. Questi elementi possono provenire da un'ampia gamma di origini, ad esempio dizionari di risorse, elementi inizialmente nascosti e elementi sovrapposti ad altri elementi.

L'ottimizzazione del codice XAML per l'efficienza richiede compromessi; non c'è sempre una singola soluzione per ogni situazione. Qui esaminiamo alcuni problemi comuni e forniamo linee guida che puoi usare per rendere i compromessi giusti per la tua app WinUI.

Riduci al minimo il numero di elementi

Anche se la piattaforma XAML è in grado di visualizzare un numero elevato di elementi, puoi rendere più veloce il layout e il rendering dell'app usando il minor numero di elementi necessari per ottenere gli oggetti visivi desiderati.

Le scelte effettuate nel layout dei controlli dell'interfaccia utente influiscono sul numero di elementi dell'interfaccia utente creati all'avvio dell'app. Per informazioni più dettagliate sull'ottimizzazione del layout, vedi Ottimizzare il layout XAML.

Il conteggio degli elementi è estremamente importante nei modelli di dati perché ogni elemento viene creato di nuovo per ogni elemento di dati. Per informazioni sulla riduzione del numero di elementi in un elenco o in una griglia, vedi Riduzione degli elementi per elemento nell'articolo Ottimizzare le prestazioni di ListView e GridView per WinUI .

Ecco alcuni altri modi per ridurre il numero di elementi che l'app deve caricare all'avvio.

Rinviare la creazione dell'elemento

Se il markup XAML contiene elementi che non vengono visualizzati immediatamente, puoi rinviare il caricamento di tali elementi fino a quando non vengono visualizzati. Ad esempio, è possibile ritardare la creazione di contenuto non visibile, ad esempio una scheda secondaria in un'interfaccia utente simile a una scheda. In alternativa, è possibile visualizzare gli elementi in una visualizzazione griglia per impostazione predefinita, ma fornire all'utente un'opzione per visualizzare i dati in un elenco. È possibile ritardare il caricamento dell'elenco fino a quando non è necessario.

Usare l'attributo x:Load anziché la proprietà Visibility per controllare quando viene visualizzato un elemento. Quando la visibilità di un elemento è impostata su Collapsed, viene ignorata durante il passaggio di rendering, ma si pagano comunque i costi dell'istanza dell'oggetto in memoria. Quando si usa invece x:Load, il framework non crea l'istanza dell'oggetto finché non è necessaria, quindi i costi di memoria sono ancora inferiori. Lo svantaggio è che si paga un sovraccarico di memoria ridotto (circa 600 byte) quando l'interfaccia utente non viene caricata.

Annotazioni

In Windows App SDK x:Load è il modello di caricamento posticipato consigliato per il contenuto XAML non necessario immediatamente.

Gli esempi seguenti mostrano la differenza nel numero di elementi e nell'uso della memoria quando vengono usate tecniche diverse per nascondere gli elementi dell'interfaccia utente. Un controllo ListView e un controllo GridView contenente elementi identici vengono inseriti nella griglia radice di una pagina. ListView non è visibile, ma viene visualizzato GridView. Il codice XAML in ognuno di questi esempi produce la stessa interfaccia utente sullo schermo. Usare gli strumenti per la profilatura e le prestazioni per controllare il numero di elementi e l'uso della memoria nell'app.

Opzione 1 - Inefficiente

In questo caso, listView viene caricato, ma non è visibile perché Width è 0. ListView e ciascuno dei suoi elementi figlio vengono creati nell'albero visivo e caricati in memoria.

<!-- 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>

Albero visuale attivo con listView caricato. Il numero totale di elementi per la pagina è 89.

Screenshot dell'albero visivo con visualizzazione a elenco.

ListView e i relativi figli vengono caricati in memoria.

Screenshot della finestra dell'App di test della memoria gestita, 1.EXE, che mostra il controllo ListView e i suoi elementi figlio caricati in memoria.

Opzione 2 - Migliore

In questo caso, il controllo ListView Visibility è impostato su Collapsed (l'altro codice XAML è identico all'originale). ListView viene creato nella struttura ad albero visuale, ma i relativi elementi figlio non sono creati. Tuttavia, vengono ancora caricati in memoria, quindi l'uso della memoria è identico all'esempio precedente.

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

Albero visivo dinamico con ListView collassato. Il numero totale di elementi per la pagina è 46.

Screenshot dell'albero visivo con vista elenco ridotta.

ListView e i relativi figli vengono caricati in memoria.

Uno screenshot aggiornato dell'app di test della memoria gestita 1.EXE che mostra che il ListView e i suoi elementi figlio vengono caricati in memoria.

Opzione 3 - Più efficiente

In questo caso, ListView ha l'attributo x:Load impostato su False (l'altro codice XAML è identico all'originale). ListView non viene creato nell'albero visivo o caricato in memoria all'avvio.

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

Struttura ad albero visuale attiva con ListView non caricata. Il numero totale di elementi per la pagina è 45.

Struttura ad albero visuale con visualizzazione elenco non caricata

ListView e i suoi elementi secondari non vengono caricati in memoria.

Struttura ad albero visuale con visualizzazione elenco

Annotazioni

I conteggi degli elementi e l'uso della memoria in questi esempi sono molto piccoli e vengono mostrati solo per illustrare il concetto. In questi esempi, il sovraccarico dell'uso di x:Load è maggiore del risparmio di memoria, quindi l'app non trarrà vantaggio. È consigliabile usare gli strumenti di profilatura nell'app per determinare se il caricamento posticipato sarà utile.

Usare le proprietà del pannello di layout

I pannelli di layout hanno una proprietà Background , quindi non è necessario inserire un rettangolo davanti a un pannello solo per colorarlo.

Inefficiente

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

Efficiente

<Grid Background="Black"/>

I pannelli di layout hanno anche proprietà del bordo predefinite, quindi non è necessario inserire un elemento Border in un pannello di layout. Vedi Ottimizzare il layout XAML per altre info ed esempi.

Usare immagini al posto di elementi basati su vettori

Se si riutilizza lo stesso elemento basato su vettore abbastanza volte, diventa invece più efficiente usare un elemento Image . Gli elementi basati su vettori possono essere più costosi perché la CPU deve creare ogni singolo elemento separatamente. Il file di immagine deve essere decodificato una sola volta.

Ottimizzare le risorse e i dizionari delle risorse

In genere si usano i dizionari risorse per archiviare, a livello globale, le risorse a cui si vuole fare riferimento in più posizioni nell'app. Ad esempio, stili, pennelli, modelli e così via.

In generale, ResourceDictionary è ottimizzato per evitare l'istanza delle risorse fino a quando non vengono richieste. Tuttavia, è consigliabile evitare situazioni in modo che le risorse non vengano create inutilmente.

Risorse con x:Name

Usare l'attributo x:Key per fare riferimento alle risorse. Qualsiasi risorsa con l'attributo x:Name non trarrà vantaggio dall'ottimizzazione della piattaforma; viene invece istanziata non appena viene creato il ResourceDictionary. Questo problema si verifica perché x:Name indica alla piattaforma che l'app necessita dell'accesso sul campo a questa risorsa, quindi la piattaforma deve creare un elemento per contenere un riferimento.

ResourceDictionary in un oggetto UserControl

Un oggetto ResourceDictionary definito all'interno di un oggetto UserControl comporta una penalità. La piattaforma crea una copia di tale ResourceDictionary per ogni istanza di UserControl. Se si dispone di un UserControl che viene utilizzato frequentemente, spostare il ResourceDictionary dal UserControl e metterlo a livello di pagina.

Ambito risorsa e ResourceDictionary

Se una pagina fa riferimento a un controllo utente o a una risorsa definita in un file diverso, anche il framework analizza il file.

In questo caso, poiché InitialPage.xaml usa una risorsa di ExampleResourceDictionary.xaml, l'intero oggetto ExampleResourceDictionary.xaml deve essere analizzato all'avvio.

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>

ExampleResourceDictionary.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 usi una risorsa in molte pagine nell'app, l'archiviazione in App.xaml è una procedura consigliata ed evita la duplicazione. Ma App.xaml viene analizzato all'avvio dell'app, quindi qualsiasi risorsa usata in una sola pagina, a meno che tale pagina non sia la pagina iniziale, deve essere inserita nelle risorse locali della pagina. Questo esempio mostra App.xaml contenente risorse usate da una sola pagina che non è la pagina iniziale. Questo aumenta inutilmente il tempo di avvio.

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>

Per rendere questo esempio più efficiente, passare SecondPageTextBrush a SecondPage.xaml e passare ThirdPageTextBrush a ThirdPage.xaml. InitialPageTextBrush può rimanere in App.xaml perché le risorse dell'applicazione devono essere analizzate all'avvio dell'app in qualsiasi caso.

Consolidare più pennelli che sembrano uguali in una sola risorsa

La piattaforma XAML tenta di memorizzare nella cache gli oggetti di uso comune in modo che possano essere riutilizzati il più spesso possibile. Ma XAML non è in grado di determinare facilmente se un pennello dichiarato in un pezzo di markup è uguale a un pennello dichiarato in un altro. L'esempio seguente usa SolidColorBrush per illustrare, ma il caso è più probabile e più importante con GradientBrush. Controllare anche la presenza di pennelli che usano colori predefiniti; ad esempio "Orange" e "#FFFFA500" sono lo stesso colore.

Inefficiente

<!-- 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>

Per correggere la duplicazione, definire il pennello come risorsa. Se i controlli in altre pagine usano lo stesso pennello, spostarlo in App.xaml.

Efficiente

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

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

Ridurre al minimo l'overdrawing

L'overdrawing si verifica in cui più oggetti vengono disegnati nello stesso pixel dello schermo. Si noti che a volte c'è un compromesso tra questa guida e il desiderio di ridurre al minimo il numero di elementi.

Utilizzare DebugSettings.IsOverdrawHeatMapEnabled come strumento di diagnosi visiva. Potresti trovare oggetti disegnati che non sapevi che si trovavano nella scena.

Elementi trasparenti o nascosti

Se un elemento non è visibile perché è trasparente o nascosto dietro altri elementi e non contribuisce al layout, eliminarlo. Se l'elemento non è visibile nello stato di visualizzazione iniziale ma è visibile in altri stati di visualizzazione, usare x:Load per controllarne lo stato o impostare Visibilitysu Collapsed sull'elemento stesso e modificare il valore su Visible negli stati appropriati. Ci saranno eccezioni a questa euristica: in generale, il valore di una proprietà ha nella maggior parte degli stati di visualizzazione è preferibile impostare localmente sull'elemento.

Elementi compositi

Usare un elemento composito invece di creare più elementi per creare un effetto. In questo esempio, il risultato è una forma a due tonalità in cui la metà superiore è nera dallo sfondo della Griglia e la metà inferiore è grigia dal Rettangolo bianco semitrasparente mescolato alfa sullo sfondo nero della Griglia. In questo caso vengono riempiti 150% dei pixel necessari per ottenere il risultato.

Inefficiente

<!-- 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>

Efficiente

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

Pannelli di layout

Un pannello di layout può servire a due scopi: colorare una zona e organizzare gli elementi figli. Se un elemento più indietro nell'ordine di profondità sta già colorando un'area, un pannello di layout in primo piano non ha bisogno di dipingere quell'area; invece, può dedicarsi alla disposizione dei suoi elementi. Ecco un esempio.

Inefficiente

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

Efficiente

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

Se la Grid deve essere hit-testabile, imposta un valore di sfondo di Transparent su di essa.

Bordi

Utilizzare un elemento Border per disegnare un bordo intorno a un oggetto . In questo esempio, un oggetto Grid viene usato come bordo improvvisato intorno a un TextBox. Ma tutti i pixel nella cella centrale vengono ritirati.

Inefficiente

<!-- 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>

Efficiente

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

Margini

Tenere presente i margini. Due elementi adiacenti si sovrapporranno, possibilmente per errore, se i margini negativi si estendono nei limiti di rendering di un altro elemento causando il sovradisegno.

Memorizzare nella cache il contenuto statico

Un'altra fonte di sovradisegno è una forma costituita da molti elementi sovrapposti. Se si imposta CacheMode su BitmapCache su UIElement che contiene la forma composita, la piattaforma esegue il rendering dell'elemento in una bitmap una sola volta e usa tale bitmap invece di ridisegnare.

Inefficiente

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

Diagramma venn con tre cerchi solidi

L'immagine precedente è il risultato, ma ecco una mappa delle aree sovrappresse. Il rosso più scuro indica quantità più elevate di overdraw.

Diagramma di Venn che mostra le aree sovrapposte

Efficiente

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

Si noti l'uso di CacheMode. Non usare questa tecnica se una delle sottoforme viene animata perché è molto probabile che la cache bitmap debba essere rigenerata ogni fotogramma, vanificando lo scopo.

Usare l'output XAML compilato

Windows App SDK compila XAML in una rappresentazione binaria come parte della compilazione, evitando così i costi di analisi del testo in fase di esecuzione. Il formato compilato ottimizza anche il caricamento e la creazione di alberi per tipi XAML comuni, ad esempio stati di visualizzazione, dizionari di risorse e stili.

I controlli e i dizionari WinUI predefiniti traggono già vantaggio da questa pipeline. Per la tua app WinUI, mantieni abilitati i normali passaggi di compilazione XAML in modo che l'output di compilazione del markup generato sia disponibile in fase di esecuzione.