Reputation: 1315
According to Stefan Wick's blog, freeing memory from Images is just as simple as doing:
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
However, how can I achieve the same effect If I am using data binding in Xaml like this?:
// inside MainPage.xaml
<Button Tap="GetImages">Get Images</Button>
<ListBox ItemSource="{Binding Links}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I am not binding to Image's Source directly, because I want to use the bitmap's 'Background creation' option and it's download progress event in the future -->
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding}" CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
//inside MainPage.xaml.cs
public void GetImages(object sender, RoutedEventArgs e) {
(DataContext as ViewModel).GetMeSomeImages();
}
// inside ViewModel.cs
public void GetMeSomeImages() {
List<string> links = ThisMethodGetsLinks();
Links.Clear();
foreach(var link in links)
Links.Add(link);
}
ObservableCollection<string> _links;
public ObservableCollection<string> Links {
get {
if (_links == null)
_links = new ObservableCollection<string>();
return _links;
}
}
In this scenario, each button tap would consume additional memory until the phone/emulator crashes. Images are not freed from memory despite the Listbox's ItemSource property being cleared.
Upvotes: 2
Views: 1819
Reputation: 2473
The BitmapCreateOptions Enumeration defines the BackgroundCreation enumeration thusly:
Causes a BitmapSource to be initialized as soon as it is declared. This option uses the image cache for previously used URIs. If an image is not in the image cache, the image will be downloaded and decoded on a separate background thread.
This makes me think that when you change the UriSource property, and the old Image is disposed, the background thread handling the download of the bitmap is not notified, and that background thread continues to download the image. This may be done because the phone implements it's own cacheing of all images (note the presence of the "IgnoreImageCache" element of the BitmapCreateOptions enumeration).
This is likely the culprit, however another possibility is that virtualization of the ListBox is not actually happening. The most frequent reason for this is if the items in the list are not explicitly defined to have the same height. Virtualizing in the ListBox uses VirtualizingStackPanel under the covers, and requires every item to be the same height. If any item is a different height, the virtualzing behavior is cancelled. Below is some code that should help you determine if virt. is actually happening or not. Another thing with Virtualizing is that currently your Images don't have a set height until the image data is downloaded. This means that before images are downloaded, all images have a height of 0 pixels. If all images have a height of 0 pixels, then that means all of them are "in view" according to the virt. logic, they should all begin downloading.
In summary, try these things:
Simple Structure to Contain Image Data:
using System.Diagnostics;
public class BoundImage
{
private string imageURL;
public static int TotalImagesRequested = 0;
public BoundImage(string url)
{
imageURL = url;
}
public string ImageURL
{
get
{
TotalImagesRequested++;
// Watch the output window and see if TotalImagesRequested is
// growing to a crazy high amount (if it is it will eventually
// reach the total Count of the _links variable. But your
// app may crash before that happens.
Debug.WriteLine("Images being requested: " + TotalImagesRequested);
return imageURL;
}
}
}
Altered Property for Exposing the Links:
//inside MainPage.xaml.cs
public void GetImages(object sender, RoutedEventArgs e)
{
(DataContext as ViewModel).GetMeSomeImages();
}
// inside ViewModel.cs
public void GetMeSomeImages()
{
List<string> links = ThisMethodGetsLinks();
Links.Clear();
_links = new ObservableCollection<BoundImage>();
foreach(string link in links)
{
_links.Add(new BoundImage(link));
}
}
ObservableCollection<BoundImage> _links;
public ObservableCollection<BoundImage> Links
{
get
{
if (_links == null)
_links = new ObservableCollection<BoundImage>();
return _links;
}
set
{
_links = value;
}
}
Altered XAML to Hook Binding to ImageURL property of BoundImage:
// inside MainPage.xaml
<Button Tap="GetImages">Get Images</Button>
<ListBox ItemSource="{Binding Links}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- I am not binding to Image's Source directly, because I want to use the bitmap's 'Background creation' option and it's download progress event in the future -->
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding ImageURL}" CreateOptions="BackgroundCreation"/>
</Image.Source>
</Image>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Upvotes: 2