Brauche Hilfe wegen OutOfMemoryError

  • Antworten:13
Andreas Lorenz
  • Forum-Beiträge: 111

18.02.2011, 22:57:40 via Website

Hallo,

ich habe eine Gallerie nach Anleitung HelloGallery erstellt.
Die Bilder werden dabei aus Internet geladen, sind ca. 1024x768 groß und haben ca. 400kb groß.

Mein Problem ist, dass die DalvikVM mir einen OutOfMemoryError ausgibt. Und das für die ausführende Aktion mehr als 15MB gebraucht werden.
Ich habe das auch mit einem einzigen Bild versucht und bekomme dabei den gleichen Fehler.

Folgende Meldung wird ausgegeben:

02-18 22:40:38.867: ERROR/dalvikvm-heap(339): 15980544-byte external allocation too large for this process.
02-18 22:40:38.867: ERROR/(339): VM won't let us allocate 15980544 bytes
02-18 22:40:38.867: DEBUG/skia(339): --- decoder->decode returned false
02-18 22:40:38.867: DEBUG/AndroidRuntime(339): Shutting down VM
02-18 22:40:38.877: WARN/dalvikvm(339): threadid=3: thread exiting with uncaught exception (group=0x4001b188)
02-18 22:40:38.877: ERROR/AndroidRuntime(339): Uncaught handler: thread main exiting due to uncaught exception
02-18 22:40:38.887: ERROR/AndroidRuntime(339): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at de.andreaslorenz.app.ImageAdapter.ImageOperations(ImageAdapter.java:68)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at de.andreaslorenz.app.ImageAdapter.setDrawable(ImageAdapter.java:37)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at de.andreaslorenz.app.ImageAdapter.<init>(ImageAdapter.java:28)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at de.andreaslorenz.app.gallery$1.onItemSelected(gallery.java:63)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.widget.AdapterView.fireOnSelected(AdapterView.java:864)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.widget.AdapterView.access$200(AdapterView.java:42)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.widget.AdapterView$SelectionNotifier.run(AdapterView.java:830)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.os.Handler.handleCallback(Handler.java:587)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.os.Handler.dispatchMessage(Handler.java:92)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.os.Looper.loop(Looper.java:123)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at android.app.ActivityThread.main(ActivityThread.java:4363)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at java.lang.reflect.Method.invokeNative(Native Method)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at java.lang.reflect.Method.invoke(Method.java:521)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
02-18 22:40:38.887: ERROR/AndroidRuntime(339): at dalvik.system.NativeStart.main(Native Method)

Könnte jemand schauen wo der Fehler sein könnte?

Meine ImageAdapter Klasse:

[code]public class ImageAdapter extends BaseAdapter {
private int mGalleryItemBackground;
private Context mContext;
private String[] imageURL;
private Drawable[] mImageDraw;

public ImageAdapter(Context c, String[] url) {
mContext = c;
imageURL = url;
--> Zeile 28: setDrawable();
TypedArray a = c.obtainStyledAttributes(R.styleable.HelloGallery);
mGalleryItemBackground = a.getResourceId(R.styleable.HelloGallery_android_galleryItemBackground, 0);
a.recycle();
}

private void setDrawable(){
mImageDraw = new Drawable[imageURL.length];
for(int i = 0; i < imageURL.length; i++){
--> Zeile 37: mImageDraw[i] = ImageOperations(mContext, imageURL[i], "galleryIMG"+i+".jpg");
}
}

public int getCount() {
return mImageDraw.length;
}

public Object getItem(int position) {
return position;
}

public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView i = new ImageView(mContext);

i.setImageDrawable(mImageDraw[position]);
i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
i.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
i.setBackgroundResource(mGalleryItemBackground);

return i;
}

private Drawable ImageOperations(Context cont, String url, String saveFilename){
try {
InputStream is = (InputStream) this.fetch(url);
--> Zeile 68: Drawable d = Drawable.createFromStream(is, "src");
is.close();
return d;
} catch (MalformedURLException e){
e.printStackTrace();
return null;
} catch (IOException e){
e.printStackTrace();
return null;
}
}

public Object fetch(String adress) throws MalformedURLException,IOException {
URL url = new URL(adress);
Object content = url.getContent();
return content;
}
}[/code]

Ich habe die App auf einem Galaxy S ausprobiert. Sie läuft zwar aber es kommt ab und an zum "Force Close".

— geändert am 19.02.2011, 05:37:11

Antworten
Gelöschter Account
  • Forum-Beiträge: 107

19.02.2011, 01:13:34 via Website

Wieviele Bilder sind denn in dem Array drin? Bedenke, dass Bitmaps nicht mehr komprimiert sind! Da hast du dann pro Bild 1024*786*32 ~ 3MB. Mit 5 Stück davon hast du die 15MB voll.

Noch zwei Hinweise seien vielleicht gestattet:
* Du kannst mit der Code-Umgebung Quellcode etwas leserlicher posten
* Methodennamen beginnen in Java mit einem kleinen Buchstaben, um sie leichter von Typen zu unterscheiden (ImageOperations vs imageOperations oder besser sogar createDrawableFromImageURL() ?)

Seid nett zueinander: AndroidPIT-Regeln ;)

Antworten
Andreas Lorenz
  • Forum-Beiträge: 111

19.02.2011, 05:46:47 via Website

Im Array befand sich bei Emulatortest zwei Bilder.
Würde es was bringen, wenn ich das Bild direkt aus dem InputStream in eine Datei schreibe und das Bild natürlich vorher verkleinern?

Wegen Hinweise:
* ich hab die Code-Brackets benutzt aber es funktionierte nicht.
* Danke für den Hinweis auf die Java Konvention, werde das dann ändern.

Antworten
Gelöschter Account
  • Forum-Beiträge: 107

19.02.2011, 08:25:08 via Website

Es kommt ganz darauf an was du vor hast. Grundsätzlich ist ein Smartphone ein Gerät mit eingeschränkten Ressourcen. Das betrifft nicht nur den Speicher sondern auch die CPU und vor allem die Batterie. Hinzu kommen noch sehr dünne Anschlüsse ans Netz. Wenn du die Möglichkeit hast, lege die Bilder besser schon in der richtigen Auflösung auf den Server. So sparst du CPU-Zeit und die Nutzer der App müssen nicht ewig auf die Bilder warten und die Knete an den Data-Provider abdrücken. Du könntest die Bilder auf von einem Script auf dem Server beim Abrufen verkleinern lassen.

Wie du die Bilder auf dem Gerät behandelst hängt ganz davon ab was du damit vor hast. Wenn du immer nur eines sehen willst, ist es vielleicht auch nicht falsch, sich vom Server nur das Listing zu holen und erst beim Anzeigen das Bild abzurufen. Zusätzlich kannst du die Bilder lokal auf der SD-Card speichern, die SD sozusagen als Cache benutzen. Willst du mehr davon anzeigen, bleibt dir nicht viel mehr übrig, als sie lokal im Speicher zu halten. BTW: Java bietet für Arrays Convenience-Klassen an. ArrayList für sortierte Arrays, Vektor für Arrays mit dynamischer Größe, HashSet für Listen, in denen ein Objekt garantiert nur ein mal vorkommt... Die machen den Umgang mit solchen Konstrukten sicherer, weil du dich nicht immer um die Größe kümmern musst. Weitere Stichworte zum Lesen: Iterator, Generics, foreach (http://download.oracle.com/javase/1.5.0/docs/guide/language/foreach.html) .

— geändert am 19.02.2011, 08:30:12

Seid nett zueinander: AndroidPIT-Regeln ;)

Antworten
Gelöschter Account
  • Forum-Beiträge: 107

19.02.2011, 09:34:18 via Website

Rafael K.
Carsten Rose
ArrayList für sortierte Arrays .
Freiwillig sortiert die sich auch nicht :)

*hehe* ja hab mich ungeschickt ausgedrückt. Ich meinte dass die Reihenfolge der Elemente gleich bleibt. Das ist bei Sets zb nicht gegeben. Wenn es dir wichtig ist das ein Element beim Iterieren immer an gleicher Stelle kommt, brauchst du ArrayLists. Vector und ArrayList unterscheiden sich dafür in der Optimierung bei der Geschwindigkeit beim Direktzugriff und dem Iterieren.

Und dynamisch in der Größe sind (fast) alle Unterklassen von Collection und List...nicht nur der Vektor
Die Änderung der Größe ist bei ArrayList teurer als bei Vector.

— geändert am 19.02.2011, 09:38:13

Seid nett zueinander: AndroidPIT-Regeln ;)

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

19.02.2011, 09:52:28 via Website

Carsten Rose
Und dynamisch in der Größe sind (fast) alle Unterklassen von Collection und List...nicht nur der Vektor
Die Änderung der Größe ist bei ArrayList teurer als bei Vector.
Und der Vektor ist im Gegensatz zu den meisten anderen Listen Thread-safe, da synchronized.

Ich wollt die Formulierung halt nicht so stehen lassen, weil die Newbies die hier mitlesen, das alles sonst evtl. falsch verinnerlichen.
Dass DU das weißt ist mir klar ;)

Antworten
Andreas Lorenz
  • Forum-Beiträge: 111

20.02.2011, 11:30:34 via Website

Danke für die Tipps.
Werde meine Gallerie noch mal überdenken.

Ich habe irgendwo gelesen, dass StringArrays ziemlich speicherintensiv sind. Stimmt das so und ist es dann besser CharSequence zu verwenden?

Antworten
Gelöschter Account
  • Forum-Beiträge: 107

20.02.2011, 12:58:38 via Website

Andreas L.
Ich habe irgendwo gelesen, dass StringArrays ziemlich speicherintensiv sind. Stimmt das so und ist es dann besser CharSequence zu verwenden?

Laut dem hier ist CharSequence ein Interface das von String implementiert wird. Es gibt also keinen Unterschied:

http://download.oracle.com/javase/1.4.2/docs/api/java/lang/String.html

— geändert am 20.02.2011, 13:02:25

Seid nett zueinander: AndroidPIT-Regeln ;)

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

21.02.2011, 09:56:53 via Website

Ich denke du meinst das hier:
https://www.nextpit.de/de/android/forum/thread/418273/Speicherverbrauch-von-Strings-VS-16MB-RAM-Limit-pro-App

Das Problem tritt nur bei SEHR langen TEMPORÄREN Strings auf in Verbindung mit substring()

In solchen Fällen sollte man immer folgedes machen:

1String subString = new String( orgString.substring(x,y) );

So wird auf jeden Fall ein neues String erzeugt, der intern einen neuen CharArray benutzt und keine Referenz mehr zum Orginal String besitzt.
Damit kann der temporäre String vom GC erfasst werden und müllt nicht den Speicher voll.

Antworten
Andreas Lorenz
  • Forum-Beiträge: 111

21.02.2011, 10:11:41 via Website

Danke für zahlreiche Hilfe und Denkanstösse.

Ich habe jetzt ein wenig experimentiert und bin momentan auf folgende Lösung gekommen.

Ich habe die Gallerie in ein eigenes Activity ausgelagert. Das Problem war bei mir, dass ich verschiedene Galleriekategorien hatte und der User konnte, mittels Spinner, zwischen den Kategorien wechseln. Dazu war die Gallerie am Ende zu überladen und es kam zu einem OutOfMemoryError.

Ich lasse die Kategorien jetzt mittels ListView anzeigen, wenn der User seine Wahl getroffen hat wird die Gallerie im neuen Activity angezeigt.
Um eine andere Kategorie zu sehen muss der User zurück zur Auswahl. Dabei wird die Gallerie "zerstört" und ich teile dem GarbageCollector mit, dass er in Aktion treten kann. So hat es auf jeden Fall die Performance um einiges gesteigert.
Weiterhin kommt es ab und an immer noch zum OutOfMemoryError. Ich fange aber diesen Fehler ab und lasse die Bilder die bereits geladen wurden trotzdem anzeigen. Zumindest werden von 20 Bilder im Durchschnitt 19 Bilder geladen ohne eine "Force Close" Meldung zu erhalten. Auf realen Geräten steht wohl mehr Speicher zu Verfügung als bei dem Emulator. Auf einem Galaxy S gab es zum Beispiel keine Probleme.

Ich denke mir mal, dass ich die Bilder auf dem Server verkleinern werde, damit der Speicher auf jedenfall reicht.

Ich habe während der Lösungssuche viel über mobile Systeme dazu gelernt. Ich muss dazu sagen, dass ich erst vor kurzem mit Androidentwicklung angefangen habe.

— geändert am 21.02.2011, 10:13:25

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

21.02.2011, 10:20:35 via Website

Andreas L.
Weiterhin kommt es ab und an immer noch zum OutOfMemoryError. Ich fange aber diesen Fehler ab und lasse die Bilder die bereits geladen wurden trotzdem anzeigen. Zumindest werden von 20 Bilder im Durchschnitt 19 Bilder geladen ohne eine "Force Close" Meldung zu erhalten. Auf realen Geräten steht wohl mehr Speicher zu Verfügung als bei dem Emulator. Auf einem Galaxy S gab es zum Beispiel keine Probleme.

Das solltest du auf KEINEN FALL machen !!!

In Java werden Fehler absichtlich in 2 Klassen unterteilt.
Die einen erben von Exception und sind behandelbar, die anderen erben von Error und sind NICHT BEHANDELBAR! Bzw. sollten nicht abgefangen werden, um das Programm fortzusetzen.

Nach einem OutOfMemoryError befindet sich die VM in einem undefinierten Zustand, weil eine essenziell wichtige Operation fehlgeschlagen ist.
Man kann nicht vorhersagen wie sich das Programm nach einem solchen Fehler verhält.

Besser wäre:
Hol dir mit Runtime.getRuntime() den aktuellen Speicherstatus.
Es gibt dort 3 Methoden dafür. Wenn du feststellt, dass der Speicher nicht mehr reicht, um weitere Bilder zu laden, dann brich die Aktion ab, damit es garnicht erst zu dem Fehler kommt.

Warum es beim Galaxy geht und bei anderen nicht: Das Galaxy hat eine max. VM Größe von 48MB ... Standard ist 16 ... und wenn man Apps anbietet, sollte man für diesen Worst-Case entwickeln.

Antworten
Andreas Lorenz
  • Forum-Beiträge: 111

21.02.2011, 10:33:24 via Website

Hallo Rafael,

hab mich ein wenig falsch ausgedrückt. Mir ist bewusst, dass Errors nicht abgefangen werden sollen. Das Abfangen war bis jetzt nur zum Testen gedacht.
Aber danke für den Hinweis, ich schau mir deinen Tipp auf jeden Fall an. Die App dient zum Selbststudium (bin auch schließlich Student ;)) und wird es nicht in den Market schaffen.

Antworten
Gelöschter Account
  • Forum-Beiträge: 107

21.02.2011, 20:43:30 via Website

Bevor hier jemand den anderen Thread nicht liest: Man sollte *nicht* immer bei einem str.substr() einen neuen String anlegen, sondern nur, wenn man garantiert nur noch den Substring weiterverarbeiten will und der ganze String bedeutungslos geworden ist. Sonst verschenkt man wieder Performance und legt unnötig Speicherkopien an. ;)

Seid nett zueinander: AndroidPIT-Regeln ;)

Antworten