web-dev-qa-db-ja.com

リストボックスに画像があるのに、なぜOutOfMemoryExceptionが発生するのですか?

ListBoxを使用して画像を表示するカスタムギャラリーのWindowsPhone8フォトフォルダーに保存されているすべての画像を表示したいと思います。

ListBoxコードは次のとおりです。

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

次のコンバーターを使用:

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();
    }
}

画像はカスタムクラスに保存されます:

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

次のコードは、ListBoxデータソースを設定するために使用されます。

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

最後に、「クリーンアップ」コードを示します。

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

これはすべて正常に機能しますが、いくつかの画像の後にコードがOutOfMemoryExceptionでクラッシュします(特に高速スクロールの場合)。メソッドVirtualizingStackPanel_CleanUpVirtualizedItemEvent_1は、ListBoxがスクロールされると、定期的に(たとえば、2つまたは3つのリストボックスエントリごとに)呼び出されます。

このサンプルコードの何が問題になっていますか?

なぜメモリが解放されない(十分に速い)のですか?

27
Hyndrix

ああ、私は最近これを機能させるために一日中殺しました!

したがって、解決策は次のとおりです。

Imagecontrolを無料のリソースにします。だから、

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

前に述べたように。

リストのすべての項目で_bitmapを仮想化するようにしてください。オンデマンドでロードし(LongListSelector.Realizedメソッド)、破棄する必要があります!自動的に収集されることはなく、GC.Collectも機能しません。ヌル参照も機能しません:(しかし、メソッドは次のとおりです:1x1ピクセルファイルを作成します。それをアセンブリにコピーし、そこからリソースストリームを作成して、1x1ピクセルの空白で画像を破棄します。カスタムdisposeメソッドをLongListSelector.UnRealizedイベントにバインドします(e。コンテナはリストアイテムを処理します)。

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

それぞれ400幅の1000枚の画像を含むLongListSelectorで作業しています。

データ収集の2つのステップを見逃した場合、良好な結果を確認できますが、100〜200個のアイテムがスクロールされた後にメモリがオーバーフローします。

23
gleb.kudr

画面上のユーザーのメディアライブラリ「pictures」フォルダにあるすべての画像を表示するWindowsPhoneがあります。これは信じられないほどメモリを大量に消費し、WP8アプリの150MBの制限を考慮すると、OOM例外が発生するのも不思議ではありません。

追加を検討する必要があるいくつかの事項:

1)listboxitemをスクロールして表示しない場合は、SourceプロパティとSourceUriプロパティをnullに設定します。 Stefanの記事の「CachingImages」をここで参照してください@ 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)WP8を使用している場合は、必ず DecodePixelWidth および/またはDecodePixelHeightを設定してください。このようにして、画像がメモリにロードされ、永続的にサイズ変更され、サイズ変更されたコピーのみがメモリに保存されます。メモリにロードされる画像は、電話自体の画面サイズよりもはるかに大きくなる可能性があります。したがって、それらを適切なサイズにトリミングし、サイズ変更された画像のみを保存することが重要です。これを支援するために、BitmapImage.DecodePixelWidth = 480(最大)を設定します。

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;

ここ からのコードサンプル)

3) Picture.GetThumbnail ()の代わりにPicture.GetImage()を使用しているのはなぜですか?画面全体を占める画像が本当に必要ですか?

4)これがWP8専用アプリの場合は、ListBoxからLongListSelectorへの移行を検討してください。 LLSは、ListBoxよりもはるかに優れた仮想化を備えています。コードサンプルを見ると、XAMLListBox要素をLongListSelector要素に変更するだけで十分な場合があります。

13
JustinAngel

このアプローチを試してください: 自動メモリクリーニングを備えた画像ダウンローダー 。ここのサンプルプロジェクト: https://simca.codeplex.com/

0