1. Nimm jetzt an unserem 2. ADVENT-Gewinnspiel teil - Alle Informationen findest Du hier!

AsyncTask verursacht OutOfMemory Exception

Dieses Thema im Forum "Android App Entwicklung" wurde erstellt von samarek, 16.11.2011.

  1. samarek, 16.11.2011 #1
    samarek

    samarek Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    101
    Erhaltene Danke:
    2
    Registriert seit:
    24.10.2011
    Hi,

    ich hab folgendes Problem und zwar ...

    führe ich beim start meiner App einen AsyncTask aus, der Daten aus einer fast 6mb großen JSON-Datei liest und die in eine Datenbank schreibt.
    Wobei die App dann nach nicht allzulanger Zeit eine absehbare "OutOfMemoryException" auslöst.

    hier ist mal ein vergleichbarer Quellcodeteil (der echte ist noch einiges länger)
    Code:
    InputStream is = context.getResources().openRawResource(availibilityJson);
            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            StringBuilder alles = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null)
            {
                alles.append(line);
            }
            is.close();
            jsonArray = new JSONArray(alles.toString());
            for (int i = 0; i < jsonArray.length(); i++)
            {
                JSONObject obj = jsonArray.getJSONObject(i);
                String ean = obj.getString("EAN").trim();
                String vk6 = obj.getString("vk6").trim();
                Availability availability = new Availability(ean, vk6);
                Datenbank.availibilityEintragen(availability);
            }
    
    Meine Frage ist jetzt, wie kann ich diesen Code ein wenig "glätten" damit er nicht mit so großen Datenmengen hantieren muss?
    Also quasi 100 Datensätze lesen und in die Datenbank schreiben, dann wieder alles aus dem Speicher schmeissen und die nächsten 100 Datensätze verarbeiten.
     
  2. MichaelS, 16.11.2011 #2
    MichaelS

    MichaelS Fortgeschrittenes Mitglied

    Beiträge:
    370
    Erhaltene Danke:
    51
    Registriert seit:
    14.08.2009
    JSONObject zu nutzen bei so großen Datenmengen ist vllt nicht das richtige. Evtl. auf ein SAX Parser umstellen. und du solltest nicht unnütz "Availability" Objekte erzeugen um dise dann zu übergeben, kannst die Daten auch direkt in die DB eintragen. Was du noch probieren könntest, die Objekte wenn du sie nicht mehr brauchst auf NULL zu setzen und hoffe das der GC ordentlich aufräumt.

    Desweiteren solltest du ordentliche Variablen Namen verwenden und keine Mischung zwischen Deutsch und Englisch machen oO

    Gruß
    Michael
     
  3. Neeldarax, 16.11.2011 #3
    Neeldarax

    Neeldarax Erfahrener Benutzer

    Beiträge:
    170
    Erhaltene Danke:
    31
    Registriert seit:
    07.12.2010
    Hi samarek,

    was sagt den der logcat genau dazu? Also mich interessieren die GC_ Einträge zwischen dem Start und dem OFME.

    Hier gibt auch was zu lesen Designing for Performance | Android Developers
    Mir hatte es etwas geholfen.

    Wenn du dazu noch was nützliches erfährst, würde es mich auch interessieren. Auf solche Probleme stoße ich in letzter Zeit öfters :(

    regards
     
  4. samarek, 21.11.2011 #4
    samarek

    samarek Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    101
    Erhaltene Danke:
    2
    Registriert seit:
    24.10.2011
    Soo hab es jetzt hinbekommen, schien echt daran zuliegen dass er soviele Objekte erzeugen musste.

    Jetzt laufen alle 4 "Sachen aus JSON in die Datenbank schreiben" Tasks vernünftig durch.

    Allerdings hab ich jetzt das Problem dass das eintragen der 6mb Datei über 10 Minuten dauert, das läuft natürlich im Prinzip nur bei der ersten Initialisierung durch, aber ist dennoch viel zu lang.
    Weiss da vielleicht jemand Möglichkeiten wie sich das einlesen von JSON weiter optimieren lässt, also mindestens um die Hälfte müsste ich die Zeit schon noch drücken die das "in die Datenbank schreiben" im Moment braucht.
     
  5. Tom299, 22.11.2011 #5
    Tom299

    Tom299 Android-Experte

    Beiträge:
    602
    Erhaltene Danke:
    120
    Registriert seit:
    31.08.2011
    Ich weiß nicht, ob man bei Android und SQLite Prepared Statements und Transaktionen benutzen kann (hab es bis jetzt nicht benötigt).

    Aber im normalen Java mit JDBC hab ich bei Datenimporten durch Prepared Statements und Transaktionen schon öfter 50% Zeit gespart. Wäre sicherlich ein Versuch wert ;-)
     
  6. samarek, 22.11.2011 #6
    samarek

    samarek Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    101
    Erhaltene Danke:
    2
    Registriert seit:
    24.10.2011
    Ja, Android unterstützt Transaktionen und Prepared Statements

    Ich hab jetzt mit Hilfe von preparedStatements und Transaktionen das Ausführen aller 4 Tasks von ca. 15 Minuten auf 22 Sekunden (!!!) reduziert.
    Mehrfach getestet alle Daten sind da, Freunde sagen dass ist durchaus im Bereich des Möglichen, aber ich kann es nicht ganz glauben.

    Was sagt ihr dazu?
     
  7. Tom299, 22.11.2011 #7
    Tom299

    Tom299 Android-Experte

    Beiträge:
    602
    Erhaltene Danke:
    120
    Registriert seit:
    31.08.2011
    Na wenn du es mehrfach getestet hast und alle Daten da sind, dann wirds schon stimmen :D

    Kannst mal den Teil mit den Statements posten? Würd mich mal interessieren, wie der Code dazu aussieht.
     
  8. samarek, 22.11.2011 #8
    samarek

    samarek Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    101
    Erhaltene Danke:
    2
    Registriert seit:
    24.10.2011
    Hier mal die Kurzversion einer der Methoden, geht sicher noch schöner, aber bin erstmal froh dass es läuft

    Code:
    public static boolean availabilityEintragen(String ean, String vk6, int i, boolean letzter)
        {
            if ((i % 1000) == 0)
            {
                db.beginTransaction();
            }
            try
            {
                String sql = "INSERT INTO availability (EAN, vk6) VALUES (?, ?)"; 
                SQLiteStatement stmt = db.compileStatement(sql);
                stmt.bindString(1, ean);
                stmt.bindString(2, vk6);
                stmt.execute();
                if ((i % 1000) == 999 || letzter)
                {
                    db.setTransactionSuccessful();
                }
                return true;
            }
            catch (Exception exc)
            {
                Log.v("Exception", exc.getMessage());
                return false;
            }
            finally
            {
                if ((i % 1000) == 999 || letzter)
                {
                    db.endTransaction();
                }
            }
        }
    1000 Inserts werden zu einer Transaktion zusammengefasst und beim letzten Datensatz wird die laufende Transaktion beendet
     
    DieGoldeneMitte bedankt sich.
  9. Tom299, 22.11.2011 #9
    Tom299

    Tom299 Android-Experte

    Beiträge:
    602
    Erhaltene Danke:
    120
    Registriert seit:
    31.08.2011
    hmm, ich denke
    Code:
                String sql = "INSERT INTO availability (EAN, vk6) VALUES (?, ?)"; 
                SQLiteStatement stmt = db.compileStatement(sql);
    
    gehört nicht in die Schleife, weil du ja jedes mal das Statement neu erstellst. Glaub in die Schleife kommt nur noch das bind und das execute rein, oder?
     
  10. samarek, 22.11.2011 #10
    samarek

    samarek Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    101
    Erhaltene Danke:
    2
    Registriert seit:
    24.10.2011
    Die Methode wird in einer Schleife aufgerufen, in der Methode selber gibt es keine Schleife.
    Wäre es denn soviel sinnvoller das Statement und den String ausserhalb zu erzeugen und dann jedesmal an die Methode zu übergeben?
     
  11. Tom299, 22.11.2011 #11
    Tom299

    Tom299 Android-Experte

    Beiträge:
    602
    Erhaltene Danke:
    120
    Registriert seit:
    31.08.2011
    Deine Methode ist statisch, dann kannste doch auch die Variablen statisch machen. Fragst sie anfangs auf NULL ab, wenn NULL dann initialisieren, ansonsten nur noch bind, execute usw.

    Der Sinn von einem Prepared Statement sollte doch sein, daß man es nur 1 mal initialisiert und nicht in jedem Durchlauf. Zumindest hab ich das so immer verstanden ;-)
     
  12. samarek, 22.11.2011 #12
    samarek

    samarek Threadstarter Android-Hilfe.de Mitglied

    Beiträge:
    101
    Erhaltene Danke:
    2
    Registriert seit:
    24.10.2011
    Stimmt eigentlich, wobei mein primäres Ziel ja jetzt die Reduzierung der Laufzeit war und das habe ich so ja erreicht

    ... aber vielleicht krieg ich es ja noch von 22 Sekunden auf 20 Sekunden gedrückt ;)
     

Diese Seite empfehlen