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