具有自动记忆清洁function的图像下载器

我有一个列表(简单ListBox)的项目与主要细节基础上的图像(如果用户点击列表项,详细信息页面打开)。 我在这里 , 这里 , 这里和这里描述了图像内存泄漏的相当着名的问题。

一种可能的方法是在NavigatingFrom 和清理它们时浏览所有图像 。

在其中一个线程中 ,我找到了更有趣的解决方案:它自动清理图像,但这不适用于虚拟化(图像丢失或混合,如果添加私有字段用于存储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; } } 

用法:

  

因此,乍一看,它工作正常,但如果向下滚动并返回,图像会随机更改。

编辑:在这种情况下,目前,我只使用纯ListBox,没有虚拟化(但在其他情况下期待它)。

EDIT2:重现此问题的示例项目。 我相信,它会在一段时间内包含解决方案: https : //simca.codeplex.com/

问题是,当使用虚拟化时,用于每个项目的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 OnLoaded在OnSourceWithCustomRefererChanged中因为SafePath属性更改后实际上可以在加载元素后开心 – 添加清除uri和源在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)。