Optimizacija performansi .NET MAUI 9 aplikacija: Vodič s primjerima koda

Naučite kako optimizirati .NET MAUI 9 aplikacije — od smanjenja vremena pokretanja i UI renderiranja, preko upravljanja memorijom i NativeAOT kompilacije, do profiliranja s konkretnim primjerima koda.

Zašto su performanse ključne za uspjeh mobilne aplikacije

Korisnici mobilnih aplikacija nemaju strpljenja — i to nije klišej, nego stvarnost s kojom se suočavamo svaki dan. Istraživanja dosljedno pokazuju da više od 50% korisnika napušta aplikaciju ako se učitava dulje od tri sekunde, a čak 80% nikada ne otvori aplikaciju nakon lošeg prvog iskustva. U svijetu .NET MAUI razvoja, gdje jedna baza koda cilja Android, iOS, macOS i Windows, performanse postaju još kritičniji faktor — jer svaka platforma ima vlastite karakteristike i ograničenja.

.NET MAUI 9 donio je značajna poboljšanja u odnosu na prijašnje verzije: podršku za NativeAOT, poboljšane kompilirane vezove i nove CollectionView handlere. Ali sam framework nije dovoljan. Način na koji arhitektirate, pišete i konfigurirate svoju aplikaciju odlučuje hoće li vaši korisnici uživati u glatkom iskustvu ili frustrirano zatvarati aplikaciju.

U ovom vodiču pokriti ćemo sve aspekte optimizacije .NET MAUI aplikacija — od smanjenja vremena pokretanja i optimizacije korisničkog sučelja, preko upravljanja memorijom i sprečavanja curenja, do smanjenja veličine aplikacije i korištenja profesionalnih alata za profiliranje. Svaki dio sadrži konkretne primjere koda koje možete odmah primijeniti u svojim projektima.

Optimizacija vremena pokretanja aplikacije

Vrijeme pokretanja je prvo što korisnik doživljava. Ako vaša aplikacija treba pet sekundi da prikaže prvi ekran, već ste izgubili bitku — korisnik je već počeo sumnjati je li uopće vrijedno čekati.

Koristite Shell navigaciju

Shell navigacija je jedan od najjednostavnijih načina da ubrzate pokretanje. Za razliku od tradicionalnog pristupa s TabbedPage gdje se sve stranice kreiraju pri pokretanju, Shell stvara stranice na zahtjev — tek kada korisnik navigira na njih.

<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:views="clr-namespace:MojaAplikacija.Views"
       x:Class="MojaAplikacija.AppShell">

    <TabBar>
        <ShellContent Title="Početna"
                      Icon="home.png"
                      ContentTemplate="{DataTemplate views:HomePage}" />

        <ShellContent Title="Profil"
                      Icon="profile.png"
                      ContentTemplate="{DataTemplate views:ProfilePage}" />

        <ShellContent Title="Postavke"
                      Icon="settings.png"
                      ContentTemplate="{DataTemplate views:SettingsPage}" />
    </TabBar>
</Shell>

Ključna razlika je korištenje ContentTemplate umjesto izravnog Content. S ContentTemplate, stranica se instancira tek kada korisnik prvi put navigira na nju — čime se izbjegava nepotrebna inicijalizacija pri pokretanju. Jednostavna promjena, a efekt je primjetan.

Optimizacija dependency injection kontejnera

.NET MAUI koristi vlastitu optimiziranu DI implementaciju umjesto Microsoft.Extensions.Hosting, što je samo po sebi donijelo 5–10% brže pokretanje. No, način na koji registrirate servise i dalje značajno utječe na performanse.

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder.UseMauiApp<App>();

    // Koristite AddTransient za lagane servise
    builder.Services.AddTransient<HomePage>();
    builder.Services.AddTransient<HomeViewModel>();

    // Koristite AddSingleton za servise koji se dijele
    builder.Services.AddSingleton<IApiService, ApiService>();
    builder.Services.AddSingleton<ICacheService, CacheService>();

    // Koristite Lazy<T> za teške servise koji nisu odmah potrebni
    builder.Services.AddSingleton<Lazy<IAnalyticsService>>(sp =>
        new Lazy<IAnalyticsService>(() => sp.GetRequiredService<IAnalyticsService>()));

    return builder.Build();
}

Nekoliko ključnih pravila za DI u kontekstu performansi:

  • Registrirajte samo servise koji su stvarno potrebni — svaka registracija dodaje overhead pri pokretanju
  • Izbjegavajte duboko ugniježđene zavisnosti jer se svaki lanac mora razriješiti rekurzivno
  • Koristite Lazy<T> za servise koji nisu potrebni odmah pri pokretanju, poput analitike ili složenih inicijalizacija
  • Nemojte registrirati servise kao Scoped osim ako nemate vrlo specifičan razlog

Asinkrona inicijalizacija

Konstruktor App klase i metoda OnStart izvršavaju se na glavnoj niti. Sve što tamo stavite blokira prikaz prvog ekrana — a to je zadnje što želite. Premjestite teške operacije u pozadinu:

public partial class App : Application
{
    public App(IServiceProvider serviceProvider)
    {
        InitializeComponent();
        MainPage = new AppShell();

        // Ne radite ovo u konstruktoru:
        // var data = apiService.GetInitialData().Result; // BLOKIRA UI!

        // Umjesto toga, koristite lazy task pattern:
        Task.Run(async () =>
        {
            var cacheService = serviceProvider.GetRequiredService<ICacheService>();
            await cacheService.PreloadAsync();

            var analyticsService = serviceProvider.GetRequiredService<IAnalyticsService>();
            await analyticsService.InitializeAsync();
        });
    }
}

Sve što nije kritično za prikaz prvog ekrana — predmemoriranje podataka, inicijalizacija analitike, provjera ažuriranja — trebalo bi se odvijati u pozadini. Korisnik neka vidi sučelje što prije, a ostatak neka se učita tiho.

Optimizacija korisničkog sučelja i renderiranja

Korisničko sučelje je ono što korisnici vide i dodiruju svake sekunde. Ako se lista trzne pri skrolanju ili ako tranzicija između stranica štuca, to se odmah primijeti — i teško se zaboravlja.

Izravnajte hijerarhiju rasporeda

Svaki ugniježđeni layout zahtijeva dodatne kalkulacije za mjerenje i pozicioniranje. Duboko ugniježđeni StackLayout unutar StackLayout unutar Grid drastično usporava renderiranje.

<!-- LOŠE: Duboko ugniježđeni layouti -->
<StackLayout>
    <StackLayout Orientation="Horizontal">
        <StackLayout>
            <Label Text="{Binding Name}" />
            <Label Text="{Binding Email}" />
        </StackLayout>
        <StackLayout>
            <Image Source="{Binding Avatar}" />
        </StackLayout>
    </StackLayout>
</StackLayout>

<!-- DOBRO: Ravan Grid layout -->
<Grid ColumnDefinitions="*, Auto"
      RowDefinitions="Auto, Auto">
    <Label Text="{Binding Name}"
           Grid.Row="0" Grid.Column="0" />
    <Label Text="{Binding Email}"
           Grid.Row="1" Grid.Column="0" />
    <Image Source="{Binding Avatar}"
           Grid.Row="0" Grid.Column="1"
           Grid.RowSpan="2" />
</Grid>

Jedan Grid s definiranim stupcima i redovima zamjenjuje tri ugniježđena StackLayout elementa. Vizualni rezultat je identičan, ali s manje kalkulacija i bržim renderiranjem. Pravilo palca: ako imate više od tri razine ugniježđenosti, vrijeme je za refaktoriranje.

Kompilirani vezovi (Compiled Bindings)

Kompilirani vezovi su jedna od najznačajnijih optimizacija u .NET MAUI 9. Umjesto razrješavanja vezova putem refleksije tijekom izvođenja, kompilirani vezovi se razrješavaju u vrijeme kompilacije. Rezultati su zaista impresivni:

  • OneWay i TwoWay vezovi — do 8 puta brži od klasičnih vezova
  • OneTime vezovi — do 20 puta brži od klasičnih vezova
  • Postavljanje BindingContext — do 5 puta brže

Da biste koristili kompilirane vezove, dovoljno je postaviti atribut x:DataType:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:MojaAplikacija.ViewModels"
             x:Class="MojaAplikacija.Views.ProductsPage"
             x:DataType="vm:ProductsViewModel">

    <CollectionView ItemsSource="{Binding Products}">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="vm:ProductItem">
                <Grid Padding="10" ColumnDefinitions="80, *">
                    <Image Source="{Binding ImageUrl}"
                           WidthRequest="60"
                           HeightRequest="60" />
                    <VerticalStackLayout Grid.Column="1" Spacing="4">
                        <Label Text="{Binding Name}"
                               FontAttributes="Bold" />
                        <Label Text="{Binding Price, StringFormat='{0:C}'}" />
                    </VerticalStackLayout>
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ContentPage>

Obratite pozornost na dva x:DataType atributa — jedan na razini stranice koji upućuje na ViewModel, i drugi unutar DataTemplate koji upućuje na tip pojedinačnog elementa. .NET MAUI 9 sad generira upozorenja pri kompilaciji za svaki vez koji nije kompiliran, što vam pomaže pronaći propuštene slučajeve (što je, iskreno, korisna promjena koja štedi dosta vremena).

Kompilirani vezovi u C# kodu (.NET MAUI 9)

.NET MAUI 9 uvodi novi pristup definiranja vezova u C# kodu koristeći lambda izraze umjesto stringova. Ovaj pristup omogućuje kompilacijsku provjeru i bolje performanse:

// Stari pristup sa stringovima (sporiji, bez provjere pri kompilaciji)
var label = new Label();
label.SetBinding(Label.TextProperty, "Name");

// Novi pristup s lambda izrazima (.NET MAUI 9)
var label = new Label();
label.SetBinding(Label.TextProperty,
    static (ProductItem item) => item.Name);

Lambda pristup je posebno važan ako planirate koristiti NativeAOT ili potpuno skraćivanje (full trimming) — u tim scenarijima stringovi za vezove jednostavno neće raditi, pa je bolje navići se na novi pristup što prije.

Optimizacija CollectionView komponente

CollectionView je preporučena kontrola za prikaz lista u .NET MAUI — zamijenila je stariji ListView i donosi ugrađenu virtualizaciju. Ali čak i s virtualizacijom, pogrešna uporaba može dovesti do problema s performansama.

public class ProductsViewModel : ObservableObject
{
    private readonly IApiService _apiService;
    private int _currentPage = 0;
    private const int PageSize = 20;

    [ObservableProperty]
    private ObservableCollection<ProductItem> _products = new();

    [ObservableProperty]
    private bool _isLoadingMore;

    [RelayCommand]
    private async Task LoadMoreAsync()
    {
        if (IsLoadingMore) return;

        IsLoadingMore = true;
        try
        {
            _currentPage++;
            var newItems = await _apiService
                .GetProductsAsync(_currentPage, PageSize);

            foreach (var item in newItems)
            {
                Products.Add(item);
            }
        }
        finally
        {
            IsLoadingMore = false;
        }
    }
}
<CollectionView ItemsSource="{Binding Products}"
                RemainingItemsThreshold="5"
                RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}">
    <CollectionView.ItemTemplate>
        <!-- DataTemplate ovdje -->
    </CollectionView.ItemTemplate>
</CollectionView>

Svojstvo RemainingItemsThreshold definira koliko preostalih stavki u listi treba ostati prije nego se aktivira učitavanje sljedeće stranice. Postavljanjem na 5, korisnik nikada neće primijetiti učitavanje — novi podaci se dohvaćaju dok još skrola kroz postojeće stavke.

Novi CollectionView handleri za iOS (.NET MAUI 9)

.NET MAUI 9 uveo je opcionalne nove handlere za CollectionView na iOS-u i Mac Catalystu koji koriste UICollectionView API-je za bolje performanse i stabilnost:

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder.UseMauiApp<App>();

#if IOS || MACCATALYST
    builder.ConfigureMauiHandlers(handlers =>
    {
        handlers.AddHandler<CollectionView,
            Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
        handlers.AddHandler<CarouselView,
            Microsoft.Maui.Controls.Handlers.Items2.CarouselViewHandler2>();
    });
#endif

    return builder.Build();
}

Ovi handleri su u .NET MAUI 9 još uvijek opcionalni, ali u .NET 10 postaju zadani. Ako ciljate iOS, vrijedi ih uključiti već sada — donose primjetno poboljšanje pri skrolanju velikih lista, a migracija kasnije neće biti ugodna ako to odgodite.

Upravljanje memorijom i sprečavanje curenja

Curenje memorije (memory leaks) jedan je od najopasnijih problema u mobilnim aplikacijama. Za razliku od spore liste ili dugog pokretanja koji su odmah vidljivi, curenje memorije se nakuplja postupno — aplikacija radi savršeno prvih 10 minuta, a onda počne štucati i na kraju se sruši. Korisnici tada obično ne znaju zašto, samo znaju da im aplikacija "ne valja".

Najčešći uzroci curenja memorije u .NET MAUI

Evo najčešćih krivaca:

  • Pretplate na događaje — ako se pretplatite na događaj dugoživućeg objekta (npr. servisa koji je singleton), a ne otkažete pretplatu, pretplatnik nikada neće biti oslobođen iz memorije
  • Ciklične reference na Apple platformama — garbage collector na iOS-u ne rješava ciklične reference između upravljanog (managed) i nativnog koda
  • Nepozvani DisconnectHandler() — mnoge kontrole zahtijevaju eksplicitno čišćenje kroz ovu metodu
  • BindingContext koji drži referencu — ako ViewModel preživljava View, a View drži referencu na ViewModel kroz BindingContext, nastaje curenje

Korištenje WeakEventManager za sigurne pretplate

Kada implementirate vlastite događaje, koristite WeakEventManager umjesto standardnog C# eventa. WeakEventManager drži pretplatnike kao WeakReference, što znači da ih garbage collector može osloboditi čak i ako se pretplata ne otkaže:

public class NotificationService
{
    private readonly WeakEventManager _weakEventManager = new();

    public event EventHandler<NotificationEventArgs> NotificationReceived
    {
        add => _weakEventManager.AddEventHandler(value);
        remove => _weakEventManager.RemoveEventHandler(value);
    }

    public void OnNotificationReceived(NotificationEventArgs args)
    {
        _weakEventManager.HandleEvent(this, args,
            nameof(NotificationReceived));
    }
}

// Korištenje u ViewModelu — čak i ako zaboravite
// otkazati pretplatu, neće doći do curenja memorije
public class DashboardViewModel : ObservableObject
{
    public DashboardViewModel(NotificationService notificationService)
    {
        notificationService.NotificationReceived += OnNotification;
    }

    private void OnNotification(object? sender, NotificationEventArgs e)
    {
        // Obrada obavijesti
    }
}

MemoryToolkit.Maui — detekcija curenja u razvoju

MemoryToolkit.Maui je alat otvorenog koda koji pomaže u otkrivanju i sprečavanju curenja memorije. Instalira se kao NuGet paket i nudi tri ključne funkcionalnosti: detekciju curenja u stvarnom vremenu, automatsko čišćenje referenci i kompartmentalizaciju curenja.

// Instalacija:
// dotnet add package AdamE.MemoryToolkit.Maui

// Konfiguracija u MauiProgram.cs:
public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder.UseMauiApp<App>();

#if DEBUG
    builder.Logging.AddDebug();
    builder.UseLeakDetection(collectionTarget: CollectionTarget.Page);
#endif

    return builder.Build();
}

Za korištenje TearDownBehavior koji automatski čisti reference kada se stranica ukloni s navigacijskog stoga:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:mtk="clr-namespace:AdamE.MemoryToolkit.Maui;assembly=AdamE.MemoryToolkit.Maui"
             mtk:TearDownBehavior.Cascade="True"
             x:Class="MojaAplikacija.Views.DetailPage">

    <!-- Sadržaj stranice -->

</ContentPage>

Kada je TearDownBehavior.Cascade aktivan, toolkit automatski postavlja ItemsSource, Content i Parent na null, poziva ClearLogicalChildren(), a zatim Dispose() i DisconnectHandler(). Oprez: ovo je destruktivan proces — ne aktivirajte ga na stranicama koje su još vidljive korisniku.

Pravilno upravljanje resursima

Osim korištenja alata, postoje obrasci koje trebate slijediti pri svakom razvoju:

public class MapViewModel : ObservableObject, IDisposable
{
    private readonly ILocationService _locationService;
    private CancellationTokenSource? _cts;

    public MapViewModel(ILocationService locationService)
    {
        _locationService = locationService;
        _locationService.LocationChanged += OnLocationChanged;
        _cts = new CancellationTokenSource();
    }

    private void OnLocationChanged(object? sender, LocationEventArgs e)
    {
        // Ažuriranje mape
    }

    public void Dispose()
    {
        // Uvijek otkažite pretplate na događaje
        _locationService.LocationChanged -= OnLocationChanged;

        // Otkažite asinkrone operacije
        _cts?.Cancel();
        _cts?.Dispose();
        _cts = null;
    }
}

Implementacija IDisposable sučelja i eksplicitno čišćenje u Dispose() metodi ključni su za sprečavanje curenja. Ovo je posebno važno za ViewModele koji se pretplaćuju na događaje singleton servisa ili koriste nativne resurse — a takvih slučajeva u stvarnim projektima ima više nego što se čini na prvu.

NativeAOT: Manje aplikacije i brže pokretanje

NativeAOT (Native Ahead-of-Time) kompilacija jedno je od najznačajnijih poboljšanja u .NET MAUI 9. Za razliku od tradicionalne Mono implementacije gdje se IL kod kompilira u nativni kod tijekom izvođenja (JIT), NativeAOT kompilira sve unaprijed u nativni strojni kod specifičan za ciljnu platformu.

Rezultati s NativeAOT

Prema službenim Microsoftovim mjerilima, NativeAOT donosi impresivne rezultate:

  • Vrijeme pokretanja na iOS-u — do 2 puta brže u usporedbi s Mono implementacijom
  • Vrijeme pokretanja na Mac Catalyst — 1,2 puta brže
  • Veličina aplikacije — do 2,5 puta manja na iOS-u i Mac Catalystu

Važno je napomenuti da NativeAOT trenutno nije podržan na Androidu — dostupan je samo za iOS, Mac Catalyst i Windows.

Omogućavanje NativeAOT

Za uključivanje NativeAOT-a, dodajte sljedeća svojstva u vašu .csproj datoteku:

<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0-ios' OR
                          '$(TargetFramework)' == 'net9.0-maccatalyst'">
    <PublishAot>true</PublishAot>
    <MtouchInterpreter>none</MtouchInterpreter>
</PropertyGroup>

Zahtjevi i ograničenja NativeAOT

NativeAOT uvodi određena ograničenja koja morate razumjeti prije nego ga uključite:

  • Kompilirani vezovi su obavezni — stringovni vezovi ne rade s NativeAOT
  • Nema učitavanja XAML-a u runtime-u — sav XAML mora biti kompiliran
  • Potpuno skraćivanje (full trimming) — kod koji se koristi putem refleksije mora biti eksplicitno označen
  • Izgradnja nije moguća s Windowsa za iOS ciljeve — potreban je macOS
  • Biblioteke trećih strana moraju biti kompatibilne s trimming-om i AOT-om

Ako vaša aplikacija intenzivno koristi refleksiju ili dinamičko učitavanje XAML-a, NativeAOT možda nije pravi izbor. Ali za nove projekte koji koriste kompilirane vezove od početka, prednosti su značajne — i teško ih je ignorirati.

Smanjenje veličine aplikacije pomoću trimminga

Trimming je proces uklanjanja nekorištenog koda iz vaše aplikacije tijekom kompilacije. .NET IL Linker analizira vaš kod, identificira koji tipovi, metode i svojstva se stvarno koriste, i uklanja sve ostalo.

Konfiguracija trimminga

<PropertyGroup>
    <!-- Osnovni trimming (zadano za Release) -->
    <PublishTrimmed>true</PublishTrimmed>

    <!-- Agresivniji trimming za manju veličinu -->
    <TrimMode>full</TrimMode>

    <!-- Uklanjanje globalizacijskih podataka
         ako aplikacija ne treba lokalizaciju -->
    <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

TrimMode=full je agresivniji od zadanog partial režima i može značajno smanjiti veličinu aplikacije, ali zahtijeva da vaš kod i sve biblioteke budu kompatibilne s trimmingom. Koristite [DynamicallyAccessedMembers] i [RequiresUnreferencedCode] atribute za označavanje koda koji koristi refleksiju.

Optimizacija resursa

Osim trimminga koda, veličinu aplikacije možete smanjiti i optimizacijom resursa:

  • Slike — koristite SVG umjesto PNG/JPG kada je moguće, jer se skaliraju bez gubitka kvalitete i obično su manje. Za rasterske slike koristite WebP format koji nudi 25–35% manju veličinu od PNG-a uz istu kvalitetu
  • Fontovi — uključite samo one font datoteke koje stvarno koristite. Jedan nepotrebni font može dodati 500 KB do 2 MB
  • Resursi specifični za gustoću ekrana — .NET MAUI automatski generira verzije za različite gustoće. Provjerite da izvornici nisu nepotrebno veliki

Optimizacije specifične za Android

Podrška za 16 KB veličinu stranice

Od 1. studenog 2025., Google zahtijeva da sve nove i ažurirane aplikacije na Google Playu koje ciljaju Android 15+ podržavaju 16 KB veličinu memorijske stranice (umjesto dosadašnjih 4 KB). Ova promjena donosi prosječno poboljšanje vremena pokretanja od 3,16% (do 30% za neke aplikacije) i smanjenje potrošnje energije od 4,56%.

.NET MAUI 9 s .NET za Android automatski podržava ovu promjenu — nije potrebna nikakva dodatna konfiguracija s vaše strane. Međutim, ako koristite nativne biblioteke treće strane, provjerite da su i one kompilirane s podrškom za 16 KB stranice.

Release konfiguracija i ProGuard

Prebacivanje s Debug na Release konfiguraciju donosi enormnu razliku na Androidu. Debug konfiguracija uključuje dodatne provjere, simbole za debugiranje i onemogućava optimizacije. Za objektivno mjerenje performansi uvijek testirajte u Release modu — ovo je jedna od onih stvari gdje razlika zna biti zapanjujuća:

dotnet publish -f net9.0-android -c Release \
    /p:AndroidPackageFormats=aab \
    /p:AndroidLinkMode=Full

Svojstvo AndroidLinkMode=Full omogućuje potpuno skraćivanje za Android, uklanjajući nekorišteni kod iz aplikacije i svih referenciranjih biblioteka.

Asinkrono programiranje i upravljanje nitima

Asinkrono programiranje nije samo best practice — u kontekstu mobilnih aplikacija to je nužnost. Blokirajuća operacija na glavnoj niti znači zamrznut UI, što korisnik doživljava kao "aplikacija se srušila".

Pravila za async/await u .NET MAUI

public class OrdersViewModel : ObservableObject
{
    [ObservableProperty]
    private bool _isLoading;

    [ObservableProperty]
    private ObservableCollection<Order> _orders = new();

    [RelayCommand]
    private async Task RefreshOrdersAsync()
    {
        if (IsLoading) return;

        IsLoading = true;
        try
        {
            // Dohvat podataka s API-ja — nikada ne koristite .Result ili .Wait()!
            var freshOrders = await _apiService.GetOrdersAsync();

            // Ažuriranje UI-ja na glavnoj niti
            MainThread.BeginInvokeOnMainThread(() =>
            {
                Orders.Clear();
                foreach (var order in freshOrders)
                {
                    Orders.Add(order);
                }
            });
        }
        catch (HttpRequestException ex)
        {
            await Shell.Current.DisplayAlert("Greška",
                "Nije moguće dohvatiti narudžbe.", "U redu");
        }
        finally
        {
            IsLoading = false;
        }
    }

    [RelayCommand]
    private async Task ProcessHeavyCalculationAsync()
    {
        // Za CPU-intenzivne operacije koristite Task.Run
        var result = await Task.Run(() =>
        {
            // Složena kalkulacija na pozadinskoj niti
            return ComputeExpensiveResult();
        });

        // Ažuriranje UI-ja s rezultatom
        Summary = result;
    }
}

Ključna pravila:

  • Nikada ne koristite .Result, .Wait() ili .GetAwaiter().GetResult() na glavnoj niti — to je recept za deadlock
  • Koristite Task.Run() za CPU-intenzivne operacije koje bi inače blokirale nit
  • Ažuriranje UI-ja iz pozadinske niti zahtijeva MainThread.BeginInvokeOnMainThread()
  • Uvijek implementirajte otkazivanje (cancellation) za dugotrajne operacije koristeći CancellationToken

Profiliranje i alati za dijagnostiku

Optimizacija bez mjerenja je nagađanje. Ozbiljno. .NET MAUI ekosustav nudi nekoliko alata za precizno mjerenje performansi i identificiranje uskih grla — i stvarno ih vrijedi naučiti koristiti.

dotnet-trace za praćenje izvođenja

dotnet-trace je alat za prikupljanje ETW (Event Tracing for Windows) podataka iz vaše .NET MAUI aplikacije. Radi na Androidu, iOS-u, macOS-u i Windowsu:

# Instalacija alata
dotnet tool install --global dotnet-trace

# Pokretanje praćenja za Android aplikaciju
dotnet-trace collect --process-id <PID> \
    --providers Microsoft-Maui-Events

# Pokretanje praćenja s profilom za analizu pokretanja
dotnet-trace collect --process-id <PID> \
    --profile gc-verbose

Generirane .nettrace datoteke možete analizirati pomoću PerfView na Windowsu ili speedscope alata (dostupan putem preglednika).

dotnet-gcdump za analizu memorije

Kada sumnjate na curenje memorije, dotnet-gcdump je vaš prijatelj. Stvara snimku svih upravljanih C# objekata u memoriji:

# Instalacija alata
dotnet tool install --global dotnet-gcdump

# Kreiranje snimke memorije
dotnet-gcdump collect --process-id <PID>

# Analiza snimke
dotnet-gcdump report <datoteka.gcdump>

Usporedite dvije snimke — jednu prije korištenja problematične značajke i jednu nakon. Objekti koji postoje u drugoj snimci, a ne bi trebali, ukazuju na curenje memorije. Ova metoda "prije i poslije" iznimno je praktična i u praksi daje brze rezultate.

Visual Studio dijagnostički alati

Visual Studio 2022 integrira dijagnostičke alate izravno u IDE:

  • CPU Usage — identificira metode koje troše najviše CPU vremena
  • Memory Usage — prati alokacije i curenje memorije u stvarnom vremenu
  • .NET Counters — prikazuje GC statistike, broj niti, i druge metrike u stvarnom vremenu
  • Hot Reload performanse — pomaže identificirati probleme s Hot Reload tijekom razvoja

Za objektivnije rezultate, uvijek profilirajte na stvarnim uređajima — pogotovo na uređajima srednje i niže klase. Emulatori ne odražavaju stvarne performanse koje će vaši korisnici doživjeti, i to je greška koju mnogi naprave tek jednom.

Kontrolna lista za optimizaciju .NET MAUI aplikacija

Na kraju ovog vodiča, evo sažete kontrolne liste koju možete koristiti pri svakom projektu:

  1. Pokretanje — koristite Shell navigaciju, optimizirajte DI registracije, premjestite inicijalizaciju u pozadinu
  2. UI renderiranje — izravnajte layout hijerarhiju, koristite kompilirane vezove, implementirajte paginaciju u CollectionView
  3. Memorija — koristite WeakEventManager za vlastite događaje, implementirajte IDisposable, integrirajte MemoryToolkit.Maui u debug konfiguraciju
  4. Veličina aplikacije — uključite trimming, razmotrite NativeAOT za iOS, optimizirajte slike i fontove
  5. Android specifično — testirajte u Release konfiguraciji, osigurajte podršku za 16 KB stranice (automatski s .NET MAUI 9)
  6. Async — nikada ne blokirajte glavnu nit, koristite Task.Run za CPU-intenzivne operacije, uvijek implementirajte otkazivanje
  7. Profiliranje — mjerite prije i poslije optimizacija, testirajte na stvarnim uređajima srednje klase, koristite dotnet-trace i dotnet-gcdump

Zaključak

Optimizacija performansi .NET MAUI aplikacija nije jednokratni zadatak — to je kontinuirani proces koji počinje od prvog dana razvoja. Svaka odluka, od izbora navigacijskog obrasca do načina na koji vezujete podatke za UI, ima kumulativan utjecaj na krajnje korisničko iskustvo.

Najvažniji savjet koji mogu dati: uvijek mjerite. Nemojte nagađati gdje su uska grla — koristite alate poput dotnet-trace, dotnet-gcdump i MemoryToolkit.Maui da identificirate stvarne probleme. Optimizirajte ono što mjerenja pokažu kao problematično, a ne ono za što mislite da bi moglo biti sporo.

.NET MAUI 9 donio je značajne alate i poboljšanja — NativeAOT, kompilirane vezove, nove CollectionView handlere — koji vam omogućuju da izgradite aplikacije koje se natječu po performansama s nativnim rješenjima. Iskoristite ih.

O Autoru Editorial Team

Our team of expert writers and editors.