由于我有义务在地图上显示每个对象,我必须使用MapItemsControl集合,而不是MapElements集合.
该集合绑定到ObservableCollection< Pushpinviewmodel>对象(Pushpins)在相应的viewmodel中.当我想要刷新Pushpins时,一切都按预期工作.问题是内存泄漏.但首先,一些代码可视化问题:
XAML:
<maps:MapControl x:Name="Map" x:Uid="MapControl"> <maps:MapItemsControl ItemsSource="{Binding Pushpins}"> <maps:MapItemsControl.ItemTemplate> <DataTemplate> <Image Source="{Binding Image}"/> </DataTemplate> </maps:MapItemsControl.ItemTemplate> </maps:MapItemsControl>
Mainviewmodel:
public class Mainviewmodel : viewmodelBase { public RelayCommand AddCommand { get; set; } public RelayCommand ClearCommand { get; set; } public RelayCommand CollectCommand { get; set; } public ObservableCollection<Pushpinviewmodel> Pushpins { get; set; } /* Ctor,initialization of Pushpins and stuff like that */ private void Collect() { GC.Collect(2); GC.WaitForPendingFinalizers(); GC.Collect(2); PrintCurrentMemory(); } private void Clear() { Pushpins.Clear(); PrintCurrentMemory(); } private void Add() { for (int i = 0; i < 1000; i++) { Pushpins.Add(new Pushpinviewmodel()); } PrintCurrentMemory(); } private void PrintCurrentMemory() { Logger.Log(String.Format("Total Memory: {0}",GC.GetTotalMemory(true) / 1024.0)); } }
Pushpinviewmodel:
public class Pushpinviewmodel: viewmodelBase { public string Image { get { return "/Assets/SomeImage.png"; } } ~Pushpinviewmodel() { Logger.Log("This finalizer never gets called!"); } }
现在,请考虑以下方案.我添加了Pushpins集合1000个Pushpinviewmodel元素.它们被渲染,内存被分配,一切都很好.现在我想清除集合,并添加另一个(在真实场景中不同)1000个元素.所以,我调用了Clear()方法.但是……没有任何反应!图钉被清除,但Pushpinviewmodel的终结器未被调用!然后我再次添加1000个元素,我的内存使用量翻倍.
你可以猜到接下来会发生什么.当我重复这个Clear() – Add()程序3-5次我的应用程序崩溃.
那么,问题是什么?显然,ObservableCollection在对其执行了Clear()之后保持对Pushpinviewmodel对象的引用,因此它们不能被垃圾回收.当然,强制GC执行垃圾收集并没有帮助(有时甚至会使情况变得更糟).
现在困扰我2天了,我尝试了许多不同的场景来尝试克服这个问题,但说实话,没有任何帮助.
只有一件事物毫无价值 – 我不记得确切的情况,但是当我指定了Pushpins = null,然后做了更多的事情时,Vehiceviewmodel被摧毁了.但这对我不起作用,因为我还记得在Clear()之后我在地图上可视化这些引脚时遇到了问题.
你有什么想法会导致这种内存泄漏吗?我如何强迫OC的成员摧毁?也许OC有某种替代方案?
在此先感谢您的帮助!
编辑:
我使用XAML Map Control-https://xamlmapcontrol.codeplex.com/进行了一些测试,结果令人惊讶.添加了> 1000个元素的整体地图性能比原生MapControl差,但是,如果我调用Add()x1000,然后调用Clear(),然后添加()x1000,则Pushpinviewmodel的终结器调用!内存被释放,应用程序不会崩溃.所以微软的MapControl肯定有问题……
解决方法
另请注意,它使用绑定的项的哈希码标记XAML图钉;这是它如何识别集合更改时从地图中删除哪些图钉.这可能不适用于您的情况,但似乎是有效的.
用法:
注意:NumberedCircle是一个用户控件,它只是一个红色圆圈,在其中显示一个数字;替换为您想要用作图钉的任何XAML控件.目标是我的ObservableCollection对象,它具有Number属性(显示在图钉内)和Point属性(图钉位置).
<map:MapControl> <i:Interaction.Behaviors> <behaviors:PushpinCollectionBehavior ItemsSource="{Binding Path=Destinations}"> <behaviors:PushpinCollectionBehavior.ItemTemplate> <DataTemplate> <controls:NumberedCircle Number="{Binding Path=Number}" map:MapControl.Location="{Binding Path=Point}" /> </DataTemplate> </behaviors:PushpinCollectionBehavior.ItemTemplate> </behaviors:PushpinCollectionBehavior> </i:Interaction.Behaviors> </map:MapControl>
码:
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Xaml.Interactivity; using Windows.Devices.Geolocation; using Windows.Foundation; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls.Maps; namespace Foo.Behaviors { /// <summary> /// Behavior to draw pushpins on a map. This effectively replaces MapItemsControl,which is flaky as hell. /// </summary> public class PushpinCollectionBehavior : DependencyObject,IBehavior { #region IBehavior public DependencyObject AssociatedObject { get; private set; } public void Attach(Windows.UI.Xaml.DependencyObject associatedObject) { var mapControl = associatedObject as MapControl; if (mapControl == null) throw new ArgumentException("PushpinCollectionBehavior can be attached only to MapControl"); AssociatedObject = associatedObject; mapControl.Unloaded += MapControlUnloaded; } public void Detach() { var mapControl = AssociatedObject as MapControl; if (mapControl != null) mapControl.Unloaded -= MapControlUnloaded; } #endregion #region Dependency Properties /// <summary> /// The dependency property of the item that contains the pushpin locations. /// </summary> public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource",typeof(object),typeof(PushpinCollectionBehavior),new PropertyMetadata(null,OnItemsSourcePropertyChanged)); /// <summary> /// The item that contains the pushpin locations. /// </summary> public object ItemsSource { get { return GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty,value); } } /// <summary> /// Adds,moves,or removes the pushpin when the item source changes. /// </summary> private static void OnItemsSourcePropertyChanged(DependencyObject dependencyObject,DependencyPropertyChangedEventArgs e) { var behavior = dependencyObject as PushpinCollectionBehavior; var mapControl = behavior.AssociatedObject as MapControl; // add the items if (behavior.ItemsSource is IList) behavior.AddItems(behavior.ItemsSource as IList); else throw new Exception("PushpinCollectionBehavior needs an IList as the items source."); // subscribe to changes in the collection if (behavior.ItemsSource is INotifyCollectionChanged) { var items = behavior.ItemsSource as INotifyCollectionChanged; items.CollectionChanged += behavior.CollectionChanged; } } // <summary> /// The dependency property of the pushpin template. /// </summary> public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate",typeof(DataTemplate),new PropertyMetadata(null)); /// <summary> /// The pushpin template. /// </summary> public DataTemplate ItemTemplate { get { return (DataTemplate)GetValue(ItemTemplateProperty); } set { SetValue(ItemTemplateProperty,value); } } #endregion #region Events /// <summary> /// Adds or removes the items on the map. /// </summary> private void CollectionChanged(object sender,NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: AddItems(e.NewItems); break; case NotifyCollectionChangedAction.Remove: RemoveItems(e.OldItems); break; case NotifyCollectionChangedAction.Reset: ClearItems(); break; } } /// <summary> /// Removes the CollectionChanged event handler from the ItemsSource when the map is unloaded. /// </summary> void MapControlUnloaded(object sender,RoutedEventArgs e) { var items = ItemsSource as INotifyCollectionChanged; if (items != null) items.CollectionChanged -= CollectionChanged; } #endregion #region Private Functions /// <summary> /// Adds items to the map. /// </summary> private void AddItems(IList items) { var mapControl = AssociatedObject as MapControl; foreach (var item in items) { var templateInstance = ItemTemplate.LoadContent() as FrameworkElement; var hashCode = item.GetHashCode(); templateInstance.Tag = hashCode; templateInstance.DataContext = item; mapControl.Children.Add(templateInstance); Tags.Add(hashCode); } } /// <summary> /// Removes items from the map. /// </summary> private void RemoveItems(IList items) { var mapControl = AssociatedObject as MapControl; foreach (var item in items) { var hashCode = item.GetHashCode(); foreach (var child in mapControl.Children.Where(c => c is FrameworkElement)) { var frameworkElement = child as FrameworkElement; if (hashCode.Equals(frameworkElement.Tag)) { mapControl.Children.Remove(frameworkElement); continue; } } Tags.Remove(hashCode); } } /// <summary> /// Clears items from the map. /// </summary> private void ClearItems() { var mapControl = AssociatedObject as MapControl; foreach (var tag in Tags) { foreach (var child in mapControl.Children.Where(c => c is FrameworkElement)) { var frameworkElement = child as FrameworkElement; if (tag.Equals(frameworkElement.Tag)) { mapControl.Children.Remove(frameworkElement); continue; } } } Tags.Clear(); } #endregion #region Private Properties /// <summary> /// The object tags of the items this behavior has placed on the map. /// </summary> private List<int> Tags { get { if (_tags == null) _tags = new List<int>(); return _tags; } } private List<int> _tags; #endregion } }