Skip to content
Merged
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
12 changes: 12 additions & 0 deletions Documentation/workflow/DevelopmentTips.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

Tips and tricks while developing Xamarin.Android.

# Run MSBuild-Based On-Device Unit Tests

The [`tests/MSBuildDeviceIntegration`](tests/MSBuildDeviceIntegration)
directory contains NUnit-based unit tests which need to run against an attached
Android device (hardware or emulator). There are *lots* of tests in here, and
running them all can take a significant amount of time.

If you need to run only *one* `[Test]` method, you can use
[`dotnet test --filter`](https://docs.microsoft.com/dotnet/core/testing/selective-unit-tests?pivots=mstest):

./dotnet-local.sh test bin/TestDebug/MSBuildDeviceIntegration/net6.0/MSBuildDeviceIntegration.dll --filter "Name~TypeAndMemberRemapping"

# Update directory

When a Xamarin.Android app launches on an Android device, and the app was
Expand Down
2 changes: 1 addition & 1 deletion external/Java.Interop
162 changes: 158 additions & 4 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Reflection;
Expand Down Expand Up @@ -263,7 +264,11 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
{
string? j = JNIEnv.TypemapManagedToJava (type);
if (j != null) {
return j;
return
#if NET
GetReplacementTypeCore (j) ??
#endif // NET
j;
}
if (JNIEnv.IsRunningOnDesktop) {
return JavaNativeTypeManager.ToJniName (type);
Expand All @@ -274,14 +279,163 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
protected override IEnumerable<string> GetSimpleReferences (Type type)
{
string? j = JNIEnv.TypemapManagedToJava (type);
#if NET
j = GetReplacementTypeCore (j) ?? j;
#endif // NET
if (JNIEnv.IsRunningOnDesktop) {
string? d = JavaNativeTypeManager.ToJniName (type);
if (j != null && d != null) {
return new[]{j, d};
}
if (d != null) {
return new[]{d};
}
}
if (j != null) {
yield return j;
return new[]{j};
}
if (JNIEnv.IsRunningOnDesktop) {
yield return JavaNativeTypeManager.ToJniName (type);
return Array.Empty<string> ();
}

#if NET
protected override IReadOnlyList<string>? GetStaticMethodFallbackTypesCore (string jniSimpleReference)
{
ReadOnlySpan<char> name = jniSimpleReference;
int slash = name.LastIndexOf ('/');
var desugarType = new StringBuilder (jniSimpleReference.Length + "Desugar".Length);
if (slash > 0) {
desugarType.Append (name.Slice (0, slash+1))
.Append ("Desugar")
.Append (name.Slice (slash+1));
} else {
desugarType.Append ("Desugar").Append (name);
}

return new[]{
desugarType.ToString (),
$"{jniSimpleReference}$-CC"
};
}

protected override string? GetReplacementTypeCore (string jniSimpleReference)
{
if (JNIEnv.ReplacementTypes == null) {
return null;
}
if (JNIEnv.ReplacementTypes.TryGetValue (jniSimpleReference, out var v)) {
return v;
}
return null;
}

protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature)
{
if (JNIEnv.ReplacementMethods == null) {
return null;
}
#if !STRUCTURED
if (!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) &&
!JNIEnv.ReplacementMethods.TryGetValue (CreateReplacementMethodsKey (jniSourceType, jniMethodName, null), out r)) {
return null;
}
ReadOnlySpan<char> replacementInfo = r;

var targetType = GetNextString (ref replacementInfo);
var targetName = GetNextString (ref replacementInfo);
var targetSig = GetNextString (ref replacementInfo);
var paramCountStr = GetNextString (ref replacementInfo);
var isStaticStr = GetNextString (ref replacementInfo);

int? paramCount = null;
if (!paramCountStr.IsEmpty) {
if (!int.TryParse (paramCountStr, 0, System.Globalization.CultureInfo.InvariantCulture, out var count)) {
return null;
}
paramCount = count;
}

bool isStatic = false;
if (isStaticStr.Equals ("true", StringComparison.Ordinal)) {
isStatic = true;
}

if (targetSig.IsEmpty && isStatic) {
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
paramCount++;
jniMethodSignature = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
}

return new JniRuntime.ReplacementMethodInfo {
SourceJniType = jniSourceType,
SourceJniMethodName = jniMethodName,
SourceJniMethodSignature = jniMethodSignature,
TargetJniType = targetType.IsEmpty ? jniSourceType : new string (targetType),
TargetJniMethodName = targetName.IsEmpty ? jniMethodName : new string (targetName),
TargetJniMethodSignature = targetSig.IsEmpty ? jniMethodSignature : new string (targetSig),
TargetJniMethodParameterCount = paramCount,
TargetJniMethodInstanceToStatic = isStatic,
};
#else
if (!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, jniMethodSignature), out var r) &&
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, GetMethodSignatureWithoutReturnType ()), out r) &&
!JNIEnv.ReplacementMethods.TryGetValue ((jniSourceType, jniMethodName, null), out r)) {
return null;
}
var targetSig = r.TargetSignature;
var paramCount = r.ParamCount;
if (targetSig == null && r.TurnStatic) {
targetSig = $"(L{jniSourceType};" + jniMethodSignature.Substring ("(".Length);
paramCount = paramCount ?? JniMemberSignature.GetParameterCountFromMethodSignature (jniMethodSignature);
paramCount++;
}
return new JniRuntime.ReplacementMethodInfo {
SourceJniType = jniSourceType,
SourceJniMethodName = jniMethodName,
SourceJniMethodSignature = jniMethodSignature,
TargetJniType = r.TargetType ?? jniSourceType,
TargetJniMethodName = r.TargetName ?? jniMethodName,
TargetJniMethodSignature = targetSig ?? jniMethodSignature,
TargetJniMethodParameterCount = paramCount,
TargetJniMethodInstanceToStatic = r.TurnStatic,
};
#endif // !STRUCTURED

string GetMethodSignatureWithoutReturnType ()
{
int i = jniMethodSignature.IndexOf (')');
return jniMethodSignature.Substring (0, i+1);
}

string GetValue (string? value)
{
return value == null ? "null" : $"\"{value}\"";
}

ReadOnlySpan<char> GetNextString (ref ReadOnlySpan<char> info)
{
int index = info.IndexOf ('\t');
var r = info;
if (index >= 0) {
r = info.Slice (0, index);
info = info.Slice (index+1);
return r;
}
info = default;
return r;
}
}

static string CreateReplacementMethodsKey (string? sourceType, string? methodName, string? methodSignature) =>
new StringBuilder ()
.Append (sourceType)
.Append ('\t')
.Append (methodName)
.Append ('\t')
.Append (methodSignature)
.ToString ();
#endif // NET

delegate Delegate GetCallbackHandler ();

static MethodInfo? dynamic_callback_gen;
Expand Down
19 changes: 19 additions & 0 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
using Java.Interop.Tools.TypeNameMappings;
using System.Diagnostics.CodeAnalysis;

#if NET
using ReplacementTypesDict = System.Collections.Generic.Dictionary<string, string>;
using ReplacementMethodsDict = System.Collections.Generic.Dictionary<string, string>;
#endif // NET

namespace Android.Runtime {
#pragma warning disable 0649
struct JnienvInitializeArgs {
Expand All @@ -35,6 +40,8 @@ struct JnienvInitializeArgs {
public int packageNamingPolicy;
public byte ioExceptionType;
public int jniAddNativeMethodRegistrationAttributePresent;
public IntPtr mappingXml;
public int mappingXmlLen;
}
#pragma warning restore 0649

Expand All @@ -61,6 +68,11 @@ public static partial class JNIEnv {
static AndroidRuntime? androidRuntime;
static BoundExceptionType BoundExceptionType;

#if NET
internal static ReplacementTypesDict? ReplacementTypes;
internal static ReplacementMethodsDict? ReplacementMethods;
#endif // NET

[ThreadStatic]
static byte[]? mvid_bytes;

Expand Down Expand Up @@ -166,6 +178,13 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
gref_class = args->grefClass;
mid_Class_forName = new JniMethodInfo (args->Class_forName, isStatic: true);

#if NET
if (args->mappingXml != IntPtr.Zero) {
var xml = Encoding.UTF8.GetString ((byte*) args->mappingXml, args->mappingXmlLen);
(ReplacementTypes, ReplacementMethods) = MamXmlParser.ParseStrings (xml);
}
#endif // NET

if (args->localRefsAreIndirect == 1)
IdentityHash = v => _monodroid_get_identity_hash_code (Handle, v);
else
Expand Down
Loading