6

我正在使用大量的对象(PO​​I)正在显示在MapControl上。我正在帮助自己使用MVVM Light来遵守MVVM方法的规则。Windows Phone 8.1 WinRT内存泄漏与ObservableCollection

由于我有义务在地图上显示每个对象,因此我必须使用MapItemsControl集合,而不是MapElements。 此集合绑定到相应的ViewModel中的ObservableCollection<PushpinViewModel>对象(Pushpins)。一切都按预期工作,直到这一点,当我想刷新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​​元素。它们被渲染,内存分配,一切都很好。现在我想清除集合,并添加另一个(在实际场景中不同)1000个元素。所以,我打电话给Clear()方法。但是..没有任何反应! Pushpins被清除,但不会调用​​的终结器!然后我再次添加1000个元素,并且我的内存使用量也增加了一倍。 你可以猜到接下来会发生什么。当我重复这个Clear() - Add()程序3-5次我的应用程序崩溃。

那么,这是什么问题?显然ObservableCollection在对Clear()执行完毕后,持有对​​对象的引用,因此它们不能被垃圾收集。当然,强制GC执行垃圾收集并不会有帮助(有时甚至会使情况变得更糟)。

它现在困扰了我2天,我尝试了很多不同的场景来尝试解决这个问题,但说实话,没有任何帮助。 只有一件事值不了 - 我不记得确切的情况,但是当我分配Pushpins = null,然后做了更多的事情时,VehiceViewModel被摧毁。但是这对我不起作用,因为我还记得在Clear()之后,我在地图上显示这些引脚时遇到了问题。

你有什么想法可以导致这种内存泄漏?我如何强制OC的成员销毁?也许OC有一些替代方案? 在此先感谢您的帮助!

编辑:

我做了一些测试用XAML地图控件 - https://xamlmapcontrol.codeplex.com/,并且结果是令人惊讶的。总体地图性能增加了> 1000个元素,比本地MapControl差,但是,如果我打电话Add() x1000,然后Clear(),然后Add() x1000,​​的终结器被称为!内存被释放,应用程序不会崩溃。所以微软的MapControl肯定有什么问题...

+0

这个问题可能要加载到形式都被缓存到内存中的位图。收集GC不会将其从缓存中删除。请参阅[这些](http://stackoverflow.com/questions/1684489/how-do-you-make-sure-wpf-releases-large-bitmapsource-from-memory)[相关](http://stackoverflow.com/questions/5530645/released-bitmapimages-used-as-image-control-source-memory-problem)questions。 – 2014-10-04 23:09:43

+0

感谢您的回答。我正在调查这种可能性,并同意这确实可能是一个原因。我检查了你的链接,不幸的是,这里发布的答案主要适用于WPF应用程序(我在WinRT - 通用应用程序中)。我设法从流中加载图片(作为异步方法,但它的工作原理) - 不幸的是它没有帮助。内存消耗更大:( – Malutek 2014-10-05 00:40:02

+1

我不能说你的具体问题,但我也有一些与MapItemsControl有关的问题,我的设置几乎完全相同 - 使用MVVM绑定到ObservableCollection。我导航离开地图到另一个视图,然后回到地图,并且来回地重复3-5次,我会(通常)得到一个“Access Violation”错误,我缩小到MapItemsControl。这种行为需要物品来源,并在地图上为我绘制图钉,请告诉我是否要我发布源代码。 – 2014-10-13 22:49:52

回答

8

好的,这是我所做的模拟MapItemsControl的行为。请注意,这是相当未经测试 - 它在我的应用程序中工作,但真的没有在其他地方尝试过。我从来没有测试RemoveItems函数,因为我的应用程序只是将项目添加到ObservableCollection并清除它们;它不会逐渐删除项目。

另请注意,它使用与其绑定的项目的哈希代码标记XAML图钉;这是它如何识别哪些图钉从地图中删除,如果集合更改。这可能不适合你的情况,但它似乎是有效的。

用法:

注:NumberedCircle是一个用户控制是简单地显示其内部的号码的红色圆圈;换成你想用作图钉的任何XAML控件。 Destinations是我的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), typeof(PushpinCollectionBehavior), 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 
    } 
} 
+0

感谢您的解决方案。 – Mitius 2014-11-03 14:34:11

+2

谢谢,2-3次重复后出现“Access Violation”错误,此解决方案帮助 – Alexandr 2015-01-31 21:38:19

+0

非常感谢,给我带来了一个麻烦。虽然我在MapItemsControl DataTemplate内部有一个命令出现问题,但在切换到您的代码后,它停止工作。任何想法为什么? – stambikk 2016-02-15 12:08:22