Dela via


Metodtips för winUI-appens startprestanda

Skapa WinUI-appar med Windows App SDK som börjar snabbt genom att minska startarbetet, förenkla den första ramen och läsa in icke-kritiska funktioner när fönstret är interaktivt.

Metodtips för appens startprestanda

Delvis uppfattar användarna om din app är snabb eller långsam baserat på hur lång tid det tar att starta. I det här avsnittet börjar en apps starttid när användaren startar appen och slutar när användaren kan interagera med appen på ett meningsfullt sätt. Den här artikeln innehåller förslag på hur du får bättre startprestanda från en WinUI-app.

Mäta appens starttid

Se till att starta appen några gånger innan du faktiskt mäter starttiden. Detta ger dig en baslinje för mätningen och hjälper dig att se till att du mäter så ganska kort starttid som möjligt.

Utför mått som är representativa för vad slutanvändaren kommer att uppleva. Measure Release bygger på representativ maskinvara, tittar på både kall och varm start och fokuserar på tiden till den första interaktiva ramen i stället för bara tiden tills processen finns.

Skjut upp arbetet så länge som möjligt

För att förbättra appens starttid gör du bara det arbete som absolut behöver göras för att låta användaren börja interagera med appen. Detta kan vara särskilt fördelaktigt om du kan fördröja inläsningen av ytterligare sammansättningar. Den vanliga språkkörningen läser in en sammansättning första gången den används. Om du kan minimera antalet sammansättningar som läses in kanske du kan förbättra appens starttid och minnesförbrukning.

Utför långvarigt arbete självständigt

Din app kan vara interaktiv även om det finns delar av appen som inte är helt funktionella. Om din app till exempel visar data som tar en stund att hämta kan du få koden att köras oberoende av appens startkod genom att hämta data asynkront. När data är tillgängliga fyller du i appens användargränssnitt med data.

Många av de API:er som hämtar data är asynkrona, så du kommer förmodligen att hämta data asynkront ändå. Mer information finns i Asynkron programmering med async och await. Om du arbetar som inte använder asynkrona API:er kan du använda Task klassen för att utföra tidskrävande arbete så att du inte blockerar användaren från att interagera med appen. Detta håller din app responsiv medan data läses in.

Om det tar särskilt lång tid för appen att läsa in en del av användargränssnittet bör du överväga att visa ett meddelande i det området, till exempel "Hämta senaste data" så att användarna vet att appen fortfarande bearbetas.

Minimera starttiden

Alla utom de enklaste apparna kräver en märkbar tid för att läsa in resurser, parsa XAML, konfigurera datastrukturer och köra logik under lanseringen. För WinUI-appar hjälper det att tänka på start i fyra steg: processstart, skapande av fönster, skapande av huvudsidor och layout/återgivning för den första ramen.

Startperioden är tiden mellan det ögonblick då en användare startar appen och det ögonblick då appen blir funktionell. Det här är en kritisk tid eftersom det är en användares första intryck av din app. Användarna förväntar sig omedelbar och kontinuerlig feedback från systemet och från appar. Systemet och appen uppfattas som trasiga eller dåligt utformade när appar inte startar snabbt.

Introduktion till startfaserna

Start innebär ett antal rörliga delar, och alla måste samordnas för bästa användarupplevelse. Följande steg sker mellan användaren som startar din app och det programinnehåll som visas.

  • Processen startar och den mallgenererade startkodens anrop Main.
  • Objektet Application skapas.
    • Appkonstruktorn anropar InitializeComponent, vilket får App.xaml att parsas och skapa objekt.
  • Application.OnLaunched har aktiverats .
    • Appkoden skapar huvudfönstret, tilldelar det inledande innehållet och anropar Activate.
    • Huvudsideskonstruktorn anropar InitializeComponent, vilket gör att sidan XAML parsas och objekt skapas.
  • XAML-ramverket kör layoutpasset, inklusive mäta och arrangera.
    • ApplyTemplate gör att kontrollmallens innehåll skapas för varje kontroll, vilket vanligtvis är den största delen av layouttiden under starten.
  • Rendera skapar visuella objekt för fönsterinnehållet.
  • Den första ramen visas och arbetet efter starten fortsätter asynkront.

Fokusera mindre på din startup-karriär

Håll din startkodsökväg fri från allt som inte behövs för din första bildruta.

  • Om du har användar-DLL:er som innehåller kontroller som inte behövs under den första ramen bör du överväga att fördröja inläsningen av dem.
  • Om du har en del av användargränssnittet som är beroende av data från molnet delar du upp användargränssnittet. Ta först upp användargränssnittet som inte är beroende av molndata och sedan asynkront ta upp det molnberoende användargränssnittet. Du bör också överväga att cachelagra data lokalt så att programmet kan fungera offline eller inte påverkas av långsamma nätverksanslutningar.
  • Visa förloppsgränssnittet om användargränssnittet väntar på data.
  • Var försiktig med appdesign som omfattar mycket parsning av konfigurationsfiler eller användargränssnitt som genereras dynamiskt av kod.

Minska antalet element

Startprestanda i en XAML-app korreleras direkt med antalet element som du skapar under starten. Ju färre element du skapar, desto mindre tid tar det att starta appen. Som ett grovt riktmärke bör du överväga att varje element tar 1 ms att skapa.

  • Mallar som används i objektkontroller kan ha störst inverkan eftersom de upprepas flera gånger. Se ListView och GridView UI-optimering.
  • Användarkontroller och kontrollmallar utökas, så de bör också beaktas.
  • Om du skapar en XAML som inte visas på skärmen bör du motivera om dessa delar av XAML ska skapas under starten.

Fönstret Visual Studio Live Visual Tree visar antalet underordnade element för varje nod i trädet.

Levande visuellt träd.

Använd fördröjning. Att komprimera ett element eller ange dess opacitet till 0 hindrar inte elementet från att skapas. Använda x:Load eller x:DeferLoadStrategy kan du fördröja inläsningen av en del av användargränssnittet och läsa in det när det behövs. Det här är ett bra sätt att fördröja bearbetningen av användargränssnittet som inte visas under starten så att du kan läsa in det när det behövs eller som en del av en uppsättning fördröjd logik. För att utlösa inläsningen, behöver du bara anropa FindName-elementet. Ett exempel och mer information finns i x:Load-attributet och x:DeferLoadStrategy-attributet.

Virtualisering. Om du har list- eller repeaterinnehåll i användargränssnittet rekommenderar vi starkt att du använder virtualisering av användargränssnittet. Om listgränssnittet inte virtualiseras betalar du kostnaden för att skapa alla element i förväg och det kan göra starten långsammare. Se ListView och GridView UI-optimering.

Programprestanda handlar inte bara om råa prestanda. Det handlar också om uppfattning. Om du ändrar ordning på åtgärder så att visuella aspekter först inträffar får det ofta användaren att känna att programmet går snabbare. Användarna anser att applikationen är laddad när innehållet finns på skärmen. Program behöver ofta göra flera saker under starten, och allt arbete krävs inte för att ta upp användargränssnittet, så dessa delar bör fördröjas eller prioriteras lägre än användargränssnittet.

Den här artikeln handlar om den första ramen, som kommer från animerings- och videoterminologi och är ett mått på hur lång tid det tar innan innehållet visas av slutanvändaren.

Förbättra uppfattningen av startup

Låt oss använda exemplet med ett enkelt onlinespel för att identifiera varje fas av start och olika tekniker för att ge användaren feedback under hela processen.

I den första fasen startas processen och appen skapar sitt fönster. Under den här tiden har användaren ännu inte sett appens eget innehåll. För att snabbt få ett lätt fönster på skärmen.

Den andra fasen omfattar att skapa och initiera de strukturer som är viktiga för spelet. Om appen snabbt kan skapa sitt ursprungliga användargränssnitt med de data som är tillgängliga vid start är den här fasen trivial och du kan visa användargränssnittet omedelbart. Annars visa en enkel laddningssida medan appen initieras.

Hur inläsningssidan ser ut är upp till dig; Det kan vara så enkelt som att visa en förloppsindikator eller förloppsring. Den viktigaste punkten är att appen anger att den utför arbete innan den blir helt responsiv. När det gäller spelet kräver den första skärmen att vissa bilder och ljud läses in från disken till minnet. Dessa uppgifter tar tid, så appen håller användaren informerad genom att visa en inläsningssida med en enkel animering relaterad till spelets tema.

Den tredje fasen börjar efter att spelet har en minimal uppsättning information för att skapa ett interaktivt användargränssnitt som ersätter inläsningssidan. I det här läget kan den enda information som är tillgänglig för onlinespelet vara innehållet som appen läste in från disken. Spelet kan levereras med tillräckligt med innehåll för att skapa ett interaktivt användargränssnitt, men eftersom det är ett onlinespel kommer det inte att fungera fullt ut förrän det ansluter till Internet och hämtar ytterligare information. Tills den har all information som behövs kan användaren interagera med användargränssnittet, men funktioner som behöver ytterligare data från webben bör ge feedback om att innehållet fortfarande läses in. Det kan ta lite tid innan en app blir fullt fungerande, så det är viktigt att funktioner görs tillgängliga så snart som möjligt.

Nu när vi har identifierat de tre stegen i starten i onlinespelet ska vi koppla dem till den faktiska koden.

Fas 1 och fas 2

Använd endast appens konstruktor för att initiera datastrukturer som är viktiga för appen. Fokusera OnLaunched på att snabbt skapa det första fönstret, tilldela enkelt innehåll och aktivera fönstret så att appen kan visa feedback direkt.

public partial class App : Application
{
    public static Window MainWindow { get; private set; } = null!;

    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        base.OnLaunched(args);

        MainWindow = new MainWindow();
        MainWindow.Content = new LoadingPage();
        MainWindow.Activate();

        _ = InitializeAsync();
    }

    private async Task InitializeAsync()
    {
        // Asynchronously restore state and load the minimum data needed
        // to create the first interactive UI.
        await LoadInitialDataAsync();

        MainWindow.Content = new GameHomePage();
    }

    private static Task LoadInitialDataAsync()
    {
        // Download data to populate the initial UI.
        return Task.CompletedTask;
    }
}

En av de viktigaste uppgifterna i OnLaunched är att skapa ett användargränssnitt, tilldela det till Window.Content och sedan anropa Window.Activate. Om du behöver mer än ett aktiveringsflöde behåller du samma princip: visa enkelt innehåll snabbt och flytta dyrt arbete från den kritiska startvägen.

Appar som visar en inläsningssida under starten kan börja arbeta för att skapa huvudgränssnittet i bakgrunden. När elementet har skapats inträffar dess FrameworkElement.Loaded-händelse . I händelsehanteraren kan du ersätta fönstrets innehåll, som för närvarande är inläsningsskärmen, med den nyligen skapade startsidan.

Det är viktigt att en app med en utökad initieringsperiod visar en inläsningssida. Förutom att ge feedback om startprocessen bör fönstret aktiveras snabbt så att användarna ser att appen gör framsteg.

partial class GameHomePage : Page
{
    public GameHomePage()
    {
        InitializeComponent();

        // Add a handler to be called when the home page has been loaded.
        Loaded += GameHomePageLoaded;

        // Load the minimal amount of image and sound data from disk necessary
        // to create the home page.
    }

    private void GameHomePageLoaded(object sender, RoutedEventArgs e)
    {
        // Set the content of the main window to the home page now that it's
        // ready to be displayed.
        App.MainWindow.Content = this;
    }
}

Fas 3

Bara för att appen visade användargränssnittet betyder det inte att det är helt redo för användning. När det gäller vårt spel visas användargränssnittet med platshållare för funktioner som kräver data från Internet. Nu laddar spelet ned de ytterligare data som behövs för att appen ska fungera fullt ut och progressivt aktivera funktioner när data hämtas.

Ibland kan mycket av det innehåll som behövs för start paketeras med appen. Så är fallet med ett enkelt spel. Detta gör startprocessen ganska enkel. Men många program, till exempel nyhetsläsare och fotovisningsprogram, måste hämta information från webben för att bli funktionella. Dessa data kan vara stora och kan ta lång tid att ladda ned. Hur appen hämtar dessa data under starten kan ha en enorm inverkan på upplevd prestanda.

Du kan visa en inläsningssida för länge om en app försöker ladda ned en hel datauppsättning som den behöver för funktioner i den första eller andra fasen av starten. Det gör att en app ser låst ut. Vi rekommenderar att en app laddar ned den minsta mängd data som behövs för att visa ett interaktivt användargränssnitt med platshållarelement i fas 2 och sedan läser in data progressivt, vilket ersätter platshållarelementen, i fas 3. Mer information om hur du hanterar data finns i Optimera ListView och GridView.

Exakt hur en app reagerar på varje fas av start är helt upp till dig, men att ge användaren så mycket feedback som möjligt med hjälp av det enkla inledande användargränssnittet, inläsningsskärmar och progressiv datainläsning gör att appen känns snabbare.

Minimera hanterade sammansättningar i startsökvägen

Återanvändbar kod kommer ofta i form av moduler (DLL:er) som ingår i ett projekt. Inläsning av dessa moduler kräver åtkomst till disken, och kostnaden kan läggas till. Detta har störst inverkan på kall start, men det kan också påverka varm start. I .NET-appar försöker CLR fördröja den kostnaden så mycket som möjligt genom att läsa in sammansättningar på begäran. Det innebär att CLR inte läser in en modul förrän en körd metod refererar till den. Referera därför endast till sammansättningar som krävs för att starta appen i startkoden så att CLR inte läser in onödiga moduler. Om du har oanvända kodsökvägar i startvägen som har onödiga referenser flyttar du dessa kodsökvägar till andra metoder för att undvika onödiga belastningar.

Ett annat sätt att minska modulbelastningen är att kombinera appmoduler. Det tar vanligtvis mindre tid att läsa in en stor sammansättning än att läsa in två små. Detta är inte alltid möjligt, och du bör endast kombinera moduler om det inte gör någon väsentlig skillnad för utvecklarproduktivitet eller kodåteranvändning. Du kan använda verktyg som PerfView eller Windows Performance Analyzer (WPA) för att ta reda på vilka moduler som läses in vid start.

Göra smarta webbbegäranden

Du kan avsevärt förbättra inläsningstiden för en app genom att paketera dess innehåll lokalt, inklusive XAML, bilder och andra filer som är viktiga för appen. Diskåtgärder är snabbare än nätverksåtgärder. Om en app behöver en viss fil vid initieringen kan du minska den totala starttiden genom att läsa in den från disken i stället för att hämta den från en fjärrserver.

Logg- och cachesidor effektivt

Kontrollen Frame innehåller navigeringsfunktioner. Den erbjuder navigering till en sida (Navigate metod), navigeringsjournaler (BackStack och ForwardStack egenskaper GoForward och GoBack metoder), sidcachelagring (Page.NavigationCacheMode) och serialiseringsstöd (GetNavigationState -metod).

Prestandan som du bör känna till med Frame handlar främst om journalföring och sidcachelagring.

Ramjournalföring När du navigerar till en sida med Frame.Navigateläggs en PageStackEntry för den aktuella sidan till i Frame.BackStack samlingen. PageStackEntry är relativt liten, men det finns ingen inbyggd gräns för samlingens BackStack storlek. En användare kan eventuellt navigera i en loop och utöka samlingen på obestämd tid.

Innehåller PageStackEntry även parametern som skickades till Frame.Navigate metoden. Vi rekommenderar att parametern är en primitiv serialiserbar typ, till exempel en int eller string, för att metoden ska fungera Frame.GetNavigationState . Men den parametern kan potentiellt referera till ett objekt som står för mer betydande mängder arbetsminne eller andra resurser, vilket gör varje post i den BackStack mycket mer resurskrävande. Du kan till exempel använda StorageFile som en parameter, och därför kan BackStack hålla ett obegränsat antal filer öppna.

Därför rekommenderar vi att du håller navigeringsparametrarna små och begränsar storleken på BackStack. BackStack är en standardsamling i C#, så den kan trimmas helt enkelt genom att ta bort poster.

Cachelagring av sidor. När du som standard navigerar till en sida med Frame.Navigate metoden instansieras en ny instans av sidan. Om du sedan går tillbaka till föregående sida med Frame.GoBackallokeras en ny instans av föregående sida.

Frame erbjuder också en valfri sidcache som kan undvika dessa instansieringar. Om du vill få en sida i cacheminnet använder du egenskapen Page.NavigationCacheMode . Genom att ställa in det läget till Required tvingar du sidan att cachelagras, medan inställning till Enabled tillåter att den kan cachelagras. Som standard är cachestorleken 10 sidor, men det kan åsidosättas med Frame.CacheSize egenskapen . Alla Required sidor cachelagras, och om det finns färre än CacheSize nödvändiga sidor, kan även Enabled sidor cachelagras.

Sidcachelagring kan hjälpa prestanda genom att undvika instansiering och därmed förbättra navigeringsprestandan. Cachelagring av sidor kan skada prestandan genom övercachelagring, vilket kan påverka arbetsminnet.

Därför rekommenderar vi att du använder sidcachelagring efter behov för ditt program. Anta till exempel att du har en app som visar en lista över objekt i en Frame, och när du väljer ett objekt navigerar den till en detaljsida för objektet. Listsidan bör förmodligen vara inställd på cacheminne. Om detaljsidan är densamma för alla objekt bör den förmodligen också cachelagras. Men om detaljsidan är mer heterogen kan det vara bättre att lämna cachelagringen av.