2013-10-02 35 views
1

我有一個列表(簡單列表框),其中包含主 - 從基礎上的圖片(如果用戶單擊列表項目,則打開詳細信息頁面)。我遇到了相當有名的圖像內存泄漏問題,描述爲here,here,herehere帶有自動內存清理功能的圖片下載器

一種可能的方式是當導航從and clean themrun through all images

one of the threads,我發現更有趣的解決方案:它自動清理圖像,但這不適用於虛擬化(圖像丟失或混合,如果要添加私人字段來存儲ImageSource)。建議的修復方法是添加依賴項屬性。

但是我仍然面臨着同樣的問題:圖像在向下滾動並返回後混合起來。看起來像依賴屬性隨機更改,但我不能趕上他們正在改變的時刻。

public class SafePicture : ContentControl 
{ 
    public static readonly DependencyProperty SafePathProperty = 
     DependencyProperty.RegisterAttached(
      "SafePath", 
      typeof(string), 
      typeof(SafePicture), 
      new PropertyMetadata(OnSourceWithCustomRefererChanged)); 

    public string SafePath 
    { 
     get { return (string)GetValue(SafePathProperty); } 
     set { SetValue(SafePathProperty, value); } 
    } 

    private static void OnSourceWithCustomRefererChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 
    { 
     if (e.NewValue == null) // New value here 
      return; 
    } 


    public SafePicture() 
    { 
     Content = new Image(); 
     Loaded += OnLoaded; 
     Unloaded += OnUnloaded; 
    } 

    private void OnLoaded(object _sender, RoutedEventArgs _routedEventArgs) 
    { 
     var image = Content as Image; 

     if (image == null) 
      return; 

     var path = (string)GetValue(SafePathProperty); // Also, tried SafePath (debugger cant catch setter and getter calls), but same result. 

     image.Source = null; 
     { 
      var request = WebRequest.Create(path) as HttpWebRequest; 
      request.AllowReadStreamBuffering = true; 
      request.BeginGetResponse(result => 
      { 
       try 
       { 
        Stream imageStream = request.EndGetResponse(result).GetResponseStream(); 
        DispatcherHelper.CheckBeginInvokeOnUI(() => 
        { 
         if (imageStream == null) 
         { 
          image.Source = new BitmapImage { UriSource = new Uri(path, UriKind.Relative) }; 
          return; 
         } 

         var bitmapImage = new BitmapImage(); 
         bitmapImage.CreateOptions = BitmapCreateOptions.BackgroundCreation; 
         bitmapImage.SetSource(imageStream); 
         image.Source = bitmapImage; 
        }); 
       } 
       catch (WebException) 
       { 
       } 
      }, null); 
     } 
    } 


    private void OnUnloaded(object sender, RoutedEventArgs e) 
    { 
     var image = Content as Image; 

     if (image == null) 
      return; 

     var bitmapImage = image.Source as BitmapImage; 
     if (bitmapImage != null) 
      bitmapImage.UriSource = null; 
     image.Source = null; 
    } 
} 

用法:

<wpExtensions:SafePicture SafePath="{Binding ImageUrl}"/> 

所以,乍一看,它工作正常,但如果向下滾動,並返回後,圖像的隨機變化。編輯:在這種情況下,現在,我只使用純ListBox,沒有虛擬化(但期望它在其他情況下)。

EDIT2:重現此問題的示例項目。我相信,它會在一段時間內包含解決方案:https://simca.codeplex.com/

回答

1

問題是,使用虛擬化時,用於每個項目的ui元素將被回收並重用於其他對象(以便包含圖像對象),因爲您是當您滾動得足夠快時,您將異步加載圖像,您將在圖像上設置一個位圖,這些位圖已被重新用於其他項目。
速戰速決的就是檢查路徑值仍然是相同的,如果不是,就返回因爲圖像已經被另一個對象重用:

... 
var request = WebRequest.Create(path) as HttpWebRequest; 
     request.AllowReadStreamBuffering = true; 
     request.BeginGetResponse(result => 
     { 

      try 
      { 

       Stream imageStream = request.EndGetResponse(result).GetResponseStream(); 
       DispatcherHelper.CheckBeginInvokeOnUI(() => 
       { 
       if (path!=SafePath){ 
        //Item has been recycled 
        return; 
       } 
       .... 

編輯: 有一代碼中的幾個問題: -Switch RegisterAttached to Register,RegisterAttached用於附加屬性而非正常的依賴屬性 -call OnSourceWithCustomRefererChanged中的OnLoaded,因爲在加載該元素後SafePath屬性實際上可能發生變化 - 在清單中添加清楚的uri和source onLoaded的開始,以便在路徑爲空時清除圖像

這是一個完整的工作代碼:

public class SafeImage : ContentControl 
{ 
    private SynchronizationContext uiThread; 

    public static readonly DependencyProperty SafePathProperty = 
     DependencyProperty.Register("SafePath", typeof (string), typeof (SafeImage), 
     new PropertyMetadata(default(string), OnSourceWithCustomRefererChanged)); 

    public string SafePath 
    { 
     get { return (string) GetValue(SafePathProperty); } 
     set { SetValue(SafePathProperty, value); } 
    } 


    private static void OnSourceWithCustomRefererChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 
    { 
     SafeImage safeImage = o as SafeImage; 
     safeImage.OnLoaded(null, null); 
     //OnLoaded(null, null); 
     if (e.NewValue == null) 
      return; 
    } 





    public SafeImage() 
    { 
     Content = new Image(); 
     uiThread = SynchronizationContext.Current; 

     Loaded += OnLoaded; 
     Unloaded += OnUnloaded; 
    } 

    private void OnLoaded(object _sender, RoutedEventArgs _routedEventArgs) 
    { 
     var image = Content as Image; 

     if (image == null) 
      return; 

     var path = SafePath; //(string)GetValue(SafePathProperty); 
     //image.Source = new BitmapImage(new Uri(SafePath)); 
     Debug.WriteLine(path); 

     var bitmapImage = image.Source as BitmapImage; 
     if (bitmapImage != null) 
      bitmapImage.UriSource = null; 
     image.Source = null; 

     if (String.IsNullOrEmpty(path)) 
     { 
      //image.Source = new BitmapImage { UriSource = new Uri(Constants.RESOURCE_IMAGE_EMPTY_PRODUCT, UriKind.Relative) }; 
      return; 
     } 

     // If local image, just load it (non-local images paths starts with "http") 
     if (path.StartsWith("/")) 
     { 
      image.Source = new BitmapImage { UriSource = new Uri(path, UriKind.Relative) }; 
      return; 
     } 



     { 
      var request = WebRequest.Create(path) as HttpWebRequest; 
      request.AllowReadStreamBuffering = true; 
      request.BeginGetResponse(result => 
      { 
       try 
       { 
        Stream imageStream = request.EndGetResponse(result).GetResponseStream(); 
        uiThread.Post(_ => 
        { 

         if (path != this.SafePath) 
         { 
          return; 
         } 
         if (imageStream == null) 
         { 
          image.Source = new BitmapImage { UriSource = new Uri(path, UriKind.Relative) }; 
          return; 
         } 

         bitmapImage = new BitmapImage(); 
         bitmapImage.CreateOptions = BitmapCreateOptions.BackgroundCreation; 
         bitmapImage.SetSource(imageStream); 
         image.Source = bitmapImage; 
         //imageCache.Add(path, bitmapImage); 
        }, null); 
       } 
       catch (WebException) 
       { 
        //uiThread.Post(_ => 
        //{ 
        // image.Source = new BitmapImage { UriSource = new Uri(Constants.RESOURCE_IMAGE_EMPTY_PRODUCT, UriKind.Relative) }; 
        //}, null); 
       } 
      }, null); 
     } 
    } 


    private void OnUnloaded(object sender, RoutedEventArgs e) 
    { 
     var image = Content as Image; 

     if (image == null) 
      return; 

     var bitmapImage = image.Source as BitmapImage; 
     if (bitmapImage != null) 
      bitmapImage.UriSource = null; 
     image.Source = null; 
    } 
} 

正如最後一點,windows phone的ListBox中使用默認情況下虛擬化和再循環(該ItemPanel使用的是VirtualisedStackPanel)。

+0

感謝您的幫助!我希望,我可以給你發一杯啤酒:)。但是,問題仍然存在。 (path!= SafePath)在BeginGetResponse()後導致訪問異常。和圖像仍然混淆(即使在路徑檢查調度) –

+0

無論如何,謝謝你突出的原因。另一個問題:我在SafePath的getter/setter中添加了斷點,但是調試器永遠不會停在那裏。另外,實際上,這是最簡單的情況:即使沒有虛擬化(除非有一些內部的),我也將它用於常見的ListBox。 –

+0

上傳示例項目。 –