diff --git a/src/Uno.UI/UI/Xaml/Controls/ListViewBase/NativeListViewBase.Android.cs b/src/Uno.UI/UI/Xaml/Controls/ListViewBase/NativeListViewBase.Android.cs index 9d3f5a8a9fdb1a21597c132380958500e0bd6f9b..2ce9779ac1b229f77b823085cdb3917e2a43990a 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ListViewBase/NativeListViewBase.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ListViewBase/NativeListViewBase.Android.cs @@ -238,12 +238,32 @@ namespace Windows.UI.Xaml.Controls return NativeLayout.CanCurrentlyScrollVertically(direction); } + private bool _trackDetachedViews; + private readonly List _detachedViews = new(); + + internal void StartDetachedViewTracking() + => _trackDetachedViews = true; + + internal void StopDetachedViewTrackingAndNotifyPendingAsRecycled() + { + _trackDetachedViews = false; + + // This should be invoked only from the LV.CleanContainer() + // **BUT** the container is not Cleaned/Prepared by the LV on Android + // https://github.com/unoplatform/uno/issues/11957 + foreach (var detachedView in _detachedViews) + { + UIElement.PrepareForRecycle(detachedView.ItemView); + } + } + protected override void AttachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams) { var vh = GetChildViewHolder(child); if (vh != null) { vh.IsDetached = false; + _detachedViews.Remove(vh); } base.AttachViewToParent(child, index, layoutParams); } @@ -257,11 +277,11 @@ namespace Windows.UI.Xaml.Controls if (vh != null) { vh.IsDetached = true; - - // This should be invoked only from the LV.CleanContainer() - // **BUT** the container is not Cleaned/Prepared by the LV on Android - // https://github.com/unoplatform/uno/issues/11957 - UIElement.PrepareForRecycle(view); + if (_trackDetachedViews) + { + // Avoid memory leak by adding them only when needed + _detachedViews.Add(vh); + } } } base.DetachViewFromParent(index); @@ -273,6 +293,7 @@ namespace Windows.UI.Xaml.Controls if (vh != null) { vh.IsDetached = false; + _detachedViews.Remove(vh); } #if DEBUG if (!vh.IsDetachedPrivate) diff --git a/src/Uno.UI/UI/Xaml/Controls/ListViewBase/VirtualizingPanelLayout.Android.cs b/src/Uno.UI/UI/Xaml/Controls/ListViewBase/VirtualizingPanelLayout.Android.cs index 9c80e148d50ff28241045875a0dc47380b152c8b..99b8cff17a93cad50deb29805ef1ed687646ee85 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ListViewBase/VirtualizingPanelLayout.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ListViewBase/VirtualizingPanelLayout.Android.cs @@ -1252,6 +1252,8 @@ namespace Windows.UI.Xaml.Controls return; } + XamlParent?.NativePanel.StartDetachedViewTracking(); + var needsScrapOnMeasure = isMeasure && availableExtent > 0 && availableBreadth > 0 && ChildCount > 0; var updatedAfterCollectionChange = false; if (_isRecycleLayoutRequested) @@ -1302,6 +1304,8 @@ namespace Windows.UI.Xaml.Controls AssertValidState(); } + XamlParent?.NativePanel.StopDetachedViewTrackingAndNotifyPendingAsRecycled(); + if (!isMeasure) { // Update HorizontalScrollRange and VerticalScrollRange because they're used by the ScrollViewer to get ExtentWidth and ExtentHeight.