Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4247](xa4247.md): Could not resolve POM file for artifact '{artifact}'.
+ [XA4248](xa4248.md): Could not find NuGet package '{nugetId}' version '{version}' in lock file. Ensure NuGet Restore has run since this `<PackageReference>` was added.
+ [XA4235](xa4249.md): Maven artifact specification '{artifact}' is invalid. The correct format is 'group_id:artifact_id:version'.
+ [XA4250](xa4250.md): Manifest-referenced type '{type}' was not found in any scanned assembly. It may be a framework type.
+ XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI.
+ [XA4301](xa4301.md): Apk already contains the item `xxx`.
+ [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex}
Expand Down
33 changes: 33 additions & 0 deletions Documentation/docs-mobile/messages/xa4250.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: .NET for Android warning XA4250
description: XA4250 warning code
ms.date: 04/07/2026
f1_keywords:
- "XA4250"
---

# .NET for Android warning XA4250

## Example message

Manifest-referenced type '{0}' was not found in any scanned assembly. It may be a framework type.

```text
warning XA4250: Manifest-referenced type 'com.example.MainActivity' was not found in any scanned assembly. It may be a framework type.
```

## Issue

The build found a type name in `AndroidManifest.xml`, but it could not match that name to any Java peer discovered in the app's managed assemblies.

This can be expected for framework-provided types, but it can also indicate that the manifest entry does not match the name generated for a managed Android component.

## Solution

If the manifest entry refers to an Android framework type, this warning can usually be ignored.

Otherwise:

1. Verify the `android:name` value in the manifest.
2. Ensure the managed type is included in the app build.
3. Check for namespace, `[Register]`, or nested-type naming mismatches between the manifest and the managed type.
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ internal static void UpdateApplicationElement (XElement app, JavaPeerInfo peer)
PropertyMapper.ApplyMappings (app, component.Properties, PropertyMapper.ApplicationElementMappings);
}

internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer)
internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer, string packageName)
{
string jniName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
var element = new XElement ("instrumentation",
Expand All @@ -176,6 +176,9 @@ internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer)
return;
}
PropertyMapper.ApplyMappings (element, component.Properties, PropertyMapper.InstrumentationMappings);
if (element.Attribute (AndroidNs + "targetPackage") is null && !string.IsNullOrEmpty (packageName)) {
element.SetAttributeValue (AndroidNs + "targetPackage", packageName);
}

manifest.Add (element);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,28 @@ static void WriteClassDeclaration (JavaPeerInfo type, TextWriter writer)

static void WriteStaticInitializer (JavaPeerInfo type, TextWriter writer)
{
string className = JniSignatureHelper.GetJavaSimpleName (type.JavaName);

// Application and Instrumentation types cannot call registerNatives in their
// static initializer — the native library isn't loaded yet at that point.
// Their registerNatives call is emitted in the generated
// ApplicationRegistration.registerApplications() method instead.
// static initializer — the runtime isn't ready yet at that point. Emit a
// lazy one-time helper instead so the first managed callback can register
// the class just before invoking its native method.
if (type.CannotRegisterInStaticConstructor) {
writer.Write ($$"""
private static boolean __md_natives_registered;
private static synchronized void __md_registerNatives ()
{
if (!__md_natives_registered) {
mono.android.Runtime.registerNatives ({{className}}.class);
__md_natives_registered = true;
}
}


""");
return;
}

string className = JniSignatureHelper.GetJavaSimpleName (type.JavaName);
writer.Write ($$"""
static {
mono.android.Runtime.registerNatives ({{className}}.class);
Expand All @@ -154,7 +167,17 @@ static void WriteConstructors (JavaPeerInfo type, TextWriter writer)
public {{simpleClassName}} ({{parameters}})
{
super ({{superArgs}});

""");

if (!type.CannotRegisterInStaticConstructor) {
writer.Write ($$"""
if (getClass () == {{simpleClassName}}.class) nctor_{{ctor.ConstructorIndex}} ({{args}});

""");
}

writer.Write ($$"""
}


Expand Down Expand Up @@ -197,6 +220,10 @@ static void WriteFields (JavaPeerInfo type, TextWriter writer)

static void WriteMethods (JavaPeerInfo type, TextWriter writer)
{
string registerNativesLine = type.CannotRegisterInStaticConstructor
? "\t\t__md_registerNatives ();\n"
: "";

foreach (var method in type.MarshalMethods) {
if (method.IsConstructor) {
continue;
Expand All @@ -222,7 +249,7 @@ static void WriteMethods (JavaPeerInfo type, TextWriter writer)
@Override
public {{javaReturnType}} {{method.JniName}} ({{parameters}}){{throwsClause}}
{
{{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
{{registerNativesLine}} {{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
}
public native {{javaReturnType}} {{method.NativeCallbackName}} ({{parameters}});

Expand All @@ -233,7 +260,7 @@ static void WriteMethods (JavaPeerInfo type, TextWriter writer)

{{access}} {{javaReturnType}} {{method.JniName}} ({{parameters}}){{throwsClause}}
{
{{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
{{registerNativesLine}} {{returnPrefix}}{{method.NativeCallbackName}} ({{args}});
}
{{access}} native {{javaReturnType}} {{method.NativeCallbackName}} ({{parameters}});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class ManifestGenerator
}

if (peer.ComponentAttribute.Kind == ComponentKind.Instrumentation) {
ComponentElementBuilder.AddInstrumentation (manifest, peer);
ComponentElementBuilder.AddInstrumentation (manifest, peer, PackageName);
continue;
}

Expand Down Expand Up @@ -116,10 +116,7 @@ class ManifestGenerator
}

// Apply manifest placeholders
string? placeholders = ManifestPlaceholders;
if (placeholders is not null && placeholders.Length > 0) {
ApplyPlaceholders (doc, placeholders);
}
ApplyPlaceholders (doc, ManifestPlaceholders);

return (doc, providerNames);
}
Expand Down Expand Up @@ -250,8 +247,12 @@ XElement CreateRuntimeProvider (string name, string? processName, int initOrder)
/// Replaces ${key} placeholders in all attribute values throughout the document.
/// Placeholder format: "key1=value1;key2=value2"
/// </summary>
static void ApplyPlaceholders (XDocument doc, string placeholders)
internal static void ApplyPlaceholders (XDocument doc, string? placeholders)
{
if (placeholders.IsNullOrEmpty ()) {
return;
}

var replacements = new Dictionary<string, string> (StringComparer.Ordinal);
foreach (var entry in placeholders.Split (PlaceholderSeparators, StringSplitOptions.RemoveEmptyEntries)) {
var eqIndex = entry.IndexOf ('=');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ sealed record UcoMethodData
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
/// so the ABI is correct when JNI dispatches the call.
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
/// Body: TrimmableTypeMap.ActivateInstance(self, typeof(TargetType)).
/// </summary>
sealed record UcoConstructorData
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, HashSet<string> used
{
// Use managed type name for proxy naming to guarantee uniqueness across aliases
// (two types with the same JNI name will have different managed names).
var proxyTypeName = peer.ManagedTypeName.Replace ('.', '_').Replace ('+', '_') + "_Proxy";
// Replace generic arity markers too, because backticks would make the emitted
// proxy type itself look generic even though we don't emit generic parameters.
var proxyTypeName = peer.ManagedTypeName.Replace ('.', '_').Replace ('+', '_').Replace ('`', '_') + "_Proxy";

// Guard against name collisions (e.g., "My.Type" and "My_Type" both map to "My_Type_Proxy")
if (!usedProxyNames.Add (proxyTypeName)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Microsoft.Android.Sdk.TrimmableTypeMap;

public interface ITrimmableTypeMapLogger
{
void LogNoJavaPeerTypesFound ();

void LogJavaPeerScanInfo (int assemblyCount, int peerCount);

void LogGeneratingJcwFilesInfo (int jcwPeerCount, int totalPeerCount);

void LogDeferredRegistrationTypesInfo (int typeCount);

void LogGeneratedTypeMapAssemblyInfo (string assemblyName, int typeCount);

void LogGeneratedRootTypeMapInfo (int assemblyReferenceCount);

void LogGeneratedTypeMapAssembliesInfo (int assemblyCount);

void LogGeneratedJcwFilesInfo (int sourceCount);

void LogUnresolvedTypeWarning (string name);

void LogRootingManifestReferencedTypeInfo (string name, string managedTypeName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework>$(TargetFrameworkNETStandard)</TargetFramework>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>Microsoft.Android.Sdk.TrimmableTypeMap</RootNamespace>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
Expand All @@ -18,6 +19,8 @@

<ItemGroup>
<Compile Include="..\..\src-ThirdParty\System.Runtime.CompilerServices\CompilerFeaturePolyfills.cs" Link="CompilerFeaturePolyfills.cs" />
<Compile Include="..\..\external\Java.Interop\src\Java.Interop.Tools.JavaCallableWrappers\Java.Interop.Tools.JavaCallableWrappers\Crc64Helper.cs" Link="Crc64Helper.cs" />
<Compile Include="..\..\external\Java.Interop\src\Java.Interop.Tools.JavaCallableWrappers\Java.Interop.Tools.JavaCallableWrappers\Crc64.Table.cs" Link="Crc64.Table.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ public sealed record JavaPeerInfo
{
/// <summary>
/// JNI type name, e.g., "android/app/Activity".
/// Extracted from the [Register] attribute.
/// Extracted from the [Register] attribute or auto-computed during scanning.
/// Manifest rooting may later promote this to <see cref="CompatJniName"/> when
/// a component is referenced by its managed-namespace form.
/// </summary>
public required string JavaName { get; init; }
public required string JavaName { get; set; }

/// <summary>
/// Compat JNI type name, e.g., "myapp.namespace/MyType" for user types (uses raw namespace, not CRC64).
Expand Down Expand Up @@ -48,7 +50,7 @@ public sealed record JavaPeerInfo
/// that extends Activity. Null for java/lang/Object or types without a Java base.
/// Needed by JCW Java source generation ("extends" clause).
/// </summary>
public string? BaseJavaName { get; init; }
public string? BaseJavaName { get; set; }

/// <summary>
/// JNI names of Java interfaces this type implements, e.g., ["android/view/View$OnClickListener"].
Expand All @@ -69,16 +71,23 @@ public sealed record JavaPeerInfo
/// Types with component attributes ([Activity], [Service], etc.),
/// custom views from layout XML, or manifest-declared components
/// are unconditionally preserved (not trimmable).
/// May be set to <c>true</c> after scanning when the manifest references a type
/// that the scanner did not mark as unconditional. Should only ever be set
/// to <c>true</c>, never back to <c>false</c>.
/// </summary>
public bool IsUnconditional { get; init; }
public bool IsUnconditional { get; set; }

/// <summary>
/// True for Application and Instrumentation types. These types cannot call
/// True for Application and Instrumentation types, plus any generated managed
/// base classes they rely on during startup. These types cannot call
/// <c>registerNatives</c> in their static initializer because the native library
/// (<c>libmonodroid.so</c>) is not loaded until after the Application class is instantiated.
/// Registration is deferred to <c>ApplicationRegistration.registerApplications()</c>.
/// This may also be set after scanning when a type is only discovered from
/// manifest <c>android:name</c> usage on <c>&lt;application&gt;</c> or
/// <c>&lt;instrumentation&gt;</c>.
/// </summary>
public bool CannotRegisterInStaticConstructor { get; init; }
public bool CannotRegisterInStaticConstructor { get; set; }

/// <summary>
/// Marshal methods: methods with [Register(name, sig, connector)], [Export], or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using Java.Interop.Tools.JavaCallableWrappers;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

Expand Down Expand Up @@ -732,7 +733,7 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
(RegisterInfo Info, string DeclaringTypeName, string DeclaringAssemblyName)? FindBaseRegisteredMethodInfo (
TypeDefinition typeDef, AssemblyIndex index, string methodName, MethodDefinition derivedMethod)
{
if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out var baseHandle, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) {
if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) {
return null;
}

Expand Down Expand Up @@ -760,10 +761,8 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
}
}

// Recurse up the hierarchy (stop at DoNotGenerateAcw boundary)
if (baseIndex.RegisterInfoByType.TryGetValue (baseHandle, out var baseRegInfo) && baseRegInfo.DoNotGenerateAcw) {
return null;
}
// Keep walking the full base hierarchy so overrides can inherit [Register]
// metadata declared above an intermediate MCW base type.
return FindBaseRegisteredMethodInfo (baseTypeDef, baseIndex, methodName, derivedMethod);
}

Expand Down Expand Up @@ -796,7 +795,7 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
MarshalMethodInfo? FindBaseRegisteredProperty (TypeDefinition typeDef, AssemblyIndex index,
string getterName, MethodDefinition derivedGetter)
{
if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out var baseHandle, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) {
if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out var baseAssemblyName)) {
return null;
}

Expand Down Expand Up @@ -835,10 +834,8 @@ bool TryResolveBaseType (TypeDefinition typeDef, AssemblyIndex index,
}
}

// Recurse up (stop at DoNotGenerateAcw boundary)
if (baseIndex.RegisterInfoByType.TryGetValue (baseHandle, out var baseRegInfo) && baseRegInfo.DoNotGenerateAcw) {
return null;
}
// Keep walking the full base hierarchy so property overrides can inherit
// [Register] metadata declared above an intermediate MCW base type.
return FindBaseRegisteredProperty (baseTypeDef, baseIndex, getterName, derivedGetter);
}

Expand Down Expand Up @@ -1477,8 +1474,11 @@ static string GetCrc64PackageName (string ns, string assemblyName)
return ns.ToLowerInvariant ().Replace ('.', '/');
}

// Keep this in sync with JavaNativeTypeManager.ToJniName(Type)/(TypeDefinition).
// The trimmable build path must emit the exact same CRC64 package names that the
// runtime later computes for FindClass(Type) and peer activation.
var data = System.Text.Encoding.UTF8.GetBytes ($"{ns}:{assemblyName}");
var hash = System.IO.Hashing.Crc64.Hash (data);
var hash = Crc64Helper.Compute (data);
return $"crc64{BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant ()}";
}

Expand Down
Loading