2014-09-12 5 views
11

Jeder jemals versucht A Xamarin.Forms Listview mit einem ItemTemplate mit einer Bildansicht? Was passiert jetzt, wenn ListView ca. 20 Zeilen enthält?Xamarin.Forms ListView OutOfMemoryError Ausnahme auf Android

Für mich habe ich eine PNG-Datei von etwa 4K Größe in die Bildansicht geladen. Es wurden maximal 9 - 12 Zeilen angezeigt, bevor die Anwendung mit einem OutOfMemoryError abgestürzt ist. Nachdem im Android-Manifest ein großer Heap angefordert wurde, stürzt die App nach 60 - 70 Zeilen ab.

Ich weiß, dass Xamarin die Verwendung der BitmapFactory-Klasse zur Verkleinerung von Bitmaps fördert, aber dies ist für die Xamarin-Formularabbildansicht nicht anwendbar (sofort einsatzbereit).

Ich bin dabei zu versuchen, mit einer Sub-Klasse des ImageRenderer zu fiedeln, um zu sehen, ob ich eine BitmapFactory.Options-Eigenschaft hinzufügen kann und ob das das Problem lösen wird.

Außerdem muss ich möglicherweise überprüfen, ob Xamarin.Forms die enthaltene Bitmap entsorgt (recycelt), nachdem die ViewCell auf dem Bildschirm gescrollt wurde.

Bevor ich mich auf diesen Weg beginne, wäre ich sehr daran interessiert, Kommentare zu erhalten, die dies einfacher machen oder eine einfachere Lösung, die diesen Prozess für unnötig hält.

Wir freuen uns ...

+0

Was die Bitmap-Größe des 4K PNG ist ? PNGs werden im Speicher ohne Komprimierung gespeichert. Es ist möglich, ein 4K-PNG zu erstellen, das bei der Konvertierung in eine Bitmap weit über 1 GB Daten beträgt. Auch, ja, Sie müssen wirklich überprüfen, ob die Bitmaps entsorgt werden. Und wahrscheinlich ist die Antwort, nein, sind sie nicht. – Frank

+0

das PNG, das ich derzeit verwende, ist als 512 x 512 definiert. – Avrohom

+0

So dass 4kB PNG erfordert 512 x 512 x 32 Bit = 1 MB RAM zu speichern/anzuzeigen. Es ist also sehr wahrscheinlich, dass Sie sie tatsächlich nicht entsorgen. – Frank

Antwort

10

Ja, ich habe eine Lösung gefunden. Code zu folgen. Aber vorher, lassen Sie mich ein bisschen erklären, was ich getan habe.

Also, es ist definitiv eine Notwendigkeit, in unseren eigenen Händen zu nehmen, um das Bild und die zugrunde liegenden Ressourcen (Bitmap oder zeichnest, wie Sie es nennen möchten) zu entsorgen. Im Grunde kommt es darauf an, das native 'ImageRenderer'-Objekt zu entfernen.

Jetzt gibt es keine Möglichkeit, von überall einen Verweis auf diesen ImageRenderer zu erhalten, weil dazu Platform.GetRenderer (...) aufgerufen werden muss. Der Zugriff auf die Klasse "Plattform" ist nicht möglich, da der Geltungsbereich als "intern" deklariert ist.

So blieb mir keine andere Wahl übrig, als die Image-Klasse und ihren (Android-) Renderer zu unterteilen und diesen Renderer selbst von innen zu zerstören (als Argument 'true' übergeben. Nicht mit 'false' versuchen '). Innerhalb des Renderers hake ich auf Seite verschwinden (im Falle einer TabbedPage). In den meisten Situationen wird das Ereignis Seite verschwinden nicht dem Zweck gut gerecht, z. B. wenn sich die Seite noch im Bildschirmstapel befindet, aber verschwindet, weil eine andere Seite unter Top davon gezeichnet wird. Wenn Sie die Bilder entsorgt haben und die Seite wieder freigelegt (angezeigt) wird, werden die Bilder nicht angezeigt. In diesem Fall müssen wir uns auf das "Popped" -Ereignis der Hauptnavigationsseite begeben.

Ich habe versucht, es so gut wie möglich zu erklären. Der Rest - ich hoffe - Sie werden in der Lage sein, von dem Code zu bekommen:

Dies ist die Bildunterklasse im PCL-Projekt.

using System; 

using Xamarin.Forms; 

namespace ApplicationClient.CustomControls 
{ 
    public class LSImage : Image 
    { 
    } 
} 

Der folgende Code ist im Droid-Projekt.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using System.Text; 

using Android.App; 
using Android.Content; 
using Android.OS; 
using Android.Runtime; 
using Android.Views; 
using Android.Views.InputMethods; 
using Android.Widget; 
using Android.Util; 
using Application.Droid.CustomControls; 
using ApplicationClient.CustomControls; 
using Xamarin.Forms; 
using Xamarin.Forms.Platform.Android; 

    [assembly: ExportRenderer(typeof(ApplicationClient.CustomControls.LSImage), typeof(LSImageRenderer))] 

    namespace Application.Droid.CustomControls 
    { 
     public class LSImageRenderer : ImageRenderer 
     { 
      Page page; 
      NavigationPage navigPage; 

      protected override void OnElementChanged(ElementChangedEventArgs<Image> e) 
      { 
       base.OnElementChanged(e); 
       if (e.OldElement == null) 
       { 
        if (GetContainingViewCell(e.NewElement) != null) 
        { 
         page = GetContainingPage(e.NewElement); 
         if (page.Parent is TabbedPage) 
         { 
          page.Disappearing += PageContainedInTabbedPageDisapearing; 
          return; 
         } 

         navigPage = GetContainingNavigationPage(page); 
         if (navigPage != null) 
          navigPage.Popped += OnPagePopped; 
        } 
        else if ((page = GetContainingTabbedPage(e.NewElement)) != null) 
        { 
         page.Disappearing += PageContainedInTabbedPageDisapearing; 
        } 
       } 
      } 

      void PageContainedInTabbedPageDisapearing (object sender, EventArgs e) 
      { 
       this.Dispose(true); 
       page.Disappearing -= PageContainedInTabbedPageDisapearing; 
      } 

      protected override void Dispose(bool disposing) 
      { 
       Log.Info("**** LSImageRenderer *****", "Image got disposed"); 
       base.Dispose(disposing); 
      } 

      private void OnPagePopped(object s, NavigationEventArgs e) 
      { 
       if (e.Page == page) 
       { 
        this.Dispose(true); 
        navigPage.Popped -= OnPagePopped; 
       } 
      } 

      private Page GetContainingPage(Xamarin.Forms.Element element) 
      { 
       Element parentElement = element.ParentView; 

       if (typeof(Page).IsAssignableFrom(parentElement.GetType())) 
        return (Page)parentElement; 
       else 
        return GetContainingPage(parentElement); 
      } 

      private ViewCell GetContainingViewCell(Xamarin.Forms.Element element) 
      { 
       Element parentElement = element.Parent; 

       if (parentElement == null) 
        return null; 

       if (typeof(ViewCell).IsAssignableFrom(parentElement.GetType())) 
        return (ViewCell)parentElement; 
       else 
        return GetContainingViewCell(parentElement); 
      } 

      private TabbedPage GetContainingTabbedPage(Element element) 
      { 
       Element parentElement = element.Parent; 

       if (parentElement == null) 
        return null; 

       if (typeof(TabbedPage).IsAssignableFrom(parentElement.GetType())) 
        return (TabbedPage)parentElement; 
       else 
        return GetContainingTabbedPage(parentElement); 
      } 

      private NavigationPage GetContainingNavigationPage(Element element) 
      { 
       Element parentElement = element.Parent; 

       if (parentElement == null) 
        return null; 

       if (typeof(NavigationPage).IsAssignableFrom(parentElement.GetType())) 
        return (NavigationPage)parentElement; 
       else 
        return GetContainingNavigationPage(parentElement); 
      } 
     } 
    } 

Schließlich habe ich den Namen der Anwendung im Namensraum zu ‚ApplicationClient‘ im PCL-Projekt geändert und auf ‚Application.Droid‘ in Droid Projekt. Sie sollten es zu Ihrem App-Namen ändern.

Auch die wenigen rekursiven Methoden am Ende der Renderer-Klasse, weiß ich, dass ich es in eine generische Methode kombinieren könnte. Die Sache ist, dass ich eine nach der anderen gebaut habe, als die Notwendigkeit entstand. So, so habe ich es verlassen.

Glücklich Codierung,

Avrohom

+0

Vielen Dank für Ihren Code und die Erklärung. nur eine Sache, kann ich eine ImageCell mit Ihrem Code verwenden? Ich habe ein benutzerdefiniertes ViewCell ausprobiert, konnte es aber nicht zur Arbeit bringen. Prost – user1667474

+0

Nie mit ImageCell versucht. Was hält Sie mit einer ViewCell zurück? Wenn Sie darauf bestehen, eine ImageCell zu verwenden, dann wäre es eine gute Idee, den Code in der 'GetContainingViewCell'-Methode zu ändern, wo immer' ViewCell 'durch' ImageCell 'ersetzt wird. Ich kann keinen Grund sehen, warum es nicht tun sollte. – Avrohom

+0

Noch mehr, Sie können "ViewCell" nur in "Cell" ändern, damit es mit beiden funktioniert. – Avrohom

2

Eine weitere Reihe von Schritten, die helfen kann, ist die folgende:

Es erscheint ein Speicherleck auf Android beteiligt Listviews mit benutzerdefinierten Zellen zu sein. Ich habe einige untersuchen und festgestellt, dass, wenn ich die folgenden in meinen Seiten haben:

protected override void OnAppearing() 
    { 
     BindingContext = controller.Model; 
     base.OnAppearing(); 
    } 

    protected override void OnDisappearing() 
    { 
     BindingContext = null; 
     Content = null; 
     base.OnDisappearing(); 
     GC.Collect(); 
    } 

die Android-Option im Manifest Legen Sie einen großen Haufen (android: largeHeap = „true“) zu verwenden, und schließlich die verwendete neuer Garbage Collector, (in der environment.txt, MONO_GC_PARAMS = bridge-implementation = new) Diese Kombination von Dingen scheint das Absturzproblem zu beheben. Ich kann nur davon ausgehen, dass dies nur, weil die Einstellung der Dinge auf Null, hilft der GC, die Elemente zu entsorgen, die große Heap-Größe, um den GC Zeit dafür zu kaufen, und die neue GC-Option, um die Sammlung selbst zu beschleunigen. Ich hoffe, das hilft jemand anderes ...

+0

Ich denke Sie haben immer noch das Speicherleck. Dinge mit largeHeap = "true" zu lösen, ist wirklich eine schlechte Idee. Es funktioniert auf Geräten mit mehr Speicher, aber "kleine" Geräte haben keinen zusätzlichen Speicher zur Verfügung. Sie verschieben den Absturz einfach. –

+0

Ihr richtig, deshalb schlug ich vor, dass es einfach war, Zeit zu kaufen, damit der Müllsammler vorbeikommen und Raum frei machen konnte. Ich hatte einen Listenansicht mit mehreren Bildern und Text pro Zelle, 5 bis 10 Zellen auf einem Bildschirm zu einer Zeit (je nach Bildschirm Platz), mit mehreren hundert Teil der Gesamtliste, auf einem Gerät mit 256 MB RAM angezeigt, und es handhabte es ohne zu stürzen. Das war einfach ein Pflaster, bis die Xamarin-Leute den Speicherleck reparieren konnten. – TChadwick

-2

In Visual Studio 2015 Gehen Sie zu Debug-Option> .droid Eigenschaften> Android Optionen> Erweitert und stellen Sie Java Max Heap Größe 1G