Introduction
.NET MAUI applications on Android have traditionally used the Mono runtime. With the move toward CoreCLR, .NET MAUI is getting the same modern .NET runtime foundation used by other .NET application models.
That change is interesting for more than just runtime architecture. For mobile applications, startup time and memory usage directly affect how an application feels to users. A runtime migration can therefore raise practical questions:
Does the application start faster?
Does it use more or less memory?
Does the application package change?
Are existing libraries still compatible?
Does the behavior differ between Debug and Release builds?
Microsoft has been working on bringing CoreCLR to .NET MAUI across Android, iOS, and Mac Catalyst. The important point for developers is that runtime changes should be evaluated with measurements rather than assumptions.
This article focuses on Android and shows how to build a repeatable benchmark for comparing startup and memory behavior.
What Is CoreCLR?
CoreCLR is the .NET runtime used by modern .NET applications. It provides services such as garbage collection, JIT compilation, type loading, exception handling, and other runtime functionality.
.NET MAUI historically used different runtime technology for mobile targets because mobile platforms have restrictions and deployment requirements that differ from desktop and server applications.
Moving toward CoreCLR creates a more consistent runtime foundation across .NET application types.
For developers, this can eventually mean fewer runtime-specific differences to consider when building applications that target multiple platforms.
However, a runtime migration should not be evaluated only from the perspective of API compatibility. Mobile applications have very different performance characteristics from server applications, so startup, memory, application size, and device behavior are worth measuring independently.
Why Startup Time Matters on Android
When a user taps an application icon, several things can happen before the first usable screen appears.
A simplified startup sequence looks like this:
User launches app
|
v
Android creates process
|
v
.NET runtime initialization
|
v
.NET MAUI initialization
|
v
Application startup code
|
v
Dependency injection / services
|
v
First page creation
|
v
First usable UI
The runtime is only one part of this process.
For example, an application with a large dependency graph may spend significant time creating services before the first page is displayed. Similarly, database initialization, configuration loading, network requests, or excessive work in MauiProgram can dominate startup.
This is why a meaningful comparison should keep application code identical between runtime configurations.
Creating a Simple .NET MAUI Test Application
For a controlled experiment, start with a small application.
A basic MauiProgram might look like this:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont(
"OpenSans-Regular.ttf",
"OpenSansRegular");
});
return builder.Build();
}
}
The main page can remain intentionally simple:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
The purpose is not to create a production application. The goal is to establish a baseline where runtime startup overhead can be observed without introducing unnecessary application work.
Preparing the Android Build
Performance testing should be performed with a Release build.
A Debug build includes development-oriented behavior that can significantly affect startup and memory measurements.
For example:
dotnet build -c Release -f net11.0-android
The exact target framework and SDK configuration should match the .NET MAUI version being evaluated.
Before collecting results, make sure the application is deployed consistently to the same Android device or emulator.
Do not compare:
CoreCLR on a physical device
Mono on an emulator
and then attribute every difference to the runtime.
Hardware, Android version, emulator configuration, background processes, and build configuration can all influence the results.
Designing a Startup Benchmark
A simple startup benchmark should measure the time between launching the application and reaching a clearly defined readiness point.
For example, the application can record a timestamp when the first page is created:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
var startupTime = DateTimeOffset.UtcNow;
System.Diagnostics.Debug.WriteLine(
$"MainPage created: {startupTime:O}");
}
}
For a more useful benchmark, define a consistent application milestone.
For example:
T0 = Android process launch
T1 = .NET runtime available
T2 = MauiProgram completed
T3 = App created
T4 = MainPage loaded
T5 = First usable UI
The benchmark should use the same milestone for every runtime configuration.
If the measurement point changes between tests, the results cannot be compared reliably.
Measuring Memory Usage
Startup time tells only part of the story.
A mobile application can start quickly and still consume more memory after initialization.
Android provides tools for observing application memory, including Android Studio's profiling tools and Android debugging commands.
For example, ADB can be used to inspect process information:
adb shell dumpsys meminfo com.example.mauibenchmark
Replace the package name with the actual application package.
The output includes memory-related information about the process.
For repeatable testing, record memory after the same application state is reached.
For example:
Launch application
|
v
Wait for first usable screen
|
v
Wait for initialization to finish
|
v
Collect memory information
Do not take a memory reading immediately after process creation and compare it with a reading taken several seconds later in another test.
Comparing Mono and CoreCLR
The most useful experiment is an A/B comparison.
Join the conversation! Your thoughts help the community grow.