Uh oh!
There was an error while loading. Please reload this page.
[TrimmableTypeMap] Replace startup hook with direct TypeMapLoader.Initialize() call - #11197
Conversation
Replace the DOTNET_STARTUP_HOOKS mechanism for trimmable typemap initialization with a direct call from JNIEnvInit.Initialize() to TypeMapLoader.Initialize(). This fixes NativeAOT compatibility (startup hooks don't work with NativeAOT) and removes the reflection-based startup hook discovery overhead. Changes: - Add ref assembly project (src/Microsoft.Android.TypeMaps.Ref/) that Mono.Android compiles against. At app build time, the generated impl assembly replaces it. - Call TypeMapLoader.Initialize() directly from JNIEnvInit when TrimmableTypeMap feature is enabled - Make TrimmableTypeMap.Initialize() public (both overloads) - Update generator to emit public TypeMapLoader class (in Microsoft.Android.Runtime namespace) instead of internal StartupHook - Remove _ConfigureTrimmableTypeMapStartupHook target and StartupHookSupport=true from trimmable typemap targets (startup hooks remain for HotReload) Fixes: #11196 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6130f19 to
bb9ac8bCompareThere was a problem hiding this comment.
Pull request overview
This PR removes the trimmable typemap initialization dependency on DOTNET_STARTUP_HOOKS (reflection-based and incompatible with NativeAOT) and replaces it with a direct JNIEnvInit.Initialize() → TypeMapLoader.Initialize() call, with the typemap generator emitting the real TypeMapLoader implementation at app build time.
Changes:
- Add a new reference-assembly project (
_Microsoft.Android.TypeMaps) that provides a compile-timeMicrosoft.Android.Runtime.TypeMapLoader.Initialize()stub. - Update
Mono.Androidruntime initialization to callTypeMapLoader.Initialize()whenRuntimeFeature.TrimmableTypeMapis enabled, and remove MSBuild startup-hook wiring for trimmable typemaps. - Update typemap generator + tests to emit/expect
TypeMapLoaderrather thanStartupHook.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs | Updates generator test expectation from StartupHook to TypeMapLoader. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets | Removes StartupHookSupport and the target that composed DOTNET_STARTUP_HOOKS for typemap initialization. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets | Updates comments to no longer mention trimmable typemap as a startup-hook contributor. |
| src/Mono.Android/Mono.Android.csproj | Adds compile-time project reference to the new _Microsoft.Android.TypeMaps ref assembly. |
| src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs | Makes TrimmableTypeMap.Initialize overloads public and updates XML docs to reference TypeMapLoader. |
| src/Mono.Android/Android.Runtime/JNIEnvInit.cs | Calls TypeMapLoader.Initialize() when trimmable typemap feature is enabled. |
| src/Microsoft.Android.TypeMaps.Ref/TypeMapLoader.cs | New TypeMapLoader stub type for compile-time reference. |
| src/Microsoft.Android.TypeMaps.Ref/Microsoft.Android.TypeMaps.Ref.csproj | New project producing the _Microsoft.Android.TypeMaps reference assembly. |
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs | Generator now emits public Microsoft.Android.Runtime.TypeMapLoader.Initialize() entrypoint. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
jonathanpeppers
commented
Apr 23, 2026
/review |
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — Clean architectural improvement
Summary: Replaces the reflection-based DOTNET_STARTUP_HOOKS mechanism with a direct TypeMapLoader.Initialize() call for trimmable typemap initialization. This eliminates reflection, removes startup hook latency, and — most importantly — enables NativeAOT support where Assembly.Load and reflection aren't available.
What works well
- Ref assembly pattern is correctly implemented —
ProduceOnlyReferenceAssembly=true, unsigned to match the generated impl,Private=false+PrivateAssets=allto prevent shipping - Feature switch isolation — the
TypeMapLoader.Initialize()call is in its ownif (RuntimeFeature.TrimmableTypeMap)block, consistent with the trimmer pattern used elsewhere in this file - Startup hooks remain available for HotReload —
StartupHookSupport=falseonly applies inOptimize=true(Release) builds; Debug builds retain the default, so HotReload is unaffected - Generated IL changes are minimal and correct —
TypeAttributes.Public, proper namespace, matching method signature - Target cleanup is thorough —
_ConfigureTrimmableTypeMapStartupHooktarget andStartupHookSupport=trueare cleanly removed with no orphaned references
Suggestions (2 × 💡)
| # | Severity | File | Issue |
|---|---|---|---|
| 1 | 💡 | RootTypeMapAssemblyGeneratorTests.cs | Verify TypeMapLoader's namespace and public visibility in the assertion |
| 2 | 💡 | RootTypeMapAssemblyGenerator.cs | Comment drops TrimmableTypeMap from IgnoresAccessChecksTo list, but the class is still internal |
Both are minor and non-blocking.
Generated by Android PR Reviewer for issue #11197 · ● 4.1M
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Add namespace and visibility assertions to TypeMapLoader test - Fix IgnoresAccessChecksTo comment to include TrimmableTypeMap (class is still internal) - Clarify ProduceOnlyReferenceAssembly intent in ref assembly csproj Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents the JIT from trying to resolve TypeMapLoader (from _Microsoft.Android.TypeMaps.dll) when compiling JNIEnvInit.Initialize() in non-trimmable builds where that assembly isn't present. This is the standard .NET pattern for conditional dependencies on assemblies that may not be available at runtime. Per docs: "runtimes will refuse to load reference assemblies for execution" so shipping the ref assembly as a fallback wouldn't work either. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The generated TypeMapLoader.Initialize() calls TrimmableTypeMap.Initialize() which is now public. Making the class public means IgnoresAccessChecksTo is no longer needed for this specific type (still needed for SingleUniverseTypeMap, AggregateTypeMap, and __TypeMapAnchor). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Summary
Fixes#11196
Replace the
DOTNET_STARTUP_HOOKSmechanism for trimmable typemap initialization with a direct call fromJNIEnvInit.Initialize()toTypeMapLoader.Initialize().Problem
Startup hooks don't work with NativeAOT (
Assembly.Loadand reflection are not supported). The current approach also adds unnecessary latency via reflection-based discovery ofSystem.StartupHookProvider.Approach
Introduce a reference assembly (
_Microsoft.Android.TypeMaps.dll) with a publicTypeMapLoader.Initialize()stub that Mono.Android compiles against. At app build time, the trimmable typemap generator produces the real implementation assembly with the same identity, which replaces the ref assembly before trimming/linking.No reflection, no startup hook discovery, works with NativeAOT.
Changes
src/Microsoft.Android.TypeMaps.Ref/) — compile-only stub withpublic static class TypeMapLoader, usingProduceOnlyReferenceAssembly=true(unsigned, to match the generated assembly)ProjectReferenceto ref assembly (Private=false,PrivateAssets=all); directTypeMapLoader.Initialize()call fromJNIEnvInit.Initialize()behind theRuntimeFeature.TrimmableTypeMapfeature switchTrimmableTypeMap.Initialize()— made public (both overloads); the class itself remainsinternal(accessed via[IgnoresAccessChecksTo])TypeMapLoaderinMicrosoft.Android.Runtimenamespace instead of internalStartupHook_ConfigureTrimmableTypeMapStartupHooktarget andStartupHookSupport=trueoverride (startup hooks remain available for HotReload)TypeMapLoadertype name, namespace (Microsoft.Android.Runtime), and public visibility (414/414 pass)