2012-12-11 5 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 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와 충돌 :

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

마지막으로 여기에 "정리"코드입니다. 방법 VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1ListBox이 스크롤 될 때 regulary (예 : 2 또는 3 개의 목록 상자 항목마다)라고합니다.

이 샘플 코드의 문제점은 무엇입니까?

메모리가 해제되지 않는 이유는 무엇입니까?

+0

'Picture' 란 무엇이고'GetImage()'메소드는 무엇을합니까? 당신은'_bitmap' 필드를'null'로 설정 만하지만'_picture' 필드는 남겨져 있습니다, 어떤 데이터를 보유하고있는 객체일까요? 또한 필드를 공개적으로 노출하는 것은 좋지 않습니다. 'PreviewImageItem'에'IDisposable'을 구현하고'VirtualizingStackPanel_CleanUpVirtualizedItemEvent_1' 메쏘드에서'Dispose()'를 호출하십시오 ... – khellang

+0

정리에서'_picture' 속성도 무효화해야합니다. –

+0

그림 형식이 "Microsoft.Xna"입니다. Framework.Media.Picture "와 많은 메모리를 필요로하지 않습니다. 대부분의 메모리는 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 픽셀 공백으로 처리합니다 .Builder 맞춤형 메서드를 LongListSelector.UnRealized 이벤트에 바인딩합니다. (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 예외가 발생하는 것은 당연한 일입니다. 당신이 추가하는 것을 고려한다

몇 가지 :보기 밖으로 ListBoxItem의를 스크롤 할 때

1) 설정 소스 및 SourceUri 속성은 null로. 당신이 DecodePixelWidth 및/또는 DecodePixelHeight을 설정해야합니다 WP8에있는 경우) 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

해독 해상도를 제한하는 힌트 (대단히 분명한 사실이지만 이것을 완전히 잊어 버렸습니다). 미리보기 이미지 스트림의 품질이 너무 낮습니다. 한 가지 중요한 점은 빠른 스크롤의 경우 null을 지정한 후에 System.GC.Collect()를 호출하는 것입니다. – Hyndrix

+0

저는 다소 혼란 스럽습니다. ListView는 데이터 바인딩을 통해 데이터를 가져 오므로 스크롤하는 것에 직접적인 영향을주지 않습니다. 기술 사용 1. 이미지를 언로드 할 수 있지만 사용자가 뒤로 스크롤하면 이미지가 검정색으로 프레임 워크에서 다시로드되지 않습니다. –

+0

Tim, WP8을 사용하는 경우 ItemRealized 및 ItemUnrealized 이벤트와 함께 LongListSelector를 사용해야합니다. – JustinAngel

관련 문제