diff --git a/external/Java.Interop b/external/Java.Interop index c7b4ea6be3c..bb2fe5e5071 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit c7b4ea6be3c38a630ff2ba3450f8721f6e43d90b +Subproject commit bb2fe5e5071484094ab77461660d197c67191ddb diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs index 78d62576a0e..7b549f0061f 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs @@ -1,4 +1,5 @@ -using Android.App; +using System; +using Android.App; using Android.Content; using Android.Util; using Android.Views; @@ -44,6 +45,47 @@ public void UpperAndLowerCaseCustomWidget_FromLibrary_ShouldNotThrowInflateExcep inflater.Inflate (Resource.Layout.upper_lower_custom, null); }, "Regression test for widgets with uppercase and lowercase namespace (bug #23880) failed."); } + + // https://github.com/dotnet/android/issues/11101 + [Test] + public void InflateCustomView_ShouldNotLeakGlobalRefs () + { + var inflater = (LayoutInflater) Application.Context.GetSystemService (Context.LayoutInflaterService); + Assert.IsNotNull (inflater); + + // Warm up: inflate once to populate caches and type mappings, + // and let any background thread activity from previous tests settle. + inflater.Inflate (Resource.Layout.lowercase_custom, null); + CollectGarbage (times: 3); + + int grefBefore = Java.Interop.Runtime.GlobalReferenceCount; + + // Use a large number of inflations so that a real leak (3+ global refs + // per inflate) produces a delta far above any background noise from + // Android system services, GC bridge processing, or finalizer threads. + const int inflateCount = 100; + for (int i = 0; i < inflateCount; i++) { + inflater.Inflate (Resource.Layout.lowercase_custom, null); + } + + CollectGarbage (times: 3); + + int grefAfter = Java.Interop.Runtime.GlobalReferenceCount; + int delta = grefAfter - grefBefore; + + // A real leak would produce delta >= 300 (3 leaked refs per inflate). + // Use a generous threshold to tolerate background noise on real devices. + Assert.IsTrue (delta <= 100, + $"Global reference leak detected: {delta} extra global refs after inflating/GC'ing {inflateCount} custom views. Before={grefBefore}, After={grefAfter}"); + + static void CollectGarbage (int times) + { + for (int i = 0; i < times; i++) { + GC.Collect (); + GC.WaitForPendingFinalizers (); + } + } + } } public class CustomButton : Button