XDev.Android.ProcessIsolation 1.0.0.1

dotnet add package XDev.Android.ProcessIsolation --version 1.0.0.1
                    
NuGet\Install-Package XDev.Android.ProcessIsolation -Version 1.0.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="XDev.Android.ProcessIsolation" Version="1.0.0.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="XDev.Android.ProcessIsolation" Version="1.0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="XDev.Android.ProcessIsolation">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add XDev.Android.ProcessIsolation --version 1.0.0.1
                    
#r "nuget: XDev.Android.ProcessIsolation, 1.0.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package XDev.Android.ProcessIsolation@1.0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=XDev.Android.ProcessIsolation&version=1.0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=XDev.Android.ProcessIsolation&version=1.0.0.1
                    
Install as a Cake Tool

XDev.Android.ProcessIsolation

NuGet

Build-time tooling that keeps a .NET Android / MAUI app's isolated child processes (e.g. Stripe Tap to Pay :stripetaptopay) free of the Mono/.NET runtime.

Why

Android SDKs that require a dedicated secure process — Stripe Terminal Tap to Pay, or anything else built on MPoC-style attestation — fail on a stock .NET MAUI app because:

  1. .NET Android auto-generates a MonoRuntimeProvider_N ContentProvider for every unique android:process declared in the merged manifest.
  2. ContentProviders load before Application.onCreate(), so the full .NET runtime ends up in the isolated process.
  3. The SDK's SecureProcessChecker sees libmonosgen-2.0.so, libmonodroid.so, etc., and rejects the environment.

This package bundles the three pieces that together solve that problem:

  • StripMonoRuntimeProviderTask — an MSBuild task that removes the MonoRuntimeProvider_N entries targeted at configured isolated processes from the merged AndroidManifest.xml.
  • NativeAppWrapper.java — a pure-Java Application class generated at build time so the isolated process can instantiate an Application without triggering .NET class loading.
  • MauiBootstrapper — a C# source file shipped as a contentFile so the main-process MAUI app can still initialize even though MauiApplication has been replaced with the pure-Java wrapper.

Install

This is a build-time-only tooling package. It MUST be consumed with PrivateAssets="all" so it does not leak into your NuGet dependency graph. A complete integration looks like:

<ItemGroup>
  <PackageReference Include="XDev.Android.ProcessIsolation" Version="1.0.0" PrivateAssets="all">
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>

  
  <IsolatedAndroidProcess Include=":stripetaptopay" />
</ItemGroup>

Without PrivateAssets="all", any NuGet that references a library using this package will transitively pull the tooling in.

Optional properties

Property Default Purpose
IncludeMauiBootstrapper true Include the shipped MauiBootstrapper.cs as a Compile source. Set false for non-MAUI apps or if you want to provide your own initialization.

Wire up - Consumer integration: process isolation

For any child process subject to MPoC attestation: the attestation rejects any process where the .NET (Mono) runtime is loaded. .NET Android auto-generates a MonoRuntimeProvider_N ContentProvider for every unique android:process in the merged manifest, which would otherwise pull Mono into the isolated process and fail the check.

The wrapper itself contains no integration glue — the host app must wire up the XDev.Android.ProcessIsolation build-time package, which strips the auto-injected MonoRuntimeProvider_N entries, supplies a pure-Java NativeAppWrapper Application class, and ships a MauiBootstrapper that re-runs MAUI's init chain from the main Activity. See the package README for the full background.

Consumers need the same four pieces:

  1. csproj package reference + isolated-process declarations (note PrivateAssets="all" — this is build-time tooling):

    <PackageReference Include="XDev.Android.ProcessIsolation" Version="0.0.1-alpha008">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <IsolatedAndroidProcess Include=":[your-isolated-process-name]" />
    
  2. AndroidManifest — swap the auto-injected C# MauiApplication ACW name for the pure-Java wrapper:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools" ...>
      <application android:name="xdev.android.processisolation.NativeAppWrapper"
                   tools:replace="android:name"
                   ...>
    
  3. MainActivity.OnCreate — initialize MAUI from the Activity before base.OnCreate(), because the Application class is no longer a MauiApplication:

    protected override void OnCreate(Bundle savedInstanceState)
    {
        XDev.Android.ProcessIsolation.MauiBootstrapper.BuildMauiApp = builder =>
        {
            // Any service registrations that used to live in MauiApplication.CreateMauiApp()
            // belong here — builder.Services is fully available.
            return MauiProgram.CreateMauiApp(builder);
        };
    
        XDev.Android.ProcessIsolation.MauiBootstrapper.Initialize(this.Application);
    
        base.OnCreate(savedInstanceState);
        // ... rest of OnCreate ...
    }
    
  4. Retire any C# [Application] class that previously extended MauiApplication. The pure-Java NativeAppWrapper is now the Application class; a competing C# [Application] will be auto-registered by .NET Android and (without tools:replace) win the merge.

Linker note (Release builds): NativeAppWrapper is kept by D8/R8 because it's referenced from the manifest, and MauiBootstrapper is kept by the managed linker because the host app calls it directly from MainActivity.OnCreate. This is safe at the default AndroidLinkMode=SdkOnly. If the consumer app sets AndroidLinkMode=Full, the linker may also trim internal MAUI types used by MauiBootstrapper.Initialize (e.g. MauiApplication.ActivityLifecycleCallbacks); a [DynamicDependency] or linker descriptor will be required in that scenario.

Verifying the fix

After a build, inspect the merged manifest to confirm the strip worked:

$ grep -A1 'MonoRuntimeProvider' obj/Debug/net8.0-android/android/AndroidManifest.xml

Only the non-numbered MonoRuntimeProvider (main process) should remain. Any MonoRuntimeProvider_N targeting an @(IsolatedAndroidProcess) process is a bug.

Troubleshooting

Build warning "No components found for isolated process '...'"

The task verifies that the merged manifest has at least one <service>, <activity>, <provider> (non-Mono), or <receiver> declaring android:process=":..." for each @(IsolatedAndroidProcess) entry. If none are found, the AAR that should contribute those components is probably missing from the build, or the manifest merger did not run. Check your @(AndroidLibrary) / @(AndroidMavenLibrary) items and rebuild.

Black screen / crash on app launch in the main process

Make sure MauiBootstrapper.Initialize(Application) is called before base.OnCreate(savedInstanceState) in your main Activity, and that MauiBootstrapper.BuildMauiApp is set before Initialize.

Background

Why all three pieces (StripMonoRuntimeProviderTask, NativeAppWrapper, and MauiBootstrapper) are required for isolating child processes from the .NET runtime.

This applies to any Android SDK that runs components in a dedicated android:process that must remain free of non-native libraries (e.g. MPoC secure processes, isolated services, etc.).

Xamarin.Forms vs MAUI boot architecture

MAUI changed where framework initialization happens compared to Xamarin.Forms:

Application class Framework init location
Xamarin.Forms Plain Java / stock Application Forms.Init() + LoadApplication() in Activity
MAUI (normal) C# MauiApplication MauiApplication.OnCreate()
Process Isolation fix Plain Java NativeAppWrapper MauiBootstrapper.Initialize() in Activity

In Xamarin.Forms, the Application class was typically a plain Java class (or not specified at all), and all framework initialization happened at the Activity level via Forms.Init(this, bundle) + LoadApplication(new App()). The Application class had no .NET dependencies.

MAUI moved that initialization up to the Application class via MauiApplication.OnCreate() — building the DI container, creating MauiContext, wiring up handlers — all before any Activity starts. This is architecturally cleaner, but it means the Application class is now a C# class with native JNI methods, which creates a conflict with any child process that needs to stay .NET-free.

The process isolation fix is essentially reverting to the Xamarin.Forms boot pattern — a plain Java Application class with MAUI initialization deferred to the Activity. MauiBootstrapper has more steps than Forms.Init() because MAUI's initialization chain is heavier (MauiContext, SetApplicationHandler, IPlatformApplication.Current, etc.), but the architectural pattern is the same.

Scenario 1: Normal MAUI app boot (no fixes, main process)

When Android launches your app's main process:

Step 1 — ContentProviders load first (before any user code). Android reads the manifest, finds all <provider> entries for this process, and calls attachInfo() on each. The generated MonoRuntimeProvider.java calls mono.MonoPackageManager.LoadApplication(...), which calls System.loadLibrary("monosgen-2.0"), System.loadLibrary("monodroid"), etc. The full Mono/.NET runtime is now loaded in the process.

Step 2 — Android instantiates the Application class. The manifest points at the C# ACW (MainApplication extends MauiApplication). Constructor runs mono.MonoPackageManager.setContext(this) (works because Mono is loaded), then Android calls onCreate() which invokes n_onCreate() — a native JNI method implemented in libmonodroid.so that calls into C# MauiApplication.OnCreate().

Step 3 — C# MauiApplication.OnCreate() runs. Builds MauiApp, creates MauiContext, calls SetApplicationHandler(), sets IPlatformApplication.Current. MAUI is now initialized.

Step 4 — MainActivity launches, UI renders.

Scenario 2: Isolated child process — no fixes

When an SDK starts a child process (e.g. :secureprocess):

Step 1 — MonoRuntimeProvider_N loads. The .NET Android build system auto-generates a numbered ContentProvider for each unique android:process in the manifest:

<provider android:name="mono.MonoRuntimeProvider_1"
          android:process=":secureprocess" />

Its attachInfo() runs → loads libmonosgen, libmonodroid, etc. Mono is now loaded in the child process.

Step 2 — Application class instantiates (same class, new process). Android uses the same android:name Application class for ALL processes. The ACW constructor and n_onCreate() run. They work because Mono was loaded in Step 1.

Step 3 — Security checker rejects. Any security checker that scans loaded native libraries finds libmonosgen-2.0.so, libmonodroid.sorejects as untrusted/non-native software.

Result: child process functionality fails.

Scenario 3: Isolated child process — strip MonoRuntimeProvider only

The MSBuild task removes MonoRuntimeProvider_1 from the manifest. No NativeAppWrapper.

Step 1 — No ContentProviders for this process. No native libraries loaded. libmonodroid.so is NOT in memory.

Step 2 — Android instantiates the Application class. Still the C# ACW. MonoPackageManager.setContext() is a Java method that just stores a reference, so the constructor likely succeeds.

Step 3 — Android calls onCreate() which invokes n_onCreate() — a JNI method implemented in libmonodroid.so. But libmonodroid.so was never loaded (no MonoRuntimeProvider ran).

Result: java.lang.UnsatisfiedLinkError: No implementation found for void ...MauiApplication.n_onCreate(). The child process crashes. The SDK's services and activities in that process can't start. The feature is completely broken, not just rejected.

Scenario 4: Isolated child process — both fixes (correct solution)

Step 1 — No ContentProviders for this process. MonoRuntimeProvider_1 stripped. No native libs loaded.

Step 2 — Android instantiates NativeAppWrapper. Manifest says android:name="...NativeAppWrapper". This is a pure Java class — no ACW, no mono.* imports, no native methods. No JNI calls, no Mono dependencies. Runs cleanly.

Step 3 — Security checker passes. Scans loaded native libraries — finds only standard Android libs. No Mono detected. Passes.

Step 4 — SDK components start normally. Services and activities in the isolated process run in a clean native environment.

But what about the main process?

With NativeAppWrapper as the Application class, the main process boot changes too:

  1. MonoRuntimeProvider (unnumbered) still runs → loads Mono.
  2. NativeAppWrapper.onCreate() runs → just logs "Main process".
  3. But MauiApplication.OnCreate() never runs → no MauiApp, no MauiContext, no SetApplicationHandler().
  4. MainActivity starts → MauiAppCompatActivity.OnCreate()CreatePlatformWindow() → checks application.Handler?.MauiContextnullblack screen.

This is why MauiBootstrapper exists. It's called from MainActivity.OnCreate() before base.OnCreate() to manually run the init chain that MauiApplication.OnCreate() used to handle — the same pattern Xamarin.Forms used (framework init at the Activity level), just with MAUI's heavier initialization steps.

Summary

Fix applied Isolated child process Main process
Neither Mono loads → security checker rejects Works normally
Strip MonoRuntimeProvider only UnsatisfiedLinkErrorprocess crash Works normally
NativeAppWrapper only MonoRuntimeProvider still loads Mono → rejected Black screen (no MAUI init)
Both + MauiBootstrapper No Mono, no crash → clean native process MAUI inits from Activity → works

Two independent vectors

The .NET runtime can enter a child process through two completely independent paths:

Vector Mechanism Fix
ContentProvider Auto-generated MonoRuntimeProvider_N calls MonoPackageManager.LoadApplication() in attachInfo(), before Application.onCreate() StripMonoRuntimeProviderTask removes these entries from the merged manifest
Application class C# ACW's onCreate() calls n_onCreate() (native JNI), which requires libmonodroid.so NativeAppWrapper replaces the C# Application class with pure Java

Fixing only one vector either crashes the process (Vector 1 fixed, Vector 2 not) or still loads Mono (Vector 2 fixed, Vector 1 not). Both must be addressed together, and MauiBootstrapper is the direct consequence of fixing Vector 2.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0.1 98 5/22/2026
0.0.1-alpha009 107 5/19/2026
0.0.1-alpha008 90 5/14/2026