.NET MAUI teljesítményoptimalizálás: indítási idő, renderelés és memória (2026)

Gyakorlati .NET MAUI teljesítményoptimalizálás: hidegindítás 900 ms alá, Native AOT beállítás, Handler mapper trükkök, memóriaszivárgás felderítés és dotnet-trace használat. Valós kódmintákkal a .NET 9/10 verzióhoz.

.NET MAUI Teljesítmény Optimalizálás 2026
On this page
  1. Mit jelent a .NET MAUI teljesítményoptimalizálás?
  2. Hogyan mérjük az indítási időt .NET MAUI-ban?
  3. A MauiProgram optimalizálása és lusta betöltés
  4. Native AOT és fordítási optimalizációk
  5. Handler architektúra és renderelési pipeline
  6. Layout kompresszió és virtualizáció
  7. Képkezelés és memóriahatékonyság
  8. Memóriaszivárgások azonosítása és javítása
  9. Profilozó eszközök: dotnet-trace, Instruments, Perfetto
  10. Gyakran ismételt kérdések

Frissítve: 2026. augusztus 20.

A .NET MAUI teljesítményoptimalizálás lényege, hogy az alkalmazás indítási idejét, memóriafogyasztását és renderelési sebességét együtt hangoljuk – nem elég csak egyet javítani. A 2026-os .NET 9 és 10 verziók már natív AOT-t, tömörített layout fát és új Handler pipeline-t kínálnak, amelyekkel egy jól konfigurált MAUI alkalmazás hidegindítása Androidon 900 ms alá, iOS-en 500 ms alá csökkenthető. Az utolsó projektemen ezt a hármast együtt húztuk meg, és a különbség (900 ms → 610 ms Androidon) valóban érezhető volt a felhasználók visszajelzésein. Ebben a gyakorlati útmutatóban a mérésektől a konkrét kódmintákig végigmegyünk minden fontos beavatkozási ponton.

Mit jelent a .NET MAUI teljesítményoptimalizálás?

Nézzük meg először, mit is jelent pontosan ez a fogalom. A .NET MAUI teljesítményoptimalizálás egy összetett folyamat, amely négy dimenziót fed le: az indítási időt (cold start és warm start), a képkockasebességet (jellemzően 60 vagy 120 FPS), a memóriafogyasztást (különösen a natív heapre gyakorolt hatást) és az akkumulátor terhelést. Ezek együtt határozzák meg, hogy egy felhasználó „gyorsnak" érzi-e az alkalmazást. A Google Play Console 2024 óta a Core Vitals részeként pontozza a hidegindítás medián értékét, iOS-en pedig a MetricKit ad hivatalos jelzést az App Store visszajelzésekhez.

Fontos tisztázni, hogy a MAUI nem lassabb, mint egy natív alkalmazás. Csak több „alapértelmezett" beállítást tartalmaz, amelyek fejlesztői kényelmet biztosítanak, de futásidőben árat kérnek. Például amikor az Application.Current.MainPage-re támaszkodunk a Shell helyett, extra allokációk keletkeznek. Vagy amikor a Grid-ben rétegzett RowDefinition="Auto" sorokat használunk, minden layout ciklusban újramérünk mindent. Az optimalizálás lényege, hogy ezeket az alapértelmezéseket tudatosan kicseréljük a projekt profilja alapján.

A gyakorlati beavatkozásokat érdemes prioritás szerint sorrendbe rakni: először a build-time (AOT, trimming, R8/ProGuard), majd a startup (MauiProgram, splash screen), végül a runtime (renderelés, memória, hálózat) optimalizációk. Ez a sorrend a legjobb megtérülést hozza. Egy AOT-fordítású app kevesebb JIT-tel indul, így a MauiProgram-optimalizáció hatása is nagyobbá válik.

Hogyan mérjük az indítási időt .NET MAUI-ban?

Optimalizálni csak azt tudod, amit megmérsz. Ez klisésen hangzik, de kegyetlenül igaz. A hidegindítás valós mérése három lépésből áll: teljes hűtés (kill app, majd várj legalább 30 másodpercet), utána egységes eszközön (nem emulátoron!), többszöri indítás. A .NET 9-től a MAUI beépített PlatformStartupMetrics eseményforrást ad, amelyet a dotnet-trace tud gyűjteni. Az alábbi minta bemutatja, hogyan iratkozz fel a saját méréshez:

// MauiProgram.cs
public static MauiApp CreateMauiApp()
{
    var stopwatch = System.Diagnostics.Stopwatch.StartNew();

    var builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp<App>()
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        });

    // Csak fejlesztéskor mérjünk, éles buildben #if DEBUG-gal takarjuk el
#if DEBUG
    builder.Services.AddSingleton<IStartupMetrics>(sp =>
        new StartupMetrics(stopwatch));
#endif

    var app = builder.Build();
    stopwatch.Stop();
    System.Diagnostics.Debug.WriteLine(
        $"MauiApp épített: {stopwatch.ElapsedMilliseconds} ms");

    return app;
}

Androidon a legpontosabb szám a adb shell am start -W -n com.company.app/.MainActivity paranccsal jön. Ez visszaadja a ThisTime (a fő activity megjelenéséig eltelt idő) és a TotalTime (az app teljes felkészülési ideje) értékeket. iOS-en az Xcode Instruments „App Launch" template a bevált eszköz, 2026-ban már figyelmeztet a 400 ms feletti pre-main időre. Egy egészséges MAUI alkalmazás célértékei:

MetrikaKiválóElfogadhatóGyenge
Android cold start< 700 ms700–1500 ms> 1500 ms
iOS cold start< 500 ms500–1000 ms> 1000 ms
Memória (átlag)< 120 MB120–200 MB> 200 MB
Bináris méret (APK)< 25 MB25–50 MB> 50 MB
FPS listákban60 FPS45–60 FPS< 45 FPS

A MauiProgram optimalizálása és lusta betöltés

Őszintén szólva itt szoktam a legnagyobb sebességnövekedést látni. A leggyakoribb hidegindítási bűnök a MauiProgram.CreateMauiApp-ban rejtőznek. Ha itt szinkron módon inicializálod az adatbázist, az analitikát vagy egy hitelesítési tokent, akkor blokkolod a felület első renderelését. A megoldás az, hogy csak azt hívjuk meg indításkor, amit az első képernyő valóban használ. Minden mást pedig Lazy<T> vagy egy háttér Task mögé teszünk. Az alábbi példa mutatja, hogyan:

// Rossz példa - blokkolja az indítást
builder.Services.AddSingleton<IDatabase>(sp =>
{
    var db = new SqliteDatabase();
    db.Migrate(); // szinkron I/O
    db.PreloadCache();
    return db;
});

// Jó példa - lusta és aszinkron
builder.Services.AddSingleton<Lazy<IDatabase>>(sp =>
    new Lazy<IDatabase>(() =>
    {
        var db = new SqliteDatabase();
        db.Migrate();
        return db;
    }));

// A háttérben előmelegítjük, miután a UI már látszik
builder.Services.AddHostedService<DatabaseWarmupService>();

Használd ki a MAUI beépített Dependency Injection támogatását: a Transient szolgáltatások olcsóbban készülnek, mint a Singleton-ok, ha ritkán kellenek. A ConfigureLifecycleEvents-ben pedig érdemes elkülöníteni a WillFinishLaunching (kritikus, gyors) és a OnResume (halasztható) munkákat.

Native AOT és fordítási optimalizációk

A .NET 9-től kezdve a MAUI hivatalosan is támogatja a Native AOT-t iOS-en és Mac Catalystön, Androidon pedig a NativeAOT for Android preview már stabil. Az AOT előre lefordítja az IL kódot natív gépi kódra, így a JIT költsége eltűnik, a startup gyorsul, a bináris viszont kisebb lesz a nem használt kód eltávolítása miatt. A projekt csoportba az alábbi tulajdonságokat kell felvenni:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
    <PublishAot>true</PublishAot>
    <IlcOptimizationPreference>Speed</IlcOptimizationPreference>
    <TrimMode>full</TrimMode>
    <UseInterpreter>false</UseInterpreter>
    <RunAOTCompilation>true</RunAOTCompilation>
    <AndroidLinkTool>r8</AndroidLinkTool>
</PropertyGroup>

Az AOT bekapcsolása nem varázsütés. Reflexiót használó könyvtárak elakadhatnak, én pedig ezt a hibát ki is fizettem az első AOT-buildemnél (a Newtonsoft.Json csendben halt meg deszerializáláskor). A hivatalos .NET 9 MAUI mi újság oldal pontos listát ad a támogatott csomagokról. A leggyakoribb konfliktus a Newtonsoft.Json és bizonyos DI konténerek (mint az Autofac), ezeket cseréld System.Text.Json-ra és a beépített Microsoft.Extensions.DependencyInjection-re. A trimming (kód eltávolítás) figyelmeztetéseket <TrimmerRootAssembly> vagy XML descriptor fájllal lehet elnémítani, de csak akkor, ha ténylegesen szükség van a szimbólumra.

Androidon a R8 kódszűkítő tovább csökkenti az APK-t, a NuGet Xamarin.AndroidX.Startup csomag pedig segít a natív inicializálás sorrendjét kordában tartani. Az App Bundle (.aab) formátumot mindig használd APK helyett. A Google Play automatikusan generálja belőle az eszközre szabott darabokat, ami átlagosan 35%-kal kisebb telepített méretet eredményez.

Handler architektúra és renderelési pipeline

A .NET MAUI Handler modell váltotta le a Xamarin.Forms nehézkes Renderer koncepcióját. Míg egy Renderer minden natív kontroll köré egy plusz réteget húzott, a Handler csak egy vékony Mapper szótár, amely a virtuális tulajdonságokat natív hívásokká alakítja. Ez konkrétan azt jelenti, hogy egy Label renderelése iOS-en 40%-kal, Androidon 25%-kal kevesebb allokációval jár, mint a Xamarin.Forms időkben.

A saját kontrolljainknál is használjuk ki ezt. Ha csak egy tulajdonságot akarunk módosítani egy natív nézeten, ne írjunk teljes custom kontrollt. Toldjuk meg a meglévő Handler Mapperjét:

#if ANDROID
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(
    "NoUnderline",
    (handler, view) =>
    {
        handler.PlatformView.BackgroundTintList =
            Android.Content.Res.ColorStateList.ValueOf(
                Android.Graphics.Color.Transparent);
    });
#endif

#if IOS
Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping(
    "BorderStyle",
    (handler, view) =>
    {
        handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None;
    });
#endif

Ez a minta jóval olcsóbb, mint egy custom ContentView létrehozása, mert nem hoz létre új vizuális elemet a fában. Ha viszont teljes új kontrollra van szükség, mindig a Handler-alapú megközelítést válaszd a Renderer-alapú helyett, és regisztráld a ConfigureMauiHandlers-ben.

Layout kompresszió és virtualizáció

A vizuális fa mérete közvetlenül befolyásolja a renderelési időt. Minden extra StackLayout vagy Grid mérési és elrendezési ciklust ad hozzá. A MAUI-ban használd inkább a Grid-et fix méretekkel, kerüld a beágyazott StackLayout-okat, és éles kódban távolítsd el a debug célra használt Border wrappereket. A VerticalStackLayout és HorizontalStackLayout egytengelyes esetekben mindig gyorsabb, mint az általános StackLayout.

Listák esetén a CollectionView a helyes választás, mert virtualizál. Ha korábban a CollectionView alapoktól haladó szintig cikkünket olvastad, tudod, hogy az ItemsUpdatingScrollMode, a RemainingItemsThreshold és a ItemSizingStrategy="MeasureFirstItem" beállítások együtt sokat számítanak. A DataTemplate-ben pedig ne végezz drága konvertálást. Tedd inkább a ViewModelbe egy előre kiszámolt tulajdonságba.

A képkocka esések (a hírhedt „jank") gyakran a fő szálon futó munkából erednek. Használj Dispatcher.DispatchAsync-et vagy Task.Run-t számításigényes műveletekhez, és a felület frissítését hozd vissza a fő szálra. A .NET 9-től elérhető MainThread.InvokeOnMainThreadAsync már támogatja a ConfigureAwait(false)-t is a folytatásban, ami tovább csökkenti a szálváltás költségét.

Képkezelés és memóriahatékonyság

A képek a leggyakoribb memóriatolvajok. Egy 4000×3000-es JPEG dekódolás után kb. 48 MB RAM-ot foglal. Ha ezt csak egy 300 pixel magas listaelemen mutatjuk, óriási pazarlás. A megoldás a méretezés a Handler szintjén. Használd a ImageSource.FromStream helyett a DownsizedImageSource-t, vagy építsd be a FFImageLoading MAUI portját, amely LRU cache-t, disk cache-t és transzformációt is ad. Az alapminta így néz ki:

<ffimage:CachedImage
    Source="{Binding ThumbnailUrl}"
    DownsampleWidth="200"
    DownsampleToViewSize="true"
    LoadingPlaceholder="placeholder.png"
    ErrorPlaceholder="error.png"
    CacheDuration="30"
    RetryCount="2"
    RetryDelay="500" />

Az embedded képek (a projekt Resources/Images mappájában) automatikusan több DPI-re generálódnak. Ne használj svg-t olyan helyen, ahol egy 24×24 pixeles PNG bőven elég, mert az SVG minden renderelésnél újra rajzolódik. A hálózatról töltött képekhez pedig mindig adj meg maximum méretet, hogy a szerver ne küldjön feleslegesen nagy fájlt.

Memóriaszivárgások azonosítása és javítása

A memóriaszivárgás akkor lép fel, amikor egy objektumra még mindig hivatkozik valami, holott már nem kellene. .NET MAUI-ban a leggyakoribb okok: statikus eseménykezelők (nem iratkozunk le), MessagingCenter-ből származó feliratkozások, a Shell útvonalain keresztüli page cache és a natív oldalról származó referenciák (különösen iOS-en, ahol a NSObject-ekre a GC nem lát rá). Én magam is beleestem ebbe: egy MessagingCenter feliratkozás miatt egy ProductPage 47 példányban élt tovább egy 5 perces szimuláció után. A javítás alapja: minden eseményre iratkozz le ott, ahol feliratkoztál, és használd a beépített WeakEventManager-t vagy a CommunityToolkit.Mvvm WeakReferenceMessenger-jét.

public partial class ProductPage : ContentPage
{
    private readonly WeakEventManager _events = new();

    protected override void OnAppearing()
    {
        base.OnAppearing();
        // Erős referencia HELYETT WeakEventManager
        MyService.DataChanged += OnDataChanged;
    }

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        // KRITIKUS: mindig iratkozz le
        MyService.DataChanged -= OnDataChanged;
    }

    private void OnDataChanged(object? sender, DataChangedEventArgs e)
    {
        // Frissítés
    }
}

A ViewModel oldalán ugyanez a szabály. Ha egy CommunityToolkit.Mvvm alapú ViewModel aktív Timer-t vagy CancellationToken-t tart, mindig implementáld az IDisposable-t, és hívd a Page.Disappearing eseményén. A szivárgásokat a Visual Studio 2026 Snapshot Debugger vagy a JetBrains dotMemory tudja megbízhatóan megtalálni. A recept egyszerű: vegyél két snapshotot, navigálj oda-vissza az oldalra tízszer, és nézd meg, mely osztályokból lett több példány, mint az elején.

Profilozó eszközök: dotnet-trace, Instruments, Perfetto

Három eszköz nélkül nem érdemes komoly optimalizálásba fogni. A dotnet-trace platformfüggetlen, futásidejű CPU és allokációs profilt ad; a Xcode Instruments (Time Profiler, Allocations, Leaks) iOS-en és Mac Catalystön nyújt natív pontosságot; a Perfetto pedig az Android Studio által is ajánlott, alacsony overheadű trace-eszköz. A dotnet-trace alapparancsa:

# Csatlakozás egy futó Android app-hoz
dotnet-trace collect     --diagnostic-port ~/dsrouter.sock,connect     --format speedscope     --profile cpu-sampling     --duration 00:00:30

# A .speedscope.json fájlt megnyithatod a https://www.speedscope.app oldalon

Az elemzés során három mintát keress: (1) hosszú, önmagukban futó „flat" függvényeket, ezek az O(n²) algoritmusok jelei; (2) mély, ismétlődő layout és measure hívásokat, ezeknél a fát kell laposítani; (3) sűrű GC eseményeket, ezek allokációs stormra utalnak, és pool-ozással oldhatók. A hivatalos dotnet-trace dokumentáció részletesen tárgyalja a szűrőket és a szimbólumfeloldást.

Végezetül: az optimalizálás iteratív munka, nem egyszeri projekt. Vezess be regressziós méréseket a CI/CD pipeline-ba (például az App Center Test-tel vagy Firebase Test Lab-bel), és állíts be küszöböket. Ha egy PR 10%-kal lassítja az indítást, azt a build utasítsa el. Így nem kell később hetekig visszakeresned, melyik commit tette lassúvá az alkalmazást.

Gyakran ismételt kérdések

Miért lassú a .NET MAUI hidegindítása Androidon?

A leggyakoribb ok a MauiProgram.CreateMauiApp-ba pakolt szinkron munka (adatbázis migráció, analitika inicializálás, hálózati hívások). Halaszd el Lazy<T> vagy háttér HostedService mögé, kapcsold be az AOT-t és az R8-at, valamint használj App Bundle-t APK helyett. Ezek együtt 40–60%-os javulást hozhatnak.

Mennyivel gyorsabb a Native AOT egy klasszikus MAUI buildhez képest?

A Microsoft 2026-os benchmarkjai iOS-en 30–40%-os, Androidon 20–25%-os indítási időcsökkenést mutatnak, valamint 15–35%-os bináris méretcsökkenést. Cserébe elveszíted a JIT-et és néhány reflexióalapú könyvtár nem működik, ezeket tesztelni kell, mielőtt bekapcsolod éles buildben.

Hogyan találok memóriaszivárgást .NET MAUI alkalmazásban?

A legmegbízhatóbb módszer két snapshot: a Visual Studio Snapshot Debuggerrel vagy JetBrains dotMemory-val vegyél egy alap snapshotot, navigálj oda-vissza egy gyanús oldalra 10-szer, majd vegyél másodikat. Ha az oldal osztályaiból 10-zel több példány szerepel, szivárgás van. Nézd meg a referencialáncot, jellemzően eseménykezelő vagy statikus mezőt találsz.

CollectionView vagy ListView, melyik gyorsabb?

A CollectionView gyorsabb és rugalmasabb, mert modern virtualizáción alapul (RecyclerView Androidon, UICollectionView iOS-en). A ListView-t a .NET 10-ben már deprecatedként jelölik, új projektekben ne használd. Meglévő ListView-t migrálj át, ha 20-nál több elemet jelenítesz meg.

Milyen eszközzel mérjem a MAUI app FPS-ét?

Androidon a adb shell dumpsys gfxinfo com.company.app parancs adja a képkocka statisztikákat és a jank százalékot. iOS-en az Xcode Instruments „Core Animation" template mutat élő FPS-t. Mindkettőt valós eszközön futtasd, mert emulátoron és szimulátoron a számok félrevezetőek.