2012-12-11 9 views
26

画像を表示するためにListBoxを使用している私のカスタムギャラリーに、Windows Phone 8の写真フォルダに保存されているすべての画像を表示したいとします。ListBoxに画像があるときにOutOfMemoryExceptionが発生するのはなぜですか?

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

次のコードが使用され、次のコンバータを

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

次のよう

ListBoxコードであります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がスクロールされたときにregulary(例えば、2つまたは3つのリストボックスエントリごと)と呼ばれます。

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

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

+0

'Picture'とは何か、GetImage()メソッドは何をしていますか?あなたは '_bitmap'フィールドを' null'にセットするだけですが、 '_picture'フィールドは残されています。それは何らかのデータを保持するオブジェクトですか?また、フィールドを公開するのは良い方法ではありません。 'PreviewImageItem'に' IDisposable'を実装し、 'VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1'メソッドで' Dispose() 'を呼び出してください... – khellang

+0

クリーンアップでは、' _picture'プロパティも無効にする必要があります –

+0

Pictureは "Microsoft.Xna"タイプです。 Framework.Media.Picture "と多くのメモリを必要としません。ほとんどのメモリは、ピクチャオブジェクトによって提供されるストリームから作成されるBitmapImagesによって使用されます。 – Hyndrix

答えて

22

ああ、私は最近、これを動作させるために一日中死んだ!

解決策は次のとおりです。

Imageコントロールを無料で利用できます。したがって、前述のように

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

を設定します。

リストのすべての項目で必ず_bitmapを仮想化してください。必要に応じてロードする必要があります(LongListSelector.Realizedメソッド)、それを破壊する必要があります!自動的に収集されず、GC.Collectも機能しません。 null参照も機能していません:( ただし、ここにはメソッドがあります: 1x1ピクセルファイルを作成してアセンブリにコピーし、リソースストリームを作成して1x1ピクセルの空白で画像を配置します。 (e.Containerはあなたのリスト項目を扱う)。

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

1000の画像400幅それぞれとLongListSelectorで私のために働く。

あなたはデータ収集と2のステップを逃した場合、あなたが良い結果を見ることができます100〜200個の項目をスクロールするとメモリがオーバーフローします。

+0

私は再びメモリ問題を横断しました。そして、それを解決した唯一の手段は、あなたの "DisposeImage"メソッドを使用していた! – Hyndrix

+1

あなたを助けてくれてうれしいです。私はこれがWP8プラットフォームのバグだと思う。 –

+0

私は同じ問題に直面しています。ソリューションに感謝します。 –

13

あなたは、画面上のすべての画像をユーザーのメディアライブラリの「画像」フォルダに表示してWindows Phoneを持っていました。それは信じられないほどメモリを消費し、WP8アプリケーションの150MBの制限を考えると、OOMの例外を取得するのは不思議ではありません。

あなたが追加することを検討すべきいくつかのこと:

1)ビューの外listboxitemをスクロールしたときnullにソースとSourceUriプロパティを設定します。あなたはWP8にしている場合はDecodePixelWidthおよび/またはDecodePixelHeightを設定してください)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 @ここステファンの記事で「画像をキャッシュ」を参照してください。こうすると、イメージがメモリにロードされ、永続的にサイズが変更され、サイズ変更されたコピーのみがメモリに格納されます。メモリにロードされた画像は、電話機自体の画面サイズよりもはるかに大きくなる可能性があります。したがって、これらの画像を正しいサイズに切り取り、サイズ変更された画像のみを保存することが不可欠です。 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; 

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

3)なぜあなたはPicture.GetImage()の代わりに、Picture.GetThumbnailを使用していますか()?あなたは本当に画面全体を取るためにイメージが必要ですか?

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

+0

デコードの解像度を制限するヒントは素晴らしいです(私は完全に忘れてしまいましたが)。サムネイルストリームの品質が低すぎます。重要なことの1つは、高速スクロールの場合のnulling後にSystem.GC.Collect()を呼び出すことです。 – Hyndrix

+0

私は幾分困惑しています、私のListViewはデータバインディングを介してそのデータを取得するので、私はスクロールに対して直接的な影響はありません。テクニック1を使用すると、私はイメージをアンロードすることができますが、ユーザーがスクロールバックすると、イメージは黒になり、フレームワークによって再びロードされません。 –

+0

Tim、WP8の場合、ItemRealizedイベントとItemUnrealizedイベントでLongListSelectorを使用する必要があります。 – JustinAngel

関連する問題