Optimera XAML-märkning

Att parsa XAML-markering för att konstruera objekt i minnet är tidskrävande för ett komplext användargränssnitt. Här följer några saker du kan göra för att förbättra parsnings- och inläsningstiden för din XAML-markering och appens minneseffektivitet.

Vid appstart begränsar du XAML-markering som läses in till endast det du behöver för det ursprungliga användargränssnittet. Granska markeringen på den första sidan (inklusive sidresurser) och bekräfta att du inte läser in extra element som inte behövs direkt. Dessa element kan komma från en mängd olika källor, till exempel resurslexikon, element som initialt är dolda och element som ritas över andra element.

För att optimera din XAML för effektivitet krävs kompromisser. Det finns inte alltid en enda lösning för varje situation. Här tittar vi på några vanliga problem och ger riktlinjer som du kan använda för att göra rätt kompromisser för din app.

Minimera antal element

Även om XAML-plattformen kan visa ett stort antal element kan du få appen att lägga ut och rendera snabbare genom att använda det minsta antalet element för att uppnå de visuella objekt du vill ha.

De val du gör i hur du lägger ut användargränssnittskontrollerna påverkar antalet gränssnittselement som skapas när appen startar. Mer detaljerad information om hur du optimerar layout finns i Optimera din XAML-layout.

Elementantal är mycket viktigt i datamallar eftersom varje element skapas igen för varje dataobjekt. Information om hur du minskar antalet element i en lista eller ett rutnät finns i artikeln Minskning av element per objekt i artikeln ListView och GridView UI-optimering.

Här tittar vi på några andra sätt att minska antalet element som appen måste läsa in vid start.

Skjuta upp skapande av objekt

Om XAML-markering innehåller element som du inte visar direkt kan du skjuta upp inläsningen av dessa element tills de visas. Du kan till exempel fördröja skapandet av icke-synligt innehåll, till exempel en sekundär flik i ett flikliknande användargränssnitt. Eller så kan du visa objekt i en rutnätsvy som standard, men ange ett alternativ för användaren att visa data i en lista i stället. Du kan fördröja inläsningen av listan tills den behövs.

Använd attributet x:Load i stället för egenskapen Synlighet för att styra när ett element visas. När ett elementets synlighet är inställd på Dold, hoppas det över under renderingspasset, men du betalar fortfarande minneskostnaderna för objektinstansen. När du använder x:Load i stället skapar ramverket inte objektinstansen förrän den behövs, så minneskostnaderna är ännu lägre. Nackdelen är att du betalar en liten minneskostnad (cirka 600 byte) när användargränssnittet inte läses in.

Anmärkning

Du kan fördröja inläsning av element med hjälp av attributet x:Load eller x:DeferLoadStrategy. Attributet x:Load är tillgängligt från och med Windows 10 Creators Update (version 1703, SDK build 15063). Den lägsta version som ditt Visual Studio-projekt har som mål måste vara Windows 10 Creators Update (10.0, Build 15063) för att kunna använda x:Load. Använd x:DeferLoadStrategy för att rikta in dig på tidigare versioner.

I följande exempel visas skillnaden i antal element och minnesanvändning när olika tekniker används för att dölja gränssnittselement. En ListView och en GridView som innehåller identiska objekt placeras i en sidas rotrutnät. ListView visas inte, men GridView visas. XAML i vart och ett av dessa exempel genererar samma användargränssnitt på skärmen. Vi använder Visual Studio verktyg för profilering och prestanda för att kontrollera antalet element och minnesanvändning.

Alternativ 1 – Ineffektiv

Här läses ListView in, men visas inte eftersom bredden är 0. ListView och vart och ett av dess underordnade element skapas i det visuella trädet och läses in i minnet.

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

Levande visuellt träd med ListView inläst. Totalt antal element för sidan är 89.

Skärmbild av det visuella trädet med listvyn.

ListView och dess underordnade element laddas in i minnet.

Skärmbild av Managed Memory Test App 1.EXE-tabellen som visar ListView och dess underordnade läses in i minnet.

Alternativ 2 – Bättre

Här är ListViews synlighet inställd på komprimerad (den andra XAML är identisk med originalet). ListView skapas i det visuella trädet, men dess underordnade element skapas inte. De läses dock in i minnet, så minnesanvändningen är identisk med föregående exempel.

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

Levande visuellt träd med ListView komprimerat. Totalt antal element för sidan är 46.

Skärmbild av det visuella trädet med komprimerad listvy.

ListView och dess underordnade element laddas in i minnet.

En uppdaterad skärmdump av Managed Memory Test App 1 dot E X E-tabellen visar ListView och dess underliggande element laddas in i minnet.

Alternativ 3 – mest effektiva

Här har ListView attributet x:Load inställt på False (den andra XAML är identisk med originalet). ListView skapas inte i det visuella trädet eller läses in i minnet vid start.

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

Levande visuellt träd med ListView inte inläst. Totalt antal element för sidan är 45.

Visuellt träd med listvyn inte inläst

ListView och dess underordnade filer läses inte in i minnet.

visuellt träd med listvy

Anmärkning

Elementantalet och minnesanvändningen i dessa exempel är mycket små och visas bara för att demonstrera konceptet. I de här exemplen är kostnaden för att använda x:Load större än minnesbesparingarna, så appen skulle inte gynnas. Du bör använda profileringsverktygen i din app för att avgöra om din app kommer att dra nytta av uppskjuten inläsning eller inte.

Använd layoutpanels egenskaper

Layoutpaneler har egenskapen Background så det finns inget behov av att placera en rektangel framför en panel bara för att färglägga den.

Ineffektiv

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

Effektiv

<Grid Background="Black"/>

Layoutpaneler har också inbyggda kantlinjeegenskaper, så du behöver inte placera ett kantlinjeelement runt en layoutpanel. Mer information och exempel finns i Optimera XAML-layouten.

Använda bilder i stället för vektorbaserade element

Om du återanvänder samma vektorbaserade element tillräckligt många gånger blir det mer effektivt att använda ett Image-element i stället. Vektorbaserade element kan vara dyrare eftersom processorn måste skapa varje enskilt element separat. Bildfilen måste bara avkodas en gång.

Optimera resurser och resursordlistor

Du använder vanligtvis resursordlistor för att lagra resurser på en något global nivå som du vill referera till på flera platser i appen. Till exempel format, penslar, mallar och så vidare.

I allmänhet har vi optimerat ResourceDictionary att inte instansiera resurser om de inte efterfrågas. Men det finns situationer som du bör undvika så att resurserna inte instansieras i onödan.

Resurser med x:Name

Använd attributet x:Key för att referera till dina resurser. Alla resurser med attributet x:Name kommer inte att dra nytta av plattformsoptimeringen. i stället instansieras den så snart ResourceDictionary har skapats. Detta beror på att x:Name talar om för plattformen att din app behöver fältåtkomst till den här resursen, så plattformen måste skapa något att skapa en referens till.

ResourceDictionary i en UserControl

En ResourceDictionary som definieras inuti en UserControl medför en straffavgift. Plattformen skapar en kopia av en sådan ResourceDictionary för varje instans av UserControl. Om du har en UserControl som används mycket flyttar du ResourceDictionary från UserControl och placerar den på sidnivå.

Resurs- och resursordboksomfång

Om en sida refererar till en användarkontroll eller en resurs som definierats i en annan fil parsar ramverket även den filen.

Eftersom InitialPage.xaml använder en resurs från ExampleResourceDictionary.xamlmåste hela ExampleResourceDictionary.xaml parsas vid start.

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>

Om du använder en resurs på många sidor i appen är det bra att lagra den i App.xaml och undvika duplicering. Men App.xaml parsas vid appstart så att alla resurser som endast används på en sida (om inte den sidan är den första sidan) ska placeras i sidans lokala resurser. Det här exemplet visar App.xaml- som innehåller resurser som endast används av en sida (det är inte den första sidan). Detta ökar i onödan starttiden för appar.

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>

Om du vill göra det här exemplet mer effektivt flyttar du SecondPageTextBrush till SecondPage.xaml- och flyttar ThirdPageTextBrush till ThirdPage.xaml. InitialPageTextBrush kan finnas kvar i App.xaml- eftersom programresurser måste parsas vid appstart i alla fall.

Konsolidera flera penslar som ser likadana ut i en resurs

XAML-plattformen försöker cachelagra objekt som används ofta så att de kan återanvändas så ofta som möjligt. Men XAML kan inte enkelt avgöra om en pensel som deklarerats i en markering är densamma som en pensel som deklareras i en annan. Exemplet här använder SolidColorBrush- för att demonstrera, men fallet är mer troligt och viktigare med GradientBrush. Kontrollera även för penslar som använder fördefinierade färger. till exempel är "Orange" och "#FFFFA500" samma färg.

ineffektiv.

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

Om du vill åtgärda dupliceringen definierar du penseln som en resurs. Om kontroller på andra sidor använder samma pensel flyttar du den till App.xaml-.

Effektiv.

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

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

Minimera övertrassering

Övertrassering sker där fler än ett objekt ritas i samma skärmpixlar. Observera att det ibland finns en kompromiss mellan den här vägledningen och viljan att minimera antalet element.

Använd DebugSettings.IsOverdrawHeatMapEnabled som ett visuellt diagnostiskt verktyg. Du kanske hittar objekt som ritas som du inte visste fanns i scenen.

Transparenta eller dolda element

Om ett element inte är synligt eftersom det är transparent eller dolt bakom andra element och inte bidrar till layouten tar du bort det. Om elementet inte visas i det inledande visuella tillståndet men det visas i andra visuella tillstånd använder du x:Load för att styra dess tillstånd eller ange Synlighet till Komprimerad på själva elementet och ändra värdet till Synlig i lämpliga tillstånd. Det kommer att finnas undantag till denna heuristiska: i allmänhet är värdet som en egenskap har i de flesta visuella tillstånd bäst inställt lokalt på elementet.

Sammansatta element

Använd ett sammansatt element i stället för att lägga flera element i lager för att skapa en effekt. I det här exemplet är resultatet en tvåtonad form där den övre halvan är svart (från bakgrunden av Grid) och den nedre halvan är grå (från den halvtransparenta vita rektangel alfablandad över den svarta bakgrunden i Grid). Här fylls 150% av de pixlar som krävs för att uppnå resultatet.

ineffektiv.

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

Effektiv.

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

Layout-paneler

En layoutpanel kan ha två syften: att färglägga ett område samt arrangera underordnade element. Om ett element längre tillbaka i z-ordning redan färgar ett område behöver en layoutpanel framför inte måla det området, utan kan den bara fokusera på att organisera dess barn. Här är ett exempel.

ineffektiv.

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

Effektiv.

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

Om Grid- måste vara träfftestbar, ange då ett bakgrundsvärde av transparent på det.

Gränser

Använd ett border-element för att rita en kantlinje runt ett objekt. I det här exemplet används en Grid- som en provisorisk kantlinje runt en TextBox-. Men alla bildpunkter i den centrala cellen är överlagrade.

ineffektiv.

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

Effektiv.

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

Marginaler

Var medveten om marginaler. Två närliggande element överlappar (eventuellt oavsiktligt) om negativa marginaler sträcker sig in i en annan elements renderingsgräns och orsakar överritning.

Cacha statiskt innehåll

En annan orsak till att rita för mycket är en form skapad av många överlappande element. Om du ställer in CacheMode till BitmapCache-UIElement- som innehåller den sammansatta formen renderar plattformen elementet till en bitmapp en gång och använder sedan bitmappen varje bildruta i stället för att övertrassera.

ineffektiv.

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

Venn-diagram med tre fasta cirklar

Bilden ovan är resultatet, men här är en karta över de övertrasserade regionerna. Mörkare röd visar högre mängder överfyllnad.

Venn-diagram som visar överlappande områden

Effektiv.

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

Observera användningen av CacheMode. Använd inte den här tekniken om någon av delformerna animeras eftersom bitmappscachen kan komma att behöva återskapas varje bildruta, vilket motverkar syftet.

Använda XBF2

XBF2 är en binär representation av XAML-markering som undviker alla kostnader för textanalys vid körningstid. Det optimerar också din binär för laddning och trädskapande och tillåter "snabbväg" för XAML-typer för att förbättra kostnaderna för heap- och objektskapande, exempelvis VSM, ResourceDictionary, Styles och så vidare. Det är helt minnesmappat så det finns inget heap-fotavtryck för inläsning och läsning av en XAML-sida. Dessutom minskar diskfotavtrycket för lagrade XAML-sidor i en appx. XBF2 är en mer kompakt representation och kan minska diskfotavtrycket för jämförande XAML/XBF1-filer med upp till 50%. Den inbyggda appen Foton såg till exempel cirka 60% minskning efter konvertering till XBF2 som sjönk från cirka ~1 mb XBF1-tillgångar till ~400 kB XBF2-tillgångar. Vi har också sett appar dra nytta av allt från 15 till 20% i CPU och 10 till 15% i Win32-heap.

XAML-inbyggda kontroller och ordlistor som ramverket tillhandahåller är redan helt XBF2-aktiverade. För din egen app kontrollerar du att projektfilen deklarerar TargetPlatformVersion 8.2 eller senare.

Om du vill kontrollera om du har XBF2 öppnar du appen i en binär redigerare. den 12:e och 13:e byteen är 00 02 om du har XBF2.