.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.
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.
A hidegindítás elsődleges gyilkosa a MauiProgram.CreateMauiApp-ba pakolt szinkron munka. Halasszuk el, vagy tegyük Lazy<T> mögé.
A Native AOT (.NET 9+) iOS-en 30–40%-kal, Androidon 20–25%-kal csökkenti a bináris méretet és az indítási időt.
A Handler-alapú architektúra gyorsabb, mint a Xamarin.Forms renderer modellje, de kézzel írt Mapperek nélkül nem használjuk ki.
Használjunk CollectionView-t ItemsUpdatingScrollMode="KeepItemsInView" értékkel, és tiltsuk le a felesleges layout ciklusokat a Grid egyszerűsítésével.
A memóriaszivárgások 80%-a eseménykezelőkből és statikus referenciákból ered. Használjunk WeakEventManager-t, és iratkozzunk le az OnDisappearing-ben.
Mérjünk mindig valós eszközön dotnet-trace és Xcode Instruments segítségével. Az emulátoros számok félrevezetők.
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:
Metrika
Kiváló
Elfogadható
Gyenge
Android cold start
< 700 ms
700–1500 ms
> 1500 ms
iOS cold start
< 500 ms
500–1000 ms
> 1000 ms
Memória (átlag)
< 120 MB
120–200 MB
> 200 MB
Bináris méret (APK)
< 25 MB
25–50 MB
> 50 MB
FPS listákban
60 FPS
45–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:
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:
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:
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.
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.
A CommunityToolkit.Mvvm forrásgenerátorai 60-70%-kal rövidebbé teszik a .NET MAUI ViewModel-eket. Gyakorlati útmutató ObservableProperty, RelayCommand, Messenger és validáció témákban éles projektből származó tippekkel.