我有一个列表(简单ListBox)的项目,其中包含主要细节基础上的图像(如果用户点击列表项,则打开详细信息页面)。我遇到了一个非常着名的图像内存泄漏问题,描述了here,here,here和here。
导航来自run through all images时,一种可能的方法是and clean them。
在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/
答案 0 :(得分: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到Register,RegisterAttached用于附加属性而不是普通的依赖属性 在OnSourceWithCustomRefererChanged中调用OnLoaded,因为更改的SafePath属性实际上可以在元素加载后开心 - 在onLoaded的开头添加清除uri和source,以便在路径为空时清除图像
这是一个完整的工作代码:
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)。