Memory consumption of BitmapImage/Image control in Windows Phone 8
Asked Answered
G

1

10

I am testing a WP8 app and it's image viewer to show many images, I found app's memory consumption is raising and want to find out how to solve it.

I've read some articles from web, however the solutions provided by those articles are not working on my app, please read the history below.

First, I found the article "Image Tips for Windows Phone 7" and download its sample to do clean image cache testing, it's working with 1 image.

And then for testing purposes, I make this app compiled with 15 offline images inside the app, and set as "Content", please download test app from here.

My testing steps are:

(1) Launch app
(2) Go to Image Caching page
(3) Enable checkbox "Avoid Image Caching"
(4) Continuously tapping button Show/Clear
(5) Keep watching the memory status textblock at the bottom

When I'm testing my app, the memory is raising, like 16.02MB => Show(19.32MB) => Clear(16.15MB) => Show(20.18MB) => Clear (17.03MB)...etc And memory won't be freed even leaving caching page and go to caching page again. It seems the solution of article "Image Tips for Windows Phone 7" is working for only 1 image.

Here comes the xaml and code-behind of solution by "Image Tips for Windows Phone 7".

[Caching.xaml]

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel Orientation="Horizontal" VerticalAlignment="Top">
                <ToggleButton Content="Show" Width="150" Checked="ShowImageClicked" Unchecked="ClearImageClicked"/>
                <CheckBox x:Name="cbAvoidCache" Content="Avoid Image Caching"/>
            </StackPanel>
            <Image x:Name="img" Grid.Row="2" Width="256" Height="192"/>
            <TextBlock x:Name="tbMemory" Grid.Row="2" Text="Memory: " VerticalAlignment="Bottom" Style="{StaticResource PhoneTextLargeStyle}"/>
        </Grid>

[Caching.xaml.cs]

public partial class Caching : PhoneApplicationPage
{
    public Caching()
    {
        InitializeComponent();

        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(500);
        timer.Start();
        timer.Tick += delegate
        {
            GC.Collect();
            tbMemory.Text = string.Format("Memory: {0} bytes", DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage"));
        };
    }

    private int nIndex = 1;
    BitmapImage bitmapImageFromUri = new BitmapImage();
    private void ShowImageClicked(object sender, RoutedEventArgs e)
    {
        string strImage = string.Format("../ImagesAsContent/{0:D2}.jpg", nIndex);
        bitmapImageFromUri.UriSource = new Uri(strImage, UriKind.Relative);
        img.Source = bitmapImageFromUri;

        nIndex++;
        if (nIndex > 15)
        {
            nIndex = 1;
        }

        (sender as ToggleButton).Content = "Clear";
    }

    private void ClearImageClicked(object sender, RoutedEventArgs e)
    {
        if (cbAvoidCache.IsChecked == true)
        {
            // set the UriSource to null in order to delete the image cache
            BitmapImage bitmapImageFromUri = img.Source as BitmapImage;
            bitmapImageFromUri.UriSource = null;
        }
        img.Source = null;
        (sender as ToggleButton).Content = "Show";
    }
}

I also tried to search any other solutions, some testing results are as below.

(1) Article "[wpdev] Memory leak with BitmapImage": It provides 2 solutions, one is DisposeImage API, another is to set BitmapImage source to null as below. Also the article let us know we must be careful about event handler attach/dettach, however my testing app doesn't have event handler in caching page.

[DisposeImage]

private void DisposeImage(BitmapImage image)
{
    if (image != null)
    {
        try
        {
            using (var ms = new MemoryStream(new byte[] { 0x0 }))
            {
                image.SetSource(ms);
            }
        }
        catch (Exception)
        {
        }
    }
}

[Set null]

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

(2) Article "Windows phone: listbox with images out-of-memory": It provides an API "DisposeImage" with little difference than (1)'s as below, but this also doesn't work, I still got the memory raising symptom.

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
    {}
}

(3) Article "Cannot find the memory leak": It provides the same 2 solutions as above mentioned, also it mentioned the issue cannot repro for isolated storage's images, however my testing app's images are from isolated storage.

(4) I also tried for 1000 images, the testing result is app crash when the app showed around 190 images sequentially, please refer to the Windows Phone Application Analysis Graphics for memory below. enter image description here

Finally, thanks for your patience to read my question and history, I've been working on this to find solution for many days. If you have any clue or solution, please kindly let me know.

Thanks.

Genealogy answered 8/8, 2013 at 13:15 Comment(2)
Good question, have the same issues. Any news here?Terrill
Still looking for a solution, too. Appearently, this is a WP7.5 bug, meaning it also happens on WP8 iff you don't target WP8 only.Greengrocer
H
3

I was dealing with the same problem and I think, in the end, that actually I've found a workaround, I am not a pro programmer but here is my solution:

  public Task ReleaseSingleImageMemoryTask(MyImage myImage, object control)
    {
        Pivot myPivot = control as Pivot;
        Task t = Task.Factory.StartNew(() =>
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                if (myImage.img.UriSource != null)
                {
                    myImage.img.UriSource = null;
                    DisposeImage(myImage.img);
                }
                PivotItem it = (PivotItem)(myPivot.ItemContainerGenerator.ContainerFromIndex(myImage.number % 10));
                Image img = FindFirstElementInVisualTree<Image>(it);
                if (img != null)
                {
                    img.Source = null;
                    GC.Collect();
                }
            });
            myImage.released = true;
        });
        return t;
    } 


private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
    {
        var count = VisualTreeHelper.GetChildrenCount(parentElement);
        if (count == 0)
            return null;

        for (int i = 0; i < count; i++)
        {
            var child = VisualTreeHelper.GetChild(parentElement, i);

            if (child != null && child is T)
            {
                return (T)child;
            }
            else
            {
                var result = FindFirstElementInVisualTree<T>(child);
                if (result != null)
                    return result;
            }
        }
        return null;
    }

    private void DisposeImage(BitmapImage img)
    {
        if (img != null)
        {
            try
            {
                using (var ms = new MemoryStream(new byte[] { 0x0 }))
                {
                    img = new BitmapImage();
                    img.SetSource(ms);
                }
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine("ImageDispose FAILED " + e.Message);
            }
        }
    }

Hope this help :)

Homo answered 24/9, 2013 at 21:10 Comment(5)
Hi Damian, thanks for your response, though I'm not testing this issue now, still thanks for your answer.Genealogy
Please help me here #24161508Penicillium
when you try to dispose the image you call img = new BitmapImage();. doesn't that defeat the purpose of the whole thing?Parkland
I may be wrong, but it appears while he is assigning a new object he is assigning a 0-byte block of memory as the source: var ms = new MemoryStream(new byte[] { 0x0 }) and img.SetSource(ms);Germiston
@Germiston yes - but on the newly created image and not the old reference that is overwritten at that point. So you end up with another leak (which is smaller)Parkland

© 2022 - 2024 — McMap. All rights reserved.