Skip to content

[Android] Fix ActivityStateManager leaking lifecycle callbacks on Activity recreation - #36161

Merged
kubaflo merged 2 commits into
dotnet:inflight/currentfrom
Shalini-Ashokan:fix-36035
Jun 28, 2026
Merged

[Android] Fix ActivityStateManager leaking lifecycle callbacks on Activity recreation#36161
kubaflo merged 2 commits into
dotnet:inflight/currentfrom
Shalini-Ashokan:fix-36035

Conversation

@Shalini-Ashokan

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Issue Details

Every time an Android Activity is programmatically recreated (via Activity.Recreate()), Platform.ActivityStateChanged fires more times than expected. After 120 recreations, the event fires 61× more than it should, causing serious performance problems.

Root Cause

ActivityStateManager.Init(Application) was called on every Activity recreation, and each call registered a new listener with Android without removing the old one. Android keeps all registered listeners, so after N recreations, N+1 listeners exist and every event fires N+1 times.

Description of Change

Added a 2-line early-return guard in Init(Application):

if (lifecycleListener is not null)
return;

This ensures the listener is registered only once on the first call. All subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Issues Fixed

Fixes #36035

Output ScreenShot

Before After
image image

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36161

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36161"

@dotnet-policy-service dotnet-policy-service Bot added the community ✨ Community Contribution label Jun 26, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Hey there @@Shalini-Ashokan! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Jun 26, 2026
@Tamilarasan-Paranthaman Tamilarasan-Paranthaman added platform/android area-core-lifecycle XPlat and Native UIApplicationDelegate/Activity/Window lifecycle events labels Jun 26, 2026
@sheiksyedm
sheiksyedm marked this pull request as ready for review June 26, 2026 15:15
Copilot AI review requested due to automatic review settings June 26, 2026 15:15
@sheiksyedm

Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests , maui-pr-devicetests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an Android lifecycle-callback leak in ActivityStateManager by making ActivityStateManagerImplementation.Init(Application) idempotent, preventing duplicate registrations during Activity recreation and avoiding multiplied Platform.ActivityStateChanged notifications.

Changes:

  • Add an early-return guard in ActivityStateManagerImplementation.Init(Application) to prevent re-registering RegisterActivityLifecycleCallbacks.
  • Add Android-only device tests to validate listener reuse and single ActivityStateChanged invocation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Essentials/src/Platform/ActivityStateManager.android.cs Makes Init(Application) idempotent to avoid accumulating application lifecycle callbacks across activity recreations.
src/Essentials/test/DeviceTests/Tests/ActivityStateManager_Tests.cs Adds regression coverage for the listener leak / event-multiplication scenario.

Comment on lines +29 to +46
public void Init_CalledMultipleTimes_SameListenerIsKept()
{
var app = (global::Android.App.Application)global::Android.App.Application.Context;
var manager = new ActivityStateManagerImplementation();

manager.Init(app);
var listenerAfterFirst = GetListener(manager);

manager.Init(app);
var listenerAfterSecond = GetListener(manager);

manager.Init(app);
var listenerAfterThird = GetListener(manager);

Assert.NotNull(listenerAfterFirst);
Assert.Same(listenerAfterFirst, listenerAfterSecond);
Assert.Same(listenerAfterFirst, listenerAfterThird);
}
Comment on lines +64 to +88
var activity = MauiPlatform.CurrentActivity;

var manager = new ActivityStateManagerImplementation();

int invocations = 0;
manager.ActivityStateChanged += (_, _) => Interlocked.Increment(ref invocations);

// Capture the listener after EACH Init call, before the next one overwrites it.
manager.Init(app);
var l1 = GetListener(manager) as global::Android.App.Application.IActivityLifecycleCallbacks;

manager.Init(app);
var l2 = GetListener(manager) as global::Android.App.Application.IActivityLifecycleCallbacks;

manager.Init(app);
var l3 = GetListener(manager) as global::Android.App.Application.IActivityLifecycleCallbacks;

// Simulate Android dispatching a Resumed event to every distinct registered listener.
// With fix: l1 == l2 == l3 (same object) → 1 unique listener → 1 invocation ✅
// With bug: l1 != l2 != l3 (different) → 3 unique listeners → 3 invocations ❌
foreach (var l in new[] { l1, l2, l3 }.Distinct())
l?.OnActivityResumed(activity);

Assert.Equal(1, invocations);
}
@github-actions

This comment has been minimized.

@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 27, 2026
@kubaflo

This comment has been minimized.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Review — 1 findings

See inline comments for details.

var app = (global::Android.App.Application)global::Android.App.Application.Context;
var manager = new ActivityStateManagerImplementation();

manager.Init(app);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[moderate] Regression Prevention / Memory Leak — These tests register a real Application.IActivityLifecycleCallbacks instance on the process-wide Android Application, but never unregister it. RegisterActivityLifecycleCallbacks is additive and the Application lives for the whole device-test process, so each test leaves behind a listener that still references its ActivityStateManagerImplementation and receives future activity lifecycle events. This can make later tests observe extra callbacks and prevents the test manager from being collected.

Please unregister the listener in finally (and do the same for the listener created around line 72), e.g. retrieve GetListener(manager) as Application.IActivityLifecycleCallbacks and call app.UnregisterActivityLifecycleCallbacks(listener) after assertions.

@MauiBot MauiBot added s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Jun 27, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review Summary

@Shalini-Ashokan — new AI review results are available based on this last commit: b31f961. To request a fresh review after new comments or commits, comment /review rerun.

Gate Passed Confidence Low Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: 2a323a50

Test Without Fix (expect FAIL) With Fix (expect PASS)
📱 ActivityStateManager_Tests (Init_CalledMultipleTimes_SameListenerIsKept, Init_CalledMultipleTimes_ActivityStateChangedFiresOnce) Category=ActivityStateManager ✅ FAIL — 505s ✅ PASS — 337s
🔴 Without fix — 📱 ActivityStateManager_Tests (Init_CalledMultipleTimes_SameListenerIsKept, Init_CalledMultipleTimes_ActivityStateChangedFiresOnce): FAIL ✅ · 505s

(truncated to last 15,000 chars)

tionsPlacemark
      06-27 10:47:32.495  8551  8705 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Maps_Tests 0.0015582 ms
      06-27 10:47:32.495  8551  8705 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Shared.Android_FileSystemUtils_Tests
      06-27 10:47:32.497  8551  8705 I DOTNET  : 	[PASS] IsFileReadable_Returns_False_For_Inaccessible_Path
      06-27 10:47:32.497  8551  8705 I DOTNET  : 	[PASS] IsFileReadable_Returns_True_For_Readable_File
      06-27 10:47:32.498  8551  8705 I DOTNET  : 	[PASS] IsFileReadable_Returns_False_For_NonExistent_File
      06-27 10:47:32.498  8551  8705 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Shared.Android_FileSystemUtils_Tests 0.0016787 ms
      06-27 10:47:32.498  8551  8705 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Permissions_Tests
      06-27 10:47:32.509  8551  8705 I DOTNET  : 	[PASS] Check_Status
      06-27 10:47:32.512  8551  8705 I DOTNET  : 	[PASS] Check_Status
      06-27 10:47:32.513  8551  8705 I DOTNET  : 	[PASS] Ensure_Declared
      06-27 10:47:32.516  8551  8705 I DOTNET  : 	[PASS] Ensure_Declared
      06-27 10:47:32.518  8551  8705 I DOTNET  : 	[PASS] Request
      06-27 10:47:32.520  8551  8705 I DOTNET  : 	[PASS] Request
      06-27 10:47:32.522  8551  8705 I DOTNET  : 	[PASS] StorageAndroid13AlwaysGranted
      06-27 10:47:32.522  8551  8705 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Permissions_Tests 0.0222112 ms
      06-27 10:47:32.522  8551  8705 I DOTNET  : UsesPreferences
      06-27 10:47:32.526  8551  8705 I DOTNET  : 	[PASS] Set_Get_Bool
      06-27 10:47:32.527  8551  8705 I DOTNET  : 	[PASS] Set_Get_Bool
      06-27 10:47:32.535  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Double_NonStatic
      06-27 10:47:32.536  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Double_NonStatic
      06-27 10:47:32.539  8551  8705 I DOTNET  : 	[PASS] Not_ContainsKey
      06-27 10:47:32.539  8551  8705 I DOTNET  : 	[PASS] Not_ContainsKey
      06-27 10:47:32.557  8551  8705 I DOTNET  : 	[PASS] FailsWithUnsupportedType
      06-27 10:47:32.561  8551  8705 I DOTNET  : 	[PASS] Set_Get_Int
      06-27 10:47:32.562  8551  8705 I DOTNET  : 	[PASS] Set_Get_Int
      06-27 10:47:32.563  8551  8705 I DOTNET  : 	[PASS] Set_Get_String
      06-27 10:47:32.563  8551  8705 I DOTNET  : 	[PASS] Set_Get_String
      06-27 10:47:32.570  8551  8705 I DOTNET  : 	[PASS] Set_Get_Long
      06-27 10:47:32.571  8551  8705 I DOTNET  : 	[PASS] Set_Get_Long
      06-27 10:47:32.578  8551  8705 I DOTNET  : 	[PASS] DateTimePreservesKind_NonStatic
      06-27 10:47:32.581  8551  8705 I DOTNET  : 	[PASS] DateTimePreservesKind_NonStatic
      06-27 10:47:32.593  8551  8705 I DOTNET  : 	[PASS] Set_Get_Float
      06-27 10:47:32.594  8551  8705 I DOTNET  : 	[PASS] Set_Get_Float
      06-27 10:47:32.595  8551  8705 I DOTNET  : 	[PASS] Remove_Get_String
      06-27 10:47:32.595  8551  8705 I DOTNET  : 	[PASS] Remove_Get_String
      06-27 10:47:32.600  8551  8705 I DOTNET  : 	[PASS] Set_Get_Double
      06-27 10:47:32.601  8551  8705 I DOTNET  : 	[PASS] Set_Get_Double
      06-27 10:47:32.601  8551  8705 I DOTNET  : 	[PASS] Set_Set_Null_Get_String_NonStatic
      06-27 10:47:32.602  8551  8705 I DOTNET  : 	[PASS] Set_Set_Null_Get_String_NonStatic
      06-27 10:47:32.604  8551  8705 I DOTNET  : 	[PASS] Clear
      06-27 10:47:32.605  8551  8705 I DOTNET  : 	[PASS] Clear
      06-27 10:47:32.606  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Float_NonStatic
      06-27 10:47:32.607  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Float_NonStatic
      06-27 10:47:32.608  8551  8705 I DOTNET  : 	[PASS] Not_ContainsKey_NonStatic
      06-27 10:47:32.608  8551  8705 I DOTNET  : 	[PASS] Not_ContainsKey_NonStatic
      06-27 10:47:32.609  8551  8705 I DOTNET  : 	[PASS] Set_Get_DateTime_NonStatic
      06-27 10:47:32.609  8551  8705 I DOTNET  : 	[PASS] Set_Get_DateTime_NonStatic
      06-27 10:47:32.610  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Long
      06-27 10:47:32.611  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Long
      06-27 10:47:32.612  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Bool
      06-27 10:47:32.612  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Bool
      06-27 10:47:32.614  8551  8705 I DOTNET  : 	[PASS] DateTimePreservesKind
      06-27 10:47:32.614  8551  8705 I DOTNET  : 	[PASS] DateTimePreservesKind
      06-27 10:47:32.615  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Double
      06-27 10:47:32.616  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Double
      06-27 10:47:32.617  8551  8705 I DOTNET  : 	[PASS] Clear_NonStatic
      06-27 10:47:32.620  8551  8705 I DOTNET  : 	[PASS] Clear_NonStatic
      06-27 10:47:32.621  8551  8705 I DOTNET  : 	[PASS] Set_Get_Long_NonStatic
      06-27 10:47:32.622  8551  8705 I DOTNET  : 	[PASS] Set_Get_Long_NonStatic
      06-27 10:47:32.622  8551  8705 I DOTNET  : 	[PASS] Set_Set_Null_Get_String
      06-27 10:47:32.622  8551  8705 I DOTNET  : 	[PASS] Set_Set_Null_Get_String
      06-27 10:47:32.623  8551  8705 I DOTNET  : 	[PASS] Set_Get_DateTime
      06-27 10:47:32.623  8551  8705 I DOTNET  : 	[PASS] Set_Get_DateTime
      06-27 10:47:32.624  8551  8705 I DOTNET  : 	[PASS] Set_Get_Float_NonStatic
      06-27 10:47:32.626  8551  8705 I DOTNET  : 	[PASS] Set_Get_Float_NonStatic
      06-27 10:47:32.629  8551  8705 I DOTNET  : 	[PASS] Remove
      06-27 10:47:32.630  8551  8705 I DOTNET  : 	[PASS] Remove
      06-27 10:47:32.631  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Long_NonStatic
      06-27 10:47:32.631  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Long_NonStatic
      06-27 10:47:32.632  8551  8705 I DOTNET  : 	[PASS] Remove_NonStatic
      06-27 10:47:32.632  8551  8705 I DOTNET  : 	[PASS] Remove_NonStatic
      06-27 10:47:32.633  8551  8705 I DOTNET  : 	[PASS] Does_ContainsKey_NonStatic
      06-27 10:47:32.633  8551  8705 I DOTNET  : 	[PASS] Does_ContainsKey_NonStatic
      06-27 10:47:32.634  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Bool_NonStatic
      06-27 10:47:32.634  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Bool_NonStatic
      06-27 10:47:32.635  8551  8705 I DOTNET  : 	[PASS] Set_Get_Int_NonStatic
      06-27 10:47:32.636  8551  8705 I DOTNET  : 	[PASS] Set_Get_Int_NonStatic
      06-27 10:47:32.662  8551  8705 I DOTNET  : 	[PASS] DateTimeOffsetPreservesOffset_NonStatic
      06-27 10:47:32.664  8551  8705 I DOTNET  : 	[PASS] DateTimeOffsetPreservesOffset_NonStatic
      06-27 10:47:32.665  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Int_NonStatic
      06-27 10:47:32.666  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Int_NonStatic
      06-27 10:47:32.667  8551  8705 I DOTNET  : 	[PASS] Set_Get_String_NonStatic
      06-27 10:47:32.667  8551  8705 I DOTNET  : 	[PASS] Set_Get_String_NonStatic
      06-27 10:47:32.676  8551  8705 I DOTNET  : 	[PASS] Does_ContainsKey
      06-27 10:47:32.676  8551  8705 I DOTNET  : 	[PASS] Does_ContainsKey
      06-27 10:47:32.677  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Int
      06-27 10:47:32.678  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Int
      06-27 10:47:32.680  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Float
      06-27 10:47:32.681  8551  8705 I DOTNET  : 	[PASS] Remove_Get_Float
      06-27 10:47:32.682  8551  8705 I DOTNET  : 	[PASS] Remove_Get_String_NonStatic
      06-27 10:47:32.683  8551  8705 I DOTNET  : 	[PASS] Remove_Get_String_NonStatic
      06-27 10:47:32.684  8551  8705 I DOTNET  : 	[PASS] Set_Get_Double_NonStatic
      06-27 10:47:32.688  8551  8705 I DOTNET  : 	[PASS] Set_Get_Double_NonStatic
      06-27 10:47:32.689  8551  8705 I DOTNET  : 	[PASS] Set_Get_Bool_NonStatic
      06-27 10:47:32.689  8551  8705 I DOTNET  : 	[PASS] Set_Get_Bool_NonStatic
      06-27 10:47:32.689  8551  8705 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Preferences_Tests 0.0910580 ms
      06-27 10:47:32.863  8551  8715 I DOTNET  : 	[PASS] Remove_All_Keys
      06-27 10:47:32.924  8551  8720 I DOTNET  : 	[PASS] Remove_All_Keys
      06-27 10:47:36.007  8551  8727 I DOTNET  : 	[PASS] Set_Get_Remove_Async_MultipleTimes
      06-27 10:47:36.052  8551  8732 I DOTNET  : 	[PASS] Asymmetric_to_Symmetric_API_Upgrade
      06-27 10:47:36.071  8551  8732 I DOTNET  : 	[PASS] Non_Existent_Key_Returns_Null
      06-27 10:47:36.107  8551  8737 I DOTNET  : 	[PASS] Saves_Same_Key_Twice
      06-27 10:47:36.136  8551  8742 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:47:36.167  8551  8747 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:47:36.192  8551  8752 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:47:36.220  8551  8757 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:47:36.246  8551  8762 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:47:36.272  8551  8767 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:47:37.820  8551  8772 I DOTNET  : 	[PASS] Set_Get_Async_MultipleTimes
      06-27 10:47:37.900  8551  8777 I DOTNET  : 	[PASS] Fix_Corrupt_Data
      06-27 10:47:39.404  8551  8777 I DOTNET  : 	[PASS] Set_Get_Wait_MultipleTimes
      06-27 10:47:39.460  8551  8777 I DOTNET  : 	[PASS] Remove_Key
      06-27 10:47:39.490  8551  8777 I DOTNET  : 	[PASS] Remove_Key
      06-27 10:47:39.490  8551  8777 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.SecureStorage_Tests 6.7612552 ms
      06-27 10:47:39.490  8551  8777 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.DeviceDisplay_Tests
      06-27 10:47:39.492  8551  8777 I DOTNET  : 	[PASS] Screen_Metrics_Are_Not_Null
      06-27 10:47:39.492  8551  8777 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.DeviceDisplay_Tests 0.001864 ms
      06-27 10:47:39.492  8551  8777 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Shared.Android_FileProvider_Tests
      06-27 10:47:39.499  8551  8777 I DOTNET  : 	[PASS] Get_Existing_Internal_Cache_Shareable_Uri
      06-27 10:47:39.503  8551  8777 I DOTNET  : 	[PASS] Get_Existing_Internal_Cache_Shareable_Uri
      06-27 10:47:39.508  8551  8777 I DOTNET  : 	[PASS] No_Media_Fails_Get_External_Cache_Shareable_Uri
      06-27 10:47:39.541  8551  8777 I DOTNET  : 	[PASS] Get_Shareable_Uri
      06-27 10:47:39.552  8551  8777 I DOTNET  : 	[PASS] Get_Shareable_Uri
      06-27 10:47:39.555  8551  8777 I DOTNET  : 	[PASS] Get_Existing_External_Shareable_Uri
      06-27 10:47:39.560  8551  8777 I DOTNET  : 	[PASS] Get_Existing_External_Shareable_Uri
      06-27 10:47:39.562  8551  8777 I DOTNET  : 	[PASS] Get_Existing_External_Cache_Shareable_Uri
      06-27 10:47:39.567  8551  8777 I DOTNET  : 	[PASS] Get_Existing_External_Cache_Shareable_Uri
      06-27 10:47:39.570  8551  8777 I DOTNET  : 	[PASS] Get_External_Cache_Shareable_Uri
      06-27 10:47:39.570  8551  8777 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Shared.Android_FileProvider_Tests 0.0744645 ms
      06-27 10:47:39.577  8551  8581 I DOTNET  : Failed tests:
      06-27 10:47:39.578  8551  8581 I DOTNET  : 1) 	[FAIL] Init_CalledMultipleTimes_ActivityStateChangedFiresOnce   Test name: Init_CalledMultipleTimes_ActivityStateChangedFiresOnce
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Assembly:  [Microsoft.Maui.Essentials.DeviceTests, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null]
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Exception messages: Assert.Equal() Failure: Values differ
      06-27 10:47:39.578  8551  8581 I DOTNET  : Expected: 1
      06-27 10:47:39.578  8551  8581 I DOTNET  : Actual:   3   Exception stack traces:    at Microsoft.Maui.Essentials.DeviceTests.ActivityStateManager_Tests.Init_CalledMultipleTimes_ActivityStateChangedFiresOnce()
      06-27 10:47:39.578  8551  8581 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
      06-27 10:47:39.578  8551  8581 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Execution time: 0.0099839
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Test trait name: Category
      06-27 10:47:39.578  8551  8581 I DOTNET  :       value: ActivityStateManager
      06-27 10:47:39.578  8551  8581 I DOTNET  : 
      06-27 10:47:39.578  8551  8581 I DOTNET  : 2) 	[FAIL] Init_CalledMultipleTimes_SameListenerIsKept   Test name: Init_CalledMultipleTimes_SameListenerIsKept
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Assembly:  [Microsoft.Maui.Essentials.DeviceTests, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null]
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Exception messages: Assert.Same() Failure: Values are not the same instance
      06-27 10:47:39.578  8551  8581 I DOTNET  : Expected: crc64ba438d8f48cf7e75.ActivityLifecycleContextListener@10f09b1
      06-27 10:47:39.578  8551  8581 I DOTNET  : Actual:   crc64ba438d8f48cf7e75.ActivityLifecycleContextListener@8413e96   Exception stack traces:    at Microsoft.Maui.Essentials.DeviceTests.ActivityStateManager_Tests.Init_CalledMultipleTimes_SameListenerIsKept()
      06-27 10:47:39.578  8551  8581 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
      06-27 10:47:39.578  8551  8581 I DOTNET  :    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object , BindingFlags )
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Execution time: 0.0007553
      06-27 10:47:39.578  8551  8581 I DOTNET  :    Test trait name: Category
      06-27 10:47:39.578  8551  8581 I DOTNET  :       value: ActivityStateManager
      06-27 10:47:39.578  8551  8581 I DOTNET  : 
      06-27 10:47:39.599  8551  8581 I DOTNET  : Xml file was written to the provided writer.
      06-27 10:47:39.599  8551  8581 I DOTNET  : Tests run: 307 Passed: 275 Inconclusive: 0 Failed: 2 Ignored: 30
�[41m�[30mfail�[39m�[22m�[49m: Non-success instrumentation exit code: 1, expected: 0
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervm6n5x7",
        "exitCode": 1,
        "exitCodeName": "TESTS_FAILED",
        "platform": "android",
        "instrumentationExitCode": 1,
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "testResults.xml",
            "type": "test-results"
          },
          {
            "name": "adb-logcat-com.microsoft.maui.essentials.devicetests-default.log",
            "type": "logcat"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.essentials.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.essentials.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.essentials.devicetests
XHarness exit code: 1 (TESTS_FAILED)
  Tests completed with exit code: 1

🟢 With fix — 📱 ActivityStateManager_Tests (Init_CalledMultipleTimes_SameListenerIsKept, Init_CalledMultipleTimes_ActivityStateChangedFiresOnce): PASS ✅ · 337s

(truncated to last 15,000 chars)

[PASS] Remove_NonStatic
      06-27 10:53:02.388 10895 10972 I DOTNET  : 	[PASS] Remove_NonStatic
      06-27 10:53:02.389 10895 10972 I DOTNET  : 	[PASS] Does_ContainsKey_NonStatic
      06-27 10:53:02.389 10895 10972 I DOTNET  : 	[PASS] Does_ContainsKey_NonStatic
      06-27 10:53:02.390 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Bool_NonStatic
      06-27 10:53:02.390 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Bool_NonStatic
      06-27 10:53:02.391 10895 10972 I DOTNET  : 	[PASS] Set_Get_Int_NonStatic
      06-27 10:53:02.391 10895 10972 I DOTNET  : 	[PASS] Set_Get_Int_NonStatic
      06-27 10:53:02.416 10895 10972 I DOTNET  : 	[PASS] DateTimeOffsetPreservesOffset_NonStatic
      06-27 10:53:02.417 10895 10972 I DOTNET  : 	[PASS] DateTimeOffsetPreservesOffset_NonStatic
      06-27 10:53:02.418 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Int_NonStatic
      06-27 10:53:02.418 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Int_NonStatic
      06-27 10:53:02.419 10895 10972 I DOTNET  : 	[PASS] Set_Get_String_NonStatic
      06-27 10:53:02.419 10895 10972 I DOTNET  : 	[PASS] Set_Get_String_NonStatic
      06-27 10:53:02.419 10895 10972 I DOTNET  : 	[PASS] Does_ContainsKey
      06-27 10:53:02.420 10895 10972 I DOTNET  : 	[PASS] Does_ContainsKey
      06-27 10:53:02.421 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Int
      06-27 10:53:02.421 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Int
      06-27 10:53:02.422 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Float
      06-27 10:53:02.423 10895 10972 I DOTNET  : 	[PASS] Remove_Get_Float
      06-27 10:53:02.424 10895 10972 I DOTNET  : 	[PASS] Remove_Get_String_NonStatic
      06-27 10:53:02.424 10895 10972 I DOTNET  : 	[PASS] Remove_Get_String_NonStatic
      06-27 10:53:02.425 10895 10972 I DOTNET  : 	[PASS] Set_Get_Double_NonStatic
      06-27 10:53:02.427 10895 10972 I DOTNET  : 	[PASS] Set_Get_Double_NonStatic
      06-27 10:53:02.427 10895 10972 I DOTNET  : 	[PASS] Set_Get_Bool_NonStatic
      06-27 10:53:02.428 10895 10972 I DOTNET  : 	[PASS] Set_Get_Bool_NonStatic
      06-27 10:53:02.428 10895 10972 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Preferences_Tests 0.0924499 ms
      06-27 10:53:02.617 10895 10986 I DOTNET  : 	[PASS] Remove_All_Keys
      06-27 10:53:02.680 10895 10991 I DOTNET  : 	[PASS] Remove_All_Keys
      06-27 10:53:05.806 10895 10999 I DOTNET  : 	[PASS] Set_Get_Remove_Async_MultipleTimes
      06-27 10:53:05.852 10895 11004 I DOTNET  : 	[PASS] Asymmetric_to_Symmetric_API_Upgrade
      06-27 10:53:05.867 10895 11004 I DOTNET  : 	[PASS] Non_Existent_Key_Returns_Null
      06-27 10:53:05.901 10895 11009 I DOTNET  : 	[PASS] Saves_Same_Key_Twice
      06-27 10:53:05.929 10895 11014 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:53:05.956 10895 11019 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:53:05.984 10895 11024 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:53:06.016 10895 11029 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:53:06.040 10895 11034 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:53:06.067 10895 11039 I DOTNET  : 	[PASS] Saves_And_Loads
      06-27 10:53:07.986 10895 11069 I DOTNET  : 	[PASS] Set_Get_Async_MultipleTimes
      06-27 10:53:08.594 10895 11086 I DOTNET  : 	[PASS] Fix_Corrupt_Data
      06-27 10:53:10.320 10895 11086 I DOTNET  : 	[PASS] Set_Get_Wait_MultipleTimes
      06-27 10:53:10.375 10895 11086 I DOTNET  : 	[PASS] Remove_Key
      06-27 10:53:10.407 10895 11086 I DOTNET  : 	[PASS] Remove_Key
      06-27 10:53:10.408 10895 11086 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.SecureStorage_Tests 7.9261638 ms
      06-27 10:53:10.408 10895 11086 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Shared.Android_FileProvider_Tests
      06-27 10:53:10.439 10895 11086 I DOTNET  : 	[PASS] Get_Existing_Internal_Cache_Shareable_Uri
      06-27 10:53:10.445 10895 11086 I DOTNET  : 	[PASS] Get_Existing_Internal_Cache_Shareable_Uri
      06-27 10:53:10.449 10895 11086 I DOTNET  : 	[PASS] No_Media_Fails_Get_External_Cache_Shareable_Uri
      06-27 10:53:10.482 10895 11086 I DOTNET  : 	[PASS] Get_Shareable_Uri
      06-27 10:53:10.492 10895 11086 I DOTNET  : 	[PASS] Get_Shareable_Uri
      06-27 10:53:10.495 10895 11086 I DOTNET  : 	[PASS] Get_Existing_External_Shareable_Uri
      06-27 10:53:10.500 10895 11086 I DOTNET  : 	[PASS] Get_Existing_External_Shareable_Uri
      06-27 10:53:10.502 10895 11086 I DOTNET  : 	[PASS] Get_Existing_External_Cache_Shareable_Uri
      06-27 10:53:10.506 10895 11086 I DOTNET  : 	[PASS] Get_Existing_External_Cache_Shareable_Uri
      06-27 10:53:10.510 10895 11086 I DOTNET  : 	[PASS] Get_External_Cache_Shareable_Uri
      06-27 10:53:10.510 10895 11086 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Shared.Android_FileProvider_Tests 0.0983568 ms
      06-27 10:53:10.510 10895 11086 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.FileSystem_Tests
      06-27 10:53:10.510 10895 11086 I DOTNET  : 	[PASS] AppDataDirectory_Is_Valid
      06-27 10:53:10.524 10895 11091 I DOTNET  : 	[PASS] CheckFileResultWithFilePath
      06-27 10:53:10.524 10895 11091 I DOTNET  : 	[PASS] CacheDirectory_Is_Valid
      06-27 10:53:10.533 10895 11091 I DOTNET  : 	[PASS] CheckFileResultOpenReadAsyncMultipleTimes
      06-27 10:53:10.536 10895 11091 I DOTNET  : 	[PASS] OpenAppPackageFileAsync_Can_Load_File
      06-27 10:53:10.537 10895 11091 I DOTNET  : 	[PASS] OpenAppPackageFileAsync_Can_Load_File
      06-27 10:53:10.541 10895 11091 I DOTNET  : 	[PASS] OpenAppPackageFileAsync_Throws_If_File_Is_Not_Found
      06-27 10:53:10.541 10895 11091 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.FileSystem_Tests 0.0255523 ms
      06-27 10:53:10.541 10895 11091 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.AppActions_Tests
      06-27 10:53:10.572 10895 11091 I DOTNET  : 	[PASS] GetSetItems
      06-27 10:53:10.572 10895 11091 I DOTNET  : 	[PASS] IsSupported
      06-27 10:53:10.572 10895 11091 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.AppActions_Tests 0.0308267 ms
      06-27 10:53:10.572 10895 11091 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.HapticFeedback_Tests
      06-27 10:53:10.589 10895 11091 I DOTNET  : 	[PASS] LongPress
      06-27 10:53:10.590 10895 11091 I DOTNET  : 	[PASS] Click
      06-27 10:53:10.590 10895 11091 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.HapticFeedback_Tests 0.0173204 ms
      06-27 10:53:10.590 10895 11091 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Share_Tests
      06-27 10:53:10.592 10895 11091 I DOTNET  : 	[PASS] Share_NullShareMultipleFilesRequest
      06-27 10:53:10.593 10895 11091 I DOTNET  : 	[PASS] Share_ShareMultipleFilesRequestWithEmptyFilesList
      06-27 10:53:10.593 10895 11091 I DOTNET  : 	[PASS] Share_ShareMultipleFilesRequestWithInvalidFilesList
      06-27 10:53:10.594 10895 11091 I DOTNET  : 	[PASS] Share_NullShareTextRequest
      06-27 10:53:10.603 10895 11091 I DOTNET  : 	[PASS] Share_SingleFileIntent_HasClipData
      06-27 10:53:10.606 10895 11091 I DOTNET  : 	[PASS] Share_ShareFileRequestWithInvalidFile
      06-27 10:53:10.607 10895 11091 I DOTNET  : 	[PASS] Share_FiletWithNullFilePath
      06-27 10:53:10.614 10895 11091 I DOTNET  : 	[PASS] Share_MultipleFilesIntent_HasClipData
      06-27 10:53:10.615 10895 11091 I DOTNET  : 	[PASS] Share_NullShareFileRequest
      06-27 10:53:10.615 10895 11091 I DOTNET  : 	[PASS] Share_FiletWithInvalidFilePath
      06-27 10:53:10.615 10895 11091 I DOTNET  : 	[PASS] Share_ShareTextRequestWithInvalidTextAndUri
      06-27 10:53:10.615 10895 11091 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Share_Tests 0.0231955 ms
      06-27 10:53:10.616 10895 11091 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.DeviceDisplay_Tests
      06-27 10:53:10.618 10895 11091 I DOTNET  : 	[PASS] Screen_Metrics_Are_Not_Null
      06-27 10:53:10.618 10895 11091 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.DeviceDisplay_Tests 0.0020359 ms
      06-27 10:53:10.618 10895 11091 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Permissions_Tests
      06-27 10:53:10.623 10895 11091 I DOTNET  : 	[PASS] Check_Status
      06-27 10:53:10.634 10895 11091 I DOTNET  : 	[PASS] Check_Status
      06-27 10:53:10.635 10895 11091 I DOTNET  : 	[PASS] Ensure_Declared
      06-27 10:53:10.636 10895 11091 I DOTNET  : 	[PASS] Ensure_Declared
      06-27 10:53:10.638 10895 11091 I DOTNET  : 	[PASS] Request
      06-27 10:53:10.638 10895 11091 I DOTNET  : 	[PASS] Request
      06-27 10:53:10.640 10895 11091 I DOTNET  : 	[PASS] StorageAndroid13AlwaysGranted
      06-27 10:53:10.640 10895 11091 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Permissions_Tests 0.0197748 ms
      06-27 10:53:10.640 10895 11091 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Barometer_Tests
      06-27 10:53:10.641 10895 11091 I DOTNET  : 	[PASS] IsSupported
      06-27 10:53:10.714 10895 11098 I DOTNET  : 	[PASS] Monitor
      06-27 10:53:10.779 10895 11103 I DOTNET  : 	[PASS] IsMonitoring
      06-27 10:53:10.785 10895 11108 I DOTNET  : 	[PASS] Stop_Monitor
      06-27 10:53:10.785 10895 11108 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Barometer_Tests 0.1434026 ms
      06-27 10:53:10.785 10895 11108 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.AppInfo_Tests
      06-27 10:53:10.786 10895 11108 I DOTNET  : 	[PASS] App_Versions_Are_Correct
      06-27 10:53:10.790 10895 11108 I DOTNET  : 	[PASS] App_RequestedLayoutDirection_Is_Correct
      06-27 10:53:10.790 10895 11108 I DOTNET  : 	[PASS] AppPackageName_Is_Correct
      06-27 10:53:10.791 10895 11108 I DOTNET  : 	[PASS] AppName_Is_Correct
      06-27 10:53:10.791 10895 11108 I DOTNET  : 	[PASS] App_Build_Is_Correct
      06-27 10:53:10.794 10895 11108 I DOTNET  : 	[PASS] App_Theme_Is_Correct
      06-27 10:53:10.794 10895 11108 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.AppInfo_Tests 0.0078138 ms
      06-27 10:53:10.794 10895 11108 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Screenshot_Tests
      06-27 10:53:11.004 10895 11112 I DOTNET  : 	[PASS] CaptureAsync
      06-27 10:53:11.212 10895 11116 I DOTNET  : 	[PASS] GetPngScreenshot
      06-27 10:53:11.423 10895 11132 I DOTNET  : 	[PASS] GetJpegScreenshot
      06-27 10:53:11.741 10895 11154 I DOTNET  : 	[PASS] CaptureStaticAsync
      06-27 10:53:11.741 10895 11154 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Screenshot_Tests 0.8792264 ms
      06-27 10:53:11.741 10895 11154 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Maps_Tests
      06-27 10:53:11.762 10895 11154 I DOTNET  : 	[PASS] LaunchMap_NullOptionsLocation
      06-27 10:53:11.767 10895 11154 I DOTNET  : 	[PASS] LaunchMap_NullPlacemark
      06-27 10:53:11.772 10895 11154 I DOTNET  : 	[PASS] LaunchMap_NullLocation
      06-27 10:53:11.773 10895 11154 I DOTNET  : 	[PASS] LaunchMap_NullOptionsPlacemark
      06-27 10:53:11.773 10895 11154 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Maps_Tests 0.0299840 ms
      06-27 10:53:11.773 10895 11154 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Connectivity_Tests
      06-27 10:53:11.791 10895 11154 I DOTNET  : 	[PASS] Network_Access
      06-27 10:53:12.869 10895 11255 I DOTNET  : 	[PASS] ConnectivityChanged_Does_Not_Crash
      06-27 10:53:12.884 10895 11255 I DOTNET  : 	[PASS] Connection_Profiles
      06-27 10:53:12.889 10895 11255 I DOTNET  : 	[PASS] Test
      06-27 10:53:12.901 10895 11255 I DOTNET  : 	[PASS] Distict_Connection_Profiles
      06-27 10:53:12.901 10895 11255 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Connectivity_Tests 1.1179338 ms
      06-27 10:53:12.901 10895 11255 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Accelerometer_Tests
      06-27 10:53:12.958 10895 11261 I DOTNET  : 	[PASS] Stop_Monitor
      06-27 10:53:12.964 10895 11261 I DOTNET  : 	[PASS] IsMonitoring
      06-27 10:53:12.964 10895 11261 I DOTNET  : 	[PASS] IsSupported
      06-27 10:53:12.978 10895 11266 I DOTNET  : 	[PASS] Monitor
      06-27 10:53:12.978 10895 11266 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Accelerometer_Tests 0.0737711 ms
      06-27 10:53:12.978 10895 11266 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Android_Geolocation_Tests
      06-27 10:53:12.993 10895 11266 I DOTNET  : 	[PASS] ToLocation_MslAltitude_UsesGeoidReferenceSystem
      06-27 10:53:12.994 10895 11266 I DOTNET  : 	[PASS] ToLocation_EllipsoidalAltitude_UsesEllipsoidReferenceSystem
      06-27 10:53:12.994 10895 11266 I DOTNET  : 	[PASS] ToLocation_MslAltitudeWithoutMslAccuracy_ReportsNullVerticalAccuracy
      06-27 10:53:12.994 10895 11266 I DOTNET  : 	[PASS] LocationCopyConstructor_PreservesAltitudeReferenceSystem
      06-27 10:53:12.995 10895 11266 I DOTNET  : 	[PASS] ToLocation_NoAltitude_UsesUnspecifiedReferenceSystem
      06-27 10:53:12.995 10895 11266 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Android_Geolocation_Tests 0.0155517 ms
      06-27 10:53:12.995 10895 11266 I DOTNET  : Test collection for Microsoft.Maui.Essentials.DeviceTests.Magnetometer_Tests
      06-27 10:53:13.015 10895 11272 I DOTNET  : 	[PASS] Stop_Monitor
      06-27 10:53:13.027 10895 11277 I DOTNET  : 	[PASS] Monitor
      06-27 10:53:13.048 10895 11283 I DOTNET  : 	[PASS] IsMonitoring
      06-27 10:53:13.048 10895 11283 I DOTNET  : 	[PASS] IsSupported
      06-27 10:53:13.048 10895 11283 I DOTNET  : Microsoft.Maui.Essentials.DeviceTests.Magnetometer_Tests 0.0482184 ms
      06-27 10:53:13.086 10895 10993 I DOTNET  : Xml file was written to the provided writer.
      06-27 10:53:13.086 10895 10993 I DOTNET  : Tests run: 307 Passed: 277 Inconclusive: 0 Failed: 0 Ignored: 30
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervm6n5x7",
        "exitCode": 0,
        "exitCodeName": "SUCCESS",
        "platform": "android",
        "instrumentationExitCode": 0,
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "testResults.xml",
            "type": "test-results"
          },
          {
            "name": "adb-logcat-com.microsoft.maui.essentials.devicetests-default.log",
            "type": "logcat"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.essentials.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.essentials.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.essentials.devicetests
XHarness exit code: 0
  Tests completed successfully

📁 Fix files reverted (1 files)
  • src/Essentials/src/Platform/ActivityStateManager.android.cs

📱 UI Tests — Essentials

Detected UI test categories: Essentials

⏭️ Deep UI tests — 0 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above). 1 category reported 0 tests.

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Essentials 0 tests
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #36035 - ActivityStateManager listener leak on Android (inferred from PR test comments; GitHub metadata unavailable)
PR: #36161 - Fix repeated ActivityStateManager lifecycle callback registration on Android (inferred from local diff)
Platforms Affected: Android
Files Changed: 1 implementation, 1 test

Key Findings

  • Local PR diff changes ActivityStateManagerImplementation.Init(Application) so repeated initialization does not add multiple Android Application.IActivityLifecycleCallbacks registrations.
  • Added Android Essentials device tests verify repeated Init keeps one listener and emits one ActivityStateChanged notification per lifecycle event.
  • GitHub CLI is unauthenticated in this environment, so issue/PR body, comments, reviews, and required CI checks could not be fetched; local gate artifacts show the regression tests fail without the fix and pass with it.
  • Regression cross-reference artifact reports no detected regression risks.

Code Review Summary

Verdict: NEEDS_DISCUSSION
Confidence: low
Errors: 0 | Warnings: 1 | Suggestions: 2

Key code review findings:

  • src/Essentials/test/DeviceTests/Tests/ActivityStateManager_Tests.cs:72 registers a real app lifecycle callback in the test, so the event-count assertion could be sensitive to real lifecycle callbacks during the test window.
  • src/Essentials/test/DeviceTests/Tests/ActivityStateManager_Tests.cs:64 could explicitly guard null CurrentActivity for clearer diagnostics.
  • src/Essentials/src/Platform/ActivityStateManager.android.cs:76-82 is sequentially correct; a lock/atomic variant would also close a theoretical concurrent Init race.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #36161 Simple idempotency guard: return from Init(Application) when lifecycleListener is already initialized. ✅ PASSED (Gate) src/Essentials/src/Platform/ActivityStateManager.android.cs, src/Essentials/test/DeviceTests/Tests/ActivityStateManager_Tests.cs Original PR fix; local gate passed.

🔬 Code Review — Deep Analysis

Code Review — PR #36161

Independent Assessment

What this changes: Android ActivityStateManagerImplementation.Init(Application) now returns early when lifecycleListener is already initialized, so repeated Platform.Init(...) / activity recreation does not register additional Application.IActivityLifecycleCallbacks instances. The PR also adds Android Essentials device tests that instantiate a fresh manager, call Init repeatedly, and verify the same listener is retained and ActivityStateChanged fires once when distinct registered callbacks are simulated.

Inferred motivation: The previous implementation overwrote lifecycleListener and called Application.RegisterActivityLifecycleCallbacks(...) on every init. Since Android callback registration is additive and the old callbacks were never unregistered, each activity lifecycle event could be observed multiple times and retain extra manager/listener instances for the process lifetime.

Reconciliation with PR Narrative

Author claims: GitHub PR title/body/comments could not be read because gh is unauthenticated in this environment. Local test comments reference GitHub issue #36035 and describe an ActivityStateManager listener leak caused by repeated Init(Application) calls during Activity recreation.

Agreement/disagreement: The local diff matches that inferred bug: src/Essentials/src/Platform/ActivityStateManager.android.cs:76-82 makes registration idempotent, while Init(Activity, Bundle?) still updates lifecycleListener!.Activity = activity after the guarded app init (ActivityStateManager.android.cs:96-97). Local gate artifacts confirm the new tests fail without the fix and pass with it.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Unavailable — gh pr view, pull review comments, and issue comments all failed with gh auth login / missing GH_TOKEN. GitHub metadata Unknown Local artifacts under CustomAgentLogsTmp/PRState/36161/PRAgent/ contain no prior ❌ Error findings; only the local expert-review findings created during this run were found.

Blast Radius Assessment

  • Runs for all instances: Yes, Android app/activity initialization is a process-wide Essentials/MAUI startup path. The change only affects repeated initialization after the first listener has been registered.
  • Startup impact: Yes, the code executes during Platform.Init(Application) / Platform.Init(Activity, Bundle?). The new branch is a null check and early return.
  • Static/shared state: No new static state. Existing ActivityStateManager.Default remains singleton-backed; existing one-process Application lifecycle callback remains intentionally registered for the process lifetime.
  • Platform scope: Android-only (.android.cs) with Android-only device tests guarded by #if __ANDROID__.

CI Status

  • Required-check result: unavailable. gh pr checks 36161 --repo dotnet/maui --required failed because the GitHub CLI is unauthenticated.
  • Classification: undetermined for required CI. Local gate/regression artifacts are favorable: gate PASSED; Android ActivityStateManager tests fail without the fix and pass with the fix; regression check CLEAN with zero risks.
  • Action taken: Recorded GitHub/CI metadata as unavailable; capped confidence at low and did not use LGTM because required CI could not be verified.

Findings

❌ Error — None

No blocking correctness issue was found in the production fix.

⚠️ Warning — Device test registers a real app lifecycle callback that can make the assertion sensitive to real lifecycle events

src/Essentials/test/DeviceTests/Tests/ActivityStateManager_Tests.cs:72 calls manager.Init(app) on the real Android Application after subscribing to manager.ActivityStateChanged at line 69. If the test-runner activity receives a real lifecycle callback between registration and Assert.Equal(1, invocations) at line 87, invocations can include both the real callback and the manual OnActivityResumed simulation, producing a flaky count. This is not a production-code blocker and the local gate passed, but a baseline/delta assertion or subscribing only after setup would make the regression test more isolated.

💡 Suggestion — Guard the test's current activity assumption

src/Essentials/test/DeviceTests/Tests/ActivityStateManager_Tests.cs:64 assigns MauiPlatform.CurrentActivity and passes it to OnActivityResumed at line 85. The current Android device-test run has an activity, but an explicit throw if it is null would preserve the non-null API contract and make failures easier to diagnose.

💡 Suggestion — Consider whether Init(Application) should be race-free

src/Essentials/src/Platform/ActivityStateManager.android.cs:76-82 uses a non-atomic null-check-then-assign. Normal MAUI/Android startup calls this from lifecycle/main-thread paths, so this is acceptable in practice, but a lock/Interlocked.CompareExchange would fully prevent duplicate registration if third-party code invoked the public interface concurrently.

Failure-Mode Probing

  • Repeated Init(Activity, Bundle?) after rotation/recreation: The guard skips re-registering callbacks, then line 97 still assigns the latest Activity, so GetCurrentActivity() remains current while duplicate callbacks stop accumulating.
  • App-level Init(Application) followed by later Activity lifecycle callbacks: The first call still registers one ActivityLifecycleContextListener; its OnActivityCreated/OnActivityResumed methods continue updating the weak current-activity reference.
  • Existing consumers of Platform.ActivityStateChanged and WaitForActivityAsync: They receive a single event per native callback instead of N duplicated events. WaitForActivityAsync still subscribes/unsubscribes its handler in finally and benefits from reduced duplicate completion attempts.
  • Null/default startup values: Before any activity is known, GetCurrentActivity() can still return null as before; the fix does not introduce a new null dereference.
  • Handler disconnect/reconnect: Not a handler change. The app lifecycle callback remains process-scoped; this PR reduces, rather than increases, long-lived listener accumulation.
  • Test hermeticity: The new tests correctly reproduce the leak without the fix, but because they register with the real Application, they are not fully isolated from real lifecycle dispatch.

Verdict: NEEDS_DISCUSSION

Confidence: low (Android startup/platform lifecycle path plus required CI unavailable via unauthenticated gh; local gate evidence is strong but not a substitute for required checks.)
Summary: The production fix is small and sound: it makes ActivityStateManager listener registration idempotent without breaking current-activity updates. I found no blocking production issue. The main residual concern is test isolation around real Android lifecycle callbacks, and the merge verdict cannot be LGTM because required CI/GitHub metadata is unavailable in this environment.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 maui-expert-reviewer/code-review loop Race-free locked initialization with rollback around ActivityStateManager listener registration. ✅ PASS 1 file More robust than PR fix for theoretical concurrent Init(Application) calls; 0 self-review findings; more complex than PR.
PR PR #36161 Simple idempotency guard in Init(Application) when lifecycleListener is non-null. ✅ PASSED (Gate) 2 files Original PR; simpler and gate-proven.

Cross-Pollination

Model Round New Ideas? Details
gpt-5.5 + maui-expert-reviewer 1 Yes Proposed race-free locked initialization with rollback. Candidate passed all Android ActivityStateManager tests.

Exhausted: No — stopped because Candidate #1 passed all targeted Android tests and is demonstrably more robust than the PR fix for concurrent initialization and registration-failure rollback. Further variants would be tradeoff exploration rather than necessary failure-driven iteration.
Selected Fix: Candidate #1 — It preserves the PR behavior while adding race-safety and rollback. The tradeoff is added complexity; if the team values simplicity over theoretical concurrency hardening, the PR fix remains acceptable.


🏁 Report — Final Recommendation

Comparative Report — PR #36161

Candidates compared

Rank Candidate Regression result Review result Assessment
1 pr-plus-reviewer ✅ Inherits gate-passing PR production fix; reviewer change is test cleanup only ✅ Resolves the current expert inline finding Best balance. Keeps the simple idempotency production fix, preserves the regression coverage, and cleans up real Android Application lifecycle callbacks registered by the tests.
2 try-fix-1 ✅ PASS — Run-DeviceTests.ps1 -Project Essentials -Platform android -TestFilter "Category=ActivityStateManager" reported XHarness exit code 0 and Tests run: 307 Passed: 277 Inconclusive: 0 Failed: 0 Ignored: 30 ✅ Self-review recorded 0 findings Strong production alternative. Adds a lock and rollback around listener registration, which is more robust for theoretical concurrent Init(Application) calls, but also adds complexity for a path that normally runs on Android lifecycle/main-thread startup. It does not address the current expert finding against the PR's test callbacks unless combined with the reviewer cleanup.
3 pr ✅ Gate passed — tests fail without fix and pass with fix ⚠️ One moderate expert finding in tests Production fix is sound and minimal, but the raw test code leaves process-wide lifecycle callbacks registered after the tests complete.

No candidate with a failed regression result was ranked above a passing candidate.

Key tradeoffs

pr fixes the actual production leak with a small early-return guard. The main residual issue is test hermeticity: the newly registered real Android lifecycle callbacks should be unregistered so the test process does not retain listeners or receive extra callbacks later.

try-fix-1 is production-hardened beyond the PR by serializing lifecycleListener initialization and rolling back the field if Android callback registration throws. That is technically stronger if concurrent/off-main Init(Application) calls are considered in scope, but the observed bug and normal MAUI call path are sequential Android lifecycle initialization. The extra lock is therefore useful but not necessary to fix #36035.

pr-plus-reviewer is the safest merge recommendation because it keeps the author's simple production fix and applies the only current expert actionable feedback. It improves the submitted PR without introducing new production synchronization complexity.

Winning candidate

Winner: pr-plus-reviewer

Rationale: It preserves the gate-proven PR behavior, fixes the expert-review test leak, and keeps the production change narrowly scoped to idempotent Android lifecycle callback registration. try-fix-1 remains a valid alternative if maintainers explicitly want to harden Init(Application) for concurrent callers, but that is a broader tradeoff than required for the regression.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 27, 2026
@PureWeen

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Tests Failure Analysis

@Shalini-Ashokan — test-failure review results are available based on commit b31f961.
To request a fresh review after new comments, commits, or CI runs, comment /review tests.

Overall Needs human investigation Failures 12 Baseline 0 on base Platform windows

Test Failure Review: Needs human investigation - click to expand

Overall verdict: Needs human investigation

All 12 extracted failures are Windows Helix device-test work items that exited non-zero while recording zero named test failures; the pattern (HybridWebView timeout, crash dump, or bare non-zero exit) is consistent with a Windows runner environment issue unrelated to the Android lifecycle change this PR makes, but deterministic attribution is unavailable and the maui-pr-uitests run was canceled mid-flight leaving its sample-app build legs unresolved.

Coverage: 50 checks · 43 passing · 5 failing · 2 pending · 0 inaccessible · 1 unmapped · 0 unexplained build legs · 4 unaccounted failing checks · 4 aborted failing checks · 5 canceled-build checks · 9 device-test unverified · 12 unattributed · 0 regressed-vs-base. Deterministic ceiling: Needs human investigation — 2 checks pending (maui-pr-uitests, Build Analysis); 1 failing check has no inspectable AzDO evidence (agent); 4 unaccounted failing checks (cancelled maui-pr-uitests sample-app build legs); 12 unattributed failures (Windows device-test work items); 4 aborted failing checks; 5 canceled-build checks; 9 device-test checks unverified.

Failure Verdict On base? Evidence
device-test work item incomplete (Controls.DeviceTests-packaged) Likely unrelated no indeterminate; Windows only; 713 passed / 0 failed; HybridWebView timeout after 480 s + crash dump — PR edits Android-only ActivityStateManager, no HybridWebView or Windows code touched; work item
device-test work item incomplete (Controls.DeviceTests-unpackaged) Likely unrelated no indeterminate; Windows only; 713 passed / 0 failed; HybridWebView timeout after 480 s + crash dump — same reasoning as packaged; work item
device-test work item incomplete (Core.DeviceTests-packaged) Likely unrelated no indeterminate; Windows only; 1975 passed / 0 failed / 111 skipped; bare non-zero ExitCode with no named failure — PR is scoped to Android; work item
device-test work item incomplete (Core.DeviceTests-unpackaged) Likely unrelated no indeterminate; Windows only; 1975 passed / 0 failed / 111 skipped; bare non-zero ExitCode with no named failure; work item
device-test work item incomplete (Essentials.AI.DeviceTests-packaged) Likely unrelated no indeterminate; Windows only; 1 passed / 0 failed; bare non-zero ExitCode; work item
device-test work item incomplete (Essentials.AI.DeviceTests-unpackaged) Likely unrelated no indeterminate; Windows only; 1 passed / 0 failed; bare non-zero ExitCode; work item
device-test work item incomplete (Essentials.DeviceTests-packaged) Likely unrelated no indeterminate; Windows only; 238 passed / 0 failed / 14 skipped; bare non-zero ExitCode; work item
device-test work item incomplete (Essentials.DeviceTests-unpackaged) Likely unrelated no indeterminate; Windows only; 238 passed / 0 failed / 14 skipped; bare non-zero ExitCode; work item
device-test work item incomplete (Graphics.DeviceTests-packaged) Likely unrelated no indeterminate; Windows only; 33 passed / 0 failed; bare non-zero ExitCode; work item
device-test work item incomplete (Graphics.DeviceTests-unpackaged) Likely unrelated no indeterminate; Windows only; 33 passed / 0 failed; bare non-zero ExitCode; work item
device-test work item incomplete (MauiBlazorWebView.DeviceTests-packaged) Likely unrelated no indeterminate; Windows only; 17 passed / 0 failed / 4 skipped; bare non-zero ExitCode; work item
device-test work item incomplete (MauiBlazorWebView.DeviceTests-unpackaged) Likely unrelated no indeterminate; Windows only; 17 passed / 0 failed / 4 skipped; bare non-zero ExitCode; work item

Recommended action

Wait for the queued maui-pr-uitests run (build 1483275) to finish; a human reviewer should confirm whether the Windows Helix device-test work item non-clean exits (all 12 show 0 named failures, consistent with a runner environment or HybridWebView timeout issue unrelated to the Android lifecycle change) are pre-existing before merging.

Evidence details

PR scope: 2 files changed (1 test file), Android-only platform label, area-core-lifecycle. The fix targets ActivityStateManager lifecycle callback leak on Activity recreation — no Windows, HybridWebView, or cross-platform paths modified.

Device test build: maui-pr-devicetests build 1483277 — result: succeeded. Helix job 6474c09d (Run DeviceTests Windows) had 12 failed work items (13 work items total, not finished). All other Helix jobs (Android CoreCLR, Android Mono, iOS Mono x2, MacCatalyst Mono x2) reported 0 failed work items and are marked confirmed clean.

Windows device-test pattern: Every work item in the Windows Helix job exited non-zero but named 0 failed tests. The Controls items show "Timeout waiting for HybridWebView test results after 480 seconds | crash dump present" — a known flaky pattern on Windows Helix runners unrelated to Android lifecycle code. The remaining 10 work items show bare non-zero ExitCode with no detail, also consistent with runner-level failures.

UI test cancellation: maui-pr-uitests build 1483275 was canceled (result: canceled). The 4 sample-app build legs (CoreCLR, Material3, Standard, Windows) all have CANCELLED conclusion. A fresh maui-pr-uitests run is currently QUEUED — once it completes, the UI test picture will be clearer.

Baseline: maui-pr-devicetests base build 1484479 had 1 baseline failure; none of the 12 PR failures matched it exactly (0 of 12 also on base). Baseline comparison is therefore inconclusive for these work-item-level failures.

Unmapped check: GitHub Actions agent check (FAILURE) has no inspectable AzDO build; its details URL should be read directly.

Limitations: AzDO access was unauthenticated; Helix work item detail was read via the public API. Device-test verified status could not be positively confirmed (XHarness exits 0 on some failure modes), so 9 green device-test checks remain unverified by the gate.

@kubaflo
kubaflo changed the base branch from main to inflight/current June 28, 2026 18:41
@kubaflo
kubaflo merged commit b49fec0 into dotnet:inflight/current Jun 28, 2026
44 of 51 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 28, 2026
PureWeen added a commit that referenced this pull request Jun 29, 2026
… crash defense

Addresses the Copilot PR reviewer's L928 finding plus two bundled polish items on
the /review tests gather script.

L928 over-cap (Get-ConsoleFailureReason): the single regex that captured a
human-readable failure reason ALSO set isIncomplete, and it matched
"Test execution completed with exit code: N" -- which
eng/devices/run-windows-devicetests.cmd:481 echoes UNCONDITIONALLY for every
non-zero run (the cmd only ever does `exit /b 0|1`). Every failed Windows work item
was therefore force-capped to "needs human investigation", defeating the Phase 2
intent of letting a cleanly-completed NAMED failure flow through to base/known-issue
attribution. Split into a broad $reasonRegex (capture, unchanged) and a narrow
$incompleteRegex that fires ONLY on genuine non-clean-finish markers
(timeout/hang/crash/kill/wipeout), excluding the unconditional exit-code line and a
bare "[FAIL] <test>". This is safe: a genuinely incomplete Windows run still trips
isIncomplete via an INDEPENDENT marker (:wait_for_result emits
"[FAIL] Timeout waiting for <cat> test results", a total wipeout prints
"All test processes may have crashed", crashes/dumps print their own markers).

Negative-ExitCode crash defense (new -WorkItemCrashed param): to close the residual
infra-kill window (a signal-killed work item could flush a partial named TRX before
dying), New-DeviceWorkItemFailureRecords now forces the incomplete cap when the Helix
work item reports a NEGATIVE ExitCode. The runners only ever exit with a small
non-negative code, so a negative code (e.g. -4, observed live on #36161) is the
signature of an abnormal Helix/OS kill. Computed at the call site from the
authoritative /workitems list ExitCode.

Polish: reword the misleading "non-fatal on its own" job-detail comment to state that
a failed detail read forces unverified (caps, never false-green); rename the
mis-named sawFailCount -> sawWorkItemCount.

Tests: +5 Pester cases (clean Windows named-failure run is no longer over-capped;
genuine timeout/wipeout still caps; -WorkItemCrashed caps; default opt-out unaffected).
41/41 pass; AST parse clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 3, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo pushed a commit that referenced this pull request Jul 6, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
kubaflo pushed a commit that referenced this pull request Jul 10, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
kubaflo pushed a commit that referenced this pull request Jul 22, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
kubaflo pushed a commit that referenced this pull request Jul 28, 2026
…ivity recreation (#36161)

<!-- Please let the below note in for people that find this PR -->
> [!NOTE]
> Are you waiting for the changes in this PR to be merged?
> It would be very helpful if you could [test the resulting
artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->


### Issue Details
Every time an Android Activity is programmatically recreated (via
Activity.Recreate()), Platform.ActivityStateChanged fires more times
than expected. After 120 recreations, the event fires 61× more than it
should, causing serious performance problems.

### Root Cause
ActivityStateManager.Init(Application) was called on every Activity
recreation, and each call registered a new listener with Android without
removing the old one. Android keeps all registered listeners, so after N
recreations, N+1 listeners exist and every event fires N+1 times.

### Description of Change
Added a 2-line early-return guard in Init(Application):
 
if (lifecycleListener is not null)
     return;
 
This ensures the listener is registered only once on the first call. All
subsequent calls (from Activity recreations) are safely skipped.

Validated the behavior in the following platforms
 
- [x] Android
- [ ] Windows
- [ ] iOS
- [ ] Mac
 
### Issues Fixed
  
Fixes #36035 

### Output  ScreenShot

|Before|After|
|--|--|
| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/5d8d5679-8285-4a7c-9bf2-5185e6f781b8"
/>| <img width="367" height="741" alt="image"
src="https://github.com/user-attachments/assets/15a01574-e0c2-4e2b-bcc3-e5e7d0d9ccd5"
/> |
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-core-lifecycle XPlat and Native UIApplicationDelegate/Activity/Window lifecycle events community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/android s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-gate-passed AI verified tests catch the bug (fail without fix, pass with fix) s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: ActivityStateManager leaks lifecycle callbacks and multiplies Platform.ActivityStateChanged events after Activity recreation

7 participants