Why do I get an OutOfMemoryException when I have images in my ListBox?

14,739

Solution 1

Oh, I recently killed whole day to make this working!

So the solution is:

Make your Image control free resources. So set the

BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;

as it was mentioned before.

Make sure you virtualize _bitmap on every item of the list. You should load it on demand (LongListSelector.Realized method) and you have to destroy it! It won't going to collect automatically and GC.Collect doesn't work either. Null reference is not working too :( But here is the method: Make 1x1 pixel file. Copy it into assembly and make resource stream from it to dispose your images with 1x1 pixel blank. Bind custom dispose method to LongListSelector.UnRealized event (e.Container handles your list item).

public static void DisposeImage(BitmapImage image)
{
    Uri uri= new Uri("oneXone.png", UriKind.Relative);
    StreamResourceInfo sr=Application.GetResourceStream(uri);
    try
    {
        using (Stream stream=sr.Stream)
        {
            image.DecodePixelWidth=1; //This is essential!
            image.SetSource(stream);
        }
    }
    catch { }
}

Working for me in LongListSelector with 1000 images 400 width each.

If you miss the 2 step with the data collection you can see the the good results but the memory overflows after 100-200 items scrolled.

Solution 2

You just had Windows Phone with show all the picture's in a user's media library "pictures" folder on screen. That's unbelievably memory intensive and considering the 150MB limit on WP8 apps it's no wonder you're getting OOM exceptions.

A few things you should consider adding:

1) Set Source and SourceUri properties to null when scrolling the listboxitem out of view. See "Caching Images" in Stefan's article here @ http://blogs.msdn.com/b/swick/archive/2011/04/07/image-tips-for-windows-phone-7.aspx

  BitmapImage bitmapImage = image.Source as BitmapImage;
  bitmapImage.UriSource = null;
  image.Source = null;

2) If you're on WP8 make sure to set DecodePixelWidth and/or DecodePixelHeight. That way an image will be loaded into memory, resized permanently and only the resized copy is stored in memory. The images loaded into memory can be much bigger then the screen size of the phone itself. So cropping those down to the right size and only storing the resized images is vital. Set BitmapImage.DecodePixelWidth=480 (at maximum) to help with that.

var bmp = new BitmapImage();

// no matter the actual size, 
// this bitmap is decoded to 480 pixels width (aspect ratio preserved)
// and only takes up the memory needed for this size
bmp.DecodePixelWidth = 480;

bmp.UriSource = new Uri(@"Assets\Demo.png", UriKind.Relative);
ImageControl.Source = bmp;

(code sample from here)

3) Why are you using Picture.GetImage() instead of Picture.GetThumbnail()? Do you really need the image to take up the whole screen?

4) Consider moving from ListBox to LongListSelector if this is a WP8 exclusive app. LLS has much, much better virtualization then ListBox. Looking at your code sample it might be enough for you to just change your XAML ListBox element to LongListSelector element.

Share:
14,739

Related videos on Youtube

Hyndrix
Author by

Hyndrix

Updated on June 12, 2022

Comments

  • Hyndrix
    Hyndrix about 2 years

    I want to display all images stored in the Windows Phone 8 photo folder in my custom gallery which uses a ListBox for displaying the images.

    The ListBox code is as follows:

        <phone:PhoneApplicationPage.Resources>
            <MyApp:PreviewPictureConverter x:Key="PreviewPictureConverter" />
        </phone:PhoneApplicationPage.Resources>
    
        <ListBox Name="previewImageListbox" VirtualizingStackPanel.VirtualizationMode="Recycling">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel CleanUpVirtualizedItemEvent="VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1">
                    </VirtualizingStackPanel>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding Converter={StaticResource PreviewPictureConverter}}" HorizontalAlignment="Center" VerticalAlignment="Center" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
         </ListBox>
    

    With the following converter:

    public class PreviewPictureConverter : System.Windows.Data.IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            PreviewImageItem c = value as PreviewImageItem;
            if (c == null)
                return null;
            return c.ImageData;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    Images are stored in a custom class:

    class PreviewImageItem
    {
        public Picture _picture = null;
        public BitmapImage _bitmap = null;
    
        public PreviewImageItem(Picture pic)
        {
            _picture = pic;
        }
    
        public BitmapImage ImageData 
        {
            get
            {
                System.Diagnostics.Debug.WriteLine("Get picture " + _picture.ToString());
                _bitmap = new BitmapImage();
                Stream data = _picture.GetImage();
                try
                {
                    _bitmap.SetSource(data); // Out-of memory exception (see text)
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine("Exception : " + ex.ToString());
                }
                finally
                {
                    data.Close();
                    data.Dispose();
                    data = null;
                }
    
                return _bitmap;
            }
        }
    }
    

    The following code is used to set the ListBox data source:

    private List<PreviewImageItem> _galleryImages = new List<PreviewImageItem>();
    
    using (MediaLibrary library = new MediaLibrary())
    {
        PictureCollection galleryPics = library.Pictures;
        foreach (Picture pic in galleryPics)
        {
            _galleryImages.Add(new PreviewImageItem(pic));
        }
    
        previewImageListbox.ItemsSource = _galleryImages;
    };
    

    Finally here is the "cleanup" code:

    private void VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1(object sender, CleanUpVirtualizedItemEventArgs e)
    {
        PreviewImageItem item = e.Value as PreviewImageItem;
    
        if (item != null)
        {
            System.Diagnostics.Debug.WriteLine("Cleanup");
            item._bitmap = null;
        }
    }
    

    All this works fine but the code crashes with an OutOfMemoryException after a few images (especially when scrolling fast). The method VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 is called regulary (e.g. every 2 or 3 listbox entries) when the ListBox is scrolled.

    What's wrong with this sample code?

    Why is memory not freed (fast enough)?

    • khellang
      khellang over 11 years
      What is Picture and what does the GetImage()method do? You only set the _bitmap field to null, but the _picture field is left alone, could it be that object which holds some data? Also, it's not a good practice to expose fields publicly. Implement IDisposable in PreviewImageItem and call Dispose() in your VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1 method...
    • Kevin Gosse
      Kevin Gosse over 11 years
      In the cleanup, you should nullify the _picture property as well
    • Hyndrix
      Hyndrix over 11 years
      The Picture is of type "Microsoft.Xna.Framework.Media.Picture" and does not require much memory. Most memory is used by the BitmapImages which are created from the streams provided by the Picture objects.
    • Kevin Gosse
      Kevin Gosse over 11 years
      There may be an obvious error in your code that I fail to see, but you should also check that question: stackoverflow.com/questions/13355496/…
    • uncle_scrooge
      uncle_scrooge over 10 years
      Hi Hyndrix,The code you have written is very useful to me.I just want to know where did you use this dispose method because you are using listbox(as there is no unrealized event).And what are you passing argument.Is it listbox container?.And please tell me by using virtualize stack panel scroll performance is slow,how can i make it fast.Please help me..
  • Hyndrix
    Hyndrix over 11 years
    The hint to limit the decoding resolution is great (I completely forgot this although it's so obvious). The thumbnail stream is way too low quality. One thing which is also important is to call System.GC.Collect() after nulling for the case of fast scrolling.
  • Hyndrix
    Hyndrix over 11 years
    I came accross the memory problem again. And the only means which solved it was using your "DisposeImage" method!
  • gleb.kudr
    gleb.kudr over 11 years
    Glad it helps you. I think this is kind of a bug in WP8 platform.
  • Ashwin N Bhanushali
    Ashwin N Bhanushali over 10 years
    I am facing the same issue. Thanks for the solution.
  • Goofy
    Goofy about 10 years
    @gleb.kudr can you please help me here stackoverflow.com/questions/24157246/…
  • gleb.kudr
    gleb.kudr about 10 years
    @Goofy have you tried my approach? To free every container resources after using it?
  • Goofy
    Goofy about 10 years
    @gleb.kudr can we go to a chat room ? so that i can explain you clearly ? can you invite me ?
  • Goofy
    Goofy about 10 years
    @gleb.kudr are you there ?
  • gleb.kudr
    gleb.kudr about 10 years
    @Goofy sorry, I've been on iOS & Xamarin for almost a year. I don't think I can help you with WP now.
  • Timothée Bourguignon
    Timothée Bourguignon over 9 years
    I'm somewhat puzzled, my ListView gets its data via databinding, thus I don't have any direct influence over the scrolling. Using the technique 1. I can unload the images, but if the user scrolls back, the images are now black and not loaded again by the framework... any idea?
  • JustinAngel
    JustinAngel over 9 years
    Tim, If you're on WP8 you should use LongListSelector with ItemRealized and ItemUnrealized events.
  • Daniel Perez
    Daniel Perez over 9 years
    it's incredible how crappy the ListBox is. Just "upgrading" to LLS I solved various memory leaks that I couldn't otherwise solve. thanks!