Auslesen von Json Daten effizienter geschalten möglich?

  • Antworten:30
Christopher
  • Forum-Beiträge: 38

25.08.2011, 09:41:25 via Website

Hallo,

ich habe folgendes Problem:
Ich muss aus zwei Json Datein jede Menge Daten (ca. 2000 Einträge) einlesen, und diese dann in einer Listview darstellen.
Ich habe in einer Datei unteranderem die Nummer von einem jeweiligen Objekt, und die Referenz zu dem dazugehörigen Text. Dieser befindet sich in der zweiten Json Datei und wird jedes mal mit ausgelesen. Gemacht habe ich das so, weil ich mehrere Sprachen unterstützen will.

dh meine erste json Datei hat folgende Struktur:
1{
2 "parameter":
3{
4"nr":"r000002",
5"label":"000002",
6...
7},
8{
9"nr":"p000003",
10"label":"000003",
11...
12}
13...

und die zweite sieht folgendermaßen aus:

1{
2 "label":
3 {
4 "name":"000003",
5 "string":"name1"
6 },
7 {
8 "name":"000004",
9 "string":"name2"
10 },
11 {
12 "name":"000010",
13 "string":"name3"
14 }
15...

Ich lese zuerst mittels Buffered Reader die json Dateien ein:
1InputStream is = this.getResources().openRawResource(R.raw.json_para);
2 ArrayList<HashMap<String, String>> mylist = new ArrayList<HashMap<String, String>>();
3 JSONObject joPara = null;
4 JSONObject joLang = null;
5 BufferedReader br = new BufferedReader(new InputStreamReader(is));
6 StringBuilder sb = new StringBuilder();
7 String line = null;
8
9 try {
10 while ((line = br.readLine()) != null) {
11 sb.append(line + "\n");
12 }
13 is.close();
14 br.close();
15 joPara = new JSONObject(sb.toString());
16
17 } catch (Exception e) {
18 Log.e("Error", "Fehler beim Parameter json Objekt erzeugen " + e.toString());
19 }

Das mache ich für beide Json Dateien. Was auch noch recht zügig klappt.

Dannach füge ich die Daten so wie sie nachher dargestellt werden sollen in eine Arraylist ein. Und hier schau ich bei jedem Eintrag nach wie der dazugehörige Name lautet, was ziemlich viel Zeit frisst.
Der Code dazu:

1try {
2
3 JSONArray parameter = joPara.getJSONArray("parameter");
4 for (int i = 0; i < parameter.length(); i++) {
5 HashMap<String, String> map = new HashMap<String, String>();
6 JSONObject e = parameter.getJSONObject(i);
7 String.valueOf(i);
8 map.put("nr", e.getString("nr"));
9 map.put("name", findName.find(e.getString("label"), joLang));
10 map.put("help", e.getString("help"));
11 mylist.add(map);
12 }
13 } catch (JSONException e) {
14 Log.e("log_tag", "Error parsing data " + e.toString());
15 }
16 adapter = new SimpleAdapter(this, mylist, R.layout.listview_layout,
17 new String[] { "nr", "name" }, new int[] { R.id.lv_nr,
18 R.id.lv_titel });
die findName() Methode sieht so aus:
1public static String find(String s, JSONObject jo) {
2
3
4
5 try {
6
7 JSONArray label = jo.getJSONArray("label");
8
9 for (int i = 0; i < label.length(); i++) {
10
11 JSONObject e = label.getJSONObject(i);
12 String vergleich = e.getString("name");
13 if (vergleich.equals(s))
14 return e.getString("string");
15 }
16 } catch (JSONException e) {
17 Log.e("log_tag", "Error parsing data " + e.toString());
18 }
19
20 return "no data" ;
21 }

Ich bin für jeden Ratschlag wie mach das ganze performanter gestalten kann dankbar!

Viele Grüße

Chris

— geändert am 25.08.2011, 09:42:05

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

25.08.2011, 10:28:41 via Website

Ganz einfache Idee:

Schreib die Daten für die findName Geschichte vor der Verarbeitung einmal in eine HashMap,
dann dauert jede Abfrage nur noch einen Bruchteil der Zeit.

[ O(log n), statt O(n) wie bei der linearen Suche momentan ]

— geändert am 25.08.2011, 10:29:27

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 11:07:00 via Website

Danke!
Habs gleich umgesetzt und siehe da es geht mindestens doppelt so schnell :).
Gibt es denn auch komplett andere Wege wie man Json Datein schnell laden könnte?

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

25.08.2011, 11:22:40 via Website

Um ehrlich zu sein, finde ich JSON bei JavaScript Anwendungen sehr sinnvoll, weil man die Daten völlig ohne Programmieraufwand in mehrdimensionale Arrays und Maps übertragen kann.

Bei Java hat man diesen Vorteil zwar nicht direkt, aber ich glaube auch hier macht es Sinn die Daten vorher in eine Datenstruktur zu übertragen, die zur Anwendung passt. Evtl. gibts ja auch schon Klassen, die das automatisch machen? Ich kenn die API an der Stelle nicht.

Sind es einfach nur tabellarische Daten -> mehrdimensionaler Array
Sind es Schlüssel-Wert-Paare wie bei Dir -> HashMap

Das kann man natürlich auch kombinieren.

Antworten
Stefan S.
  • Forum-Beiträge: 560

25.08.2011, 11:56:25 via Website

Hat zwar nichts mit Json zu tun aber ich verwende WebServices für die Kommunikation mit meinem Server.

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 12:03:56 via Website

Die Daten liegen bei mir nicht auf einem Server, sondern auf dem Gerät, von daher scheidet WebServices wohl aus.
In Java gibts schon extra JSON Klassen, die das an sich recht komfortabel machen. Ich denke das sind einfach zu viele Daten um das in ein paar ms darstellen zu können.

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

25.08.2011, 12:18:09 via Website

Ganz dumme Fragen:

* Die Daten liegen auf dem Gerät - wie kommen sie dort hin? Mit Deiner APK?

* Du sprichst von Mehrsprachigkeit - diese Dateien sind wohl nicht Sprachdateien, oder?

Warum ich das frage? Wenn Sie mit der APK ausgeliefert würden könnte man sie sicherlich vorab intelligenter aufbauen um diese schneller parsen zu können. Soll heißen: Die Rechenkraft wird mehr auf die Erzeugung der Daten verlagert statt dies von jedem User immer wieder auf jedem Device machen zu lassen.

Für die Mehrsprachigkeit gibt es wahrlich bessere Lösungen unter Android (Stichwort Resourcen).

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 12:58:16 via Website

Ja die Daten sind im raw ordner drinnen.
In der App an sich verwende ich auch den value Ordner um die Zweisprachigkeit zu gewährleisten. Bei den Daten (das sind Parameterdaten von einem Gerät) benutze ich für jede Sprache eine extra Json Datei.
Im groben geht es darum von verschiedenen Geräten, von denen immer eine Parameter Datei existiert die richtige zu laden und anzuzeigen und später einmal von dem Gerät die Werte die in den jeweiligen Parametern gesetzt sind runterzuladen und zu verändern.
Wenn du vorschläge hast wie ich sowas schneller und performanter gestalten kann bin ich für jeden Ratschlag dankbar!

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

25.08.2011, 13:04:29 via Website

Solange es nur key-value-pairs bleiben wäre die primitivste Lösung eine Properties Datei oder ResourceBundles.
Damit hast du nämlich direkt eine Datenstruktur, die den Zugriff über Schlüssel erlaubt.

http://download.oracle.com/javase/6/docs/api/java/util/Properties.html

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 13:48:30 via Website

Hm, so wie ich das sehe müsste ich dann meine ganzen Daten in eine xml Datei speichern. Ist das bei vielleicht 100 Datensätzen mit jeweils mehr als 2000 Einträgen effizient genug? Pro Parameter sind ja auch noch an die 10 Einträge vorhanden, die dann teilweise nochmal Arrays und andere Werte enthalten.

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

25.08.2011, 14:03:26 via Website

OK also doch komplexe Daten :)

Man kann es in XML machen, was aber auch einen gewissen Overhead zur Folge hat.
Wahrscheinlich sogar mehr als die JSON Geschichte.
Einziger Vorteil den ich da sehe ist die Abfrage über XPath.

Ansonsten bastel dir doch mal eben eine eigene Datenstruktur mit ein paar kleinen Javaklassen und lies die Daten da rein.
Dann arbeitest du nur noch auf dieser Datenstruktur weiter.

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 15:06:48 via Website

Das mal schnell basteln klingt für mich eher nach viel Arbeit. Ich habe erst vor zwei Wochen angefangen für Android zu programmieren. Und dementsprechend sind meine Fähigkeiten ;).
Ich denke ich belass es jetzt erstmal dabei. Momentan braucht es an die 5 sek bis alle Daten geladen sind. Und das ist glaubig noch verträglich. Aber ich werds im Hinterkopf behalten, wenn ich später noch was zu arbeiten suche ;).

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

25.08.2011, 15:16:03 via Website

5 Sekunden?

Das ist viel. Denk bitte daran das nicht alle Geräte gleich "motorisiert" sind. Je nachdem wo Du die Daten verarbeitest erhält der Nutzer dann nach 5-10 Sekunden den "Application not responding".

Um das zu umgehen würde ich ggfs. einen Lazy Load der Daten mittels einer Custom Adapter/ListView Combo einbauen. Dazu würde die ListView die ersten 25 oder so Einträge bereits kurz nach dem Beginn des Parsens erhalten und im Hintergrund munter weiterwerkeln. Mit jedem Scroll würde die ListView die nächsten 25 anfordern o.ä..

Gruß
Harald

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 15:31:26 via Website

Damit ich kein Application not responding bekomme habe ich das ganze in einen Thread gepackt. Und damit der Benutzer weiß wie lange das noch in etwa dauert einen Fortschritsbalken im Dialog dargestellt.
Mir wäre es eigentlich am liebsten eine Möglichkeit zu finden so schnell zu parsen, dass ich keine Wirkliche Verzögerung habe. Lazy Load ist für den Anwender ja auch nervig wenn er gerade zum 1400 Parameter will.

Antworten
Ansgar M
  • Forum-Beiträge: 1.544

25.08.2011, 15:51:50 via App

Nur mal als Vorschlag, wie wärs mit einer Datenbank? Oder hab ich etwas entscheidendes überlesen? :P
Lg Ansgar

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 16:05:01 via Website

Datenbank finde ich hört sich gut an. Aber wie muss ich mir das vorstellen?
Neben dem Zeitproblem habe ich nun jedoch entdeckt, das ich auch ein Speicherproblem bekomme. Wenn ich die Activity mehrmals hintereinander aufrufe, ist irgendwann mein RAM voll, und ich bekomme eine Exception. Gibt es irgendeine Möglichkeit beim verlassen der Activity alle Daten die dazu im Speicher abgelegt wurden zu löschen, damit das nicht passiert?

Antworten
Ansgar M
  • Forum-Beiträge: 1.544

25.08.2011, 19:23:17 via Website

Naja, du erstellst zuhause am PC eine SQLite Datenbank (zum Beispiel mit Sqliteman) und speicherst diese in deinem Asset-Ordner der App. Soweit ich weiß kann man dann auf die Datenbank auch lesend zugreifen ohne sie extra in den Daten-Ordner der App zu kopieren (ist aber auch kein Problem). Den Zugriff auf die Datenbank regelst du entweder mit einem sog. DatabaseHelper oder direkt über die SQLiteDatabase Klasse. Ein Speicherproblem solltest du damit übrigens auch nicht mehr haben, sofern du einen SimpleCursorAdapter für deine ListView nutzt..
Lg Ansgar
P.S. Natürlich kannst du beim Verlassen (was meistens erstmal onPause() aufruft) noch Daten leeren, oder alles andere. Sieh' dir hierfür am besten den ActivityLifecycle an.

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

25.08.2011, 21:08:59 via Website

Wenn ich die Activity mehrmals hintereinander aufrufe, ist irgendwann mein RAM voll, und ich bekomme eine Exception.

Das hört sich zunächst aber nach einem Leak an. Nur zum Verständnis: Du rufst die Activity mit startActivity oder startActivityForResult auf und gehst mit Back wieder raus. Das machst Du mehrfach hintereinander und bekommst ein Speicherproblem? Guck Dir mal den Lebenszyklus einer Activity an. Irgendwas machst Du falsch.

Antworten
Christopher
  • Forum-Beiträge: 38

25.08.2011, 23:48:55 via Website

ja genau so wie dus beschrieben hast mache ich das: ich rufe die Activty mit startActivity auf und wenn ich jetzt mit der back Taste ins Menü zurück gehe und sie ein paar mal neu starte krieg ich ein Speicherproblem.
Den Lebenszyklus kenne ich, aber wie gestalte ich das so das beim verlassen der Activity der Speicher wieder frei wird. Muss ich da in die onPause() Methode was rein schreiben?
Die Daten dürfen jedoch nur gelöscht werden, wenn ich aus der Activity mit dem Listview in die vorherige gehe. Wenn ich auf einen Eintrag aus der Listview gehe komme ich in eine neue Activity, die Details zu dem Eintrag anzeigt. Wenn ich aus der zurückgehe muss die Listview Activity ohne Verzögerung wieder da sein.
Ich weiß das war nun komisch beschrieben, ich hoffe man verstehts trozdem.

Antworten
Ansgar M
  • Forum-Beiträge: 1.544

26.08.2011, 07:07:45 via App

Hey, du kannst mit onBackPressed() die Zurücktaste anfangen. Ja, die Methoden überschreiben, so wie die onCreate. Es gibt auch isFinishing() worüber du erfahren kannst, ob die Activity vom User geschlossen wird.
Lg Ansgar

Antworten
Christopher
  • Forum-Beiträge: 38

26.08.2011, 07:19:21 via Website

mir ist schon klar das ich die überschreiben muss, aber was schreib ich in die onPause oder onFinish Methode rein damit mein Speicher wieder freigegeben wird? oder kann ich in die onBackPressed() Methode die onDestroy rein schreiben und er löscht dann automatisch alles aus dem Speicher?

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

26.08.2011, 08:07:20 via Website

Zu wenig Infos.

* Welche Activity kracht? Die Liste oder die Seite mit den Details? Ich vermute die Details.

* Welche Daten werden "rüber transportiert" und wie?

* Welche Daten legst Du in den onCreate/onStart/onResume Mehoden der Detail Seite an und welche gibst Du in onStop/onDestory/onPause wieder frei?

Gruß
Harald

Antworten
Christopher
  • Forum-Beiträge: 38

26.08.2011, 08:22:20 via Website

Also das ganz ist so aufgebaut:

HauptmenüActivity -> ListActivity -> DetailActivity

wenn ich von der ListActivity zurück ins Hauptmenü gehe und wieder in die Listactivty und das ganze ein paar mal hintereinander bekomme ich eine Exception weil der Speicher voll ist.

Von der ListActivity in die DetailActivity zu wechseln gibt kein Problem. Und wenn ich von der DetailActivity zurück zur ListActivity gehe sind auch noch alle Daten geladen. Also ich kann wieder direkt die Liste durchscrollen.

Wenn ich dagegen aus der ListActivity zurück ins Hauptmenü gehe und dann wieder in die ListActivity muss die Liste komplett neu geladen werden.
Ich weiß zwar nicht genau warum die ListActivity die Daten beim wechseln auf die DatailActivity behält, (vermute weil die "über" ihr liegt oder?´) aber an sich soll das genauso sein.

Wenn ich von der ListActivity auf die DetailActivity wechsle geb ich die Daten mit einem Intent weiter.

1Intent intent = new Intent(Parameter_Alle.this,Parameter_Detail.class);
2 intent.putExtra("nr", nr);
3 intent.putExtra("hilfe", hilfe);
4 startActivity(intent);

onStop, onPause und onDestroy machen bisher noch gar nichts, weil ich nicht weiß was ich da rein schreiben soll.

Wo wir schon bei meinen ganzen Problemen sind ;) Ein weiteres welches ich habe ist, dass ich ein eigenes Icon gemacht habe, das ich auch in drei Auflösungen in die richtigen Ordner abgespeichert habe, aber auf meinem Handy wird trozdem nur der Standard Icon dargestellt. Wenn ich im Simulator starte jedoch der richtige. Im Manifest hab ich auch den richtigen angegeben. Hat da jemand einen Tipp? (Wollt für sowas nicht extra einen extra Thread aufmachen)

— geändert am 26.08.2011, 08:28:26

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

26.08.2011, 11:44:03 via Website

Also geht es um den Lebensyklus der ListActivity.

Schau Dir bitte hier die Datenhaltung an. Du legst wahrscheinlich im onCreate/onResume/onStart irgendwelche Arrays etc. an (oder einen Adapter der das macht). Dann musst Du natürlich im entsprechenden Partner onDestroy/onPause/onStop/ diese Daten wieder freigeben.

Das zu analysieren ist für einen Außenstehenden ohne Einblick in den Code schlichtweg unmöglich. Bevor Du jetzt aber große Codeblöcke postest schau Dir das bitte zunächst selbst mal an.

Normalerweise hält man diese Daten (na ja, die Pointer darauf) in irgendwelchen privaten Variablen. Irgendetwas stimmt da bei Dir nicht.


Zu einer anderen Anmerkung hier: Den onBackPressed musst Du normalerweise nicht abfangen. Von dort geht es den normalen Weg durch den Lebenszyklus. Du musst auch kein onDestroy o.ä. selbst aufrufen. onBackPressed wird normalerweise verwendet wenn man das Verhalten des Back ändern will. Ich habe in einer App zum Beispiel eine Dateiliste. Das ist natürlich für alle Verzeichnisse nur eine Activity. Wenn der Benutzer Back drückt oder wenn er ".." in der LstView drückt dann wird das gleich behandelt. Das Back verlässt dann in diesem Fall die Activity nicht sondern führt ihn in der Liste einen Schritt zurück - er bleibt aber auf der selben Activity in der nur der Datencontainer geändert wurde (nur ein Beispiel).

Antworten
Christopher
  • Forum-Beiträge: 38

27.08.2011, 17:11:37 via Website

ich lege in der Parameter_Alle Activity (das ist die Liste) folgende Daten an:

1public class Parameter_Alle extends ListActivity {
2 public static HashMap<String, String> listLang = new HashMap<String, String>();
3 public ArrayList<HashMap<String, Object>> aListPara = new ArrayList<HashMap<String, Object>>();
4 ListView lv = null;
5 private ProgressDialog pd;
6 ListAdapter adapter;
7
8 @Override
9 public void onCreate(Bundle savedInstanceState) {
10 pd = new ProgressDialog(this);
11 new loader().execute();


in der finish() Methode mache ich folgendes:

1public void finish() {
2 listLang = null;
3 aListPara = null;
4 lv = null;
5 adapter = null;
6 System.gc();
7 super.finish();
8 }

Alle anderen Objekte werden in einem AsyncTask angelegt, oder in Klassen, die im AsyncTask aufgerufen werden. Aber die Daten müssten nach Abschluss des AsyncTask doch alleine freigegeben werden oder? Auf jeden Fall bekomme ich noch immer eine OutOfMemory Exception wenn ich mehrmals hintereinander die ListActivity neu lade und habe keine Ahnung was ich noch tun soll...

— geändert am 27.08.2011, 17:12:43

Antworten
Christopher
  • Forum-Beiträge: 38

28.08.2011, 22:09:56 via Website

okay ich habe nun eine Lösung für mein Problem gefunden.
Wenn ich in der finish() Methode System.exit(0) aufrufe geht alles einwandfrei. Aber so wie ich gelesen habe ist das keine alt zu elegante Lösung?
Weiß jemand ob man in Eclipse irgendwie den Telefonspeicher analysieren kann? Das fände ich extrem nützlich.

— geändert am 28.08.2011, 22:37:10

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

29.08.2011, 08:51:56 via Website

Ich seh da schon wieder das böse static ...

Du definierst die HashMap als static.
Damit übersteht die Instanz sogar App Neustarts, wenn sie nicht zu lange auseinanderliegen.
Dann ist es doch klar, dass die mit jedem Ladevorganz weiter aufgebläht wird und irgendwann der Speicher voll ist.

Leute echt...lasst die Finger von static...das ist evil :D

— geändert am 29.08.2011, 08:53:36

Ansgar M

Antworten
Christopher
  • Forum-Beiträge: 38

29.08.2011, 09:16:13 via Website

ja ich sehe ich muss noch viel lernen ;)... weiß gar nicht wieso ich das überhaupt mal auf static gesetzt hab.
Methoden darf ich aber schon als public static schreiben oder?
Danke für die vielen Tipps!

— geändert am 29.08.2011, 09:20:17

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

29.08.2011, 09:32:18 via Website

Christopher
Methoden darf ich aber schon als public static schreiben oder?
Nur wenn es auch Sinn macht.

Ich mache idR nur Hilfsmethoden static, die zustandslos sind, also die Parameter reinbekommen und ein Ergebnis direkt zurückliefern, ohne Werte in der Klasse zwischenzuspeichern.
Auch bei Singletons macht man die getInstance() Methode zwangsläufig static, aber ansonsten würde ich echt versuchen darauf komplett zu verzichten, weil man vor allem als Anfänger dazu neigt damit quick&dirty konzeptionelle Mankos seiner Programme zu beheben, nur um dann in noch viel größere Probleme zu laufen, die sich dann auch extrem schwer nachvollziehen lassen.

Antworten
Christopher
  • Forum-Beiträge: 38

29.08.2011, 09:50:07 via Website

okay, das hört sich schonmal gut an ;)
Eine andere Frage hätte ich noch zum BufferedReader, wenn ich mein Programm in der Virtuellen Maschine laufen lasse schmiert es immer ab, wenn ich bei der Stelle while ((line = br.readLine()) != null) bin. Also es wirft eine IO.Exception. Auf meinem Handy läuft das aber problemlos. Irgendwie jagt hier ein Problem das andere...

1public static HashMap<String, String> load(String language, Resources res) {
2 InputStream isLang = null;
3 if (language.equals("de"))
4 isLang = res.openRawResource(R.raw.g120_052_v4_4_lang_de);
5 else if (language.equals("en"))
6 isLang = res.openRawResource(R.raw.g120_052_v4_4_lang_en);
7 try {
8 StringBuilder sb = new StringBuilder();
9 String line = null;
10
11 BufferedReader br = new BufferedReader(new InputStreamReader(isLang));
12
13 while ((line = br.readLine()) != null)
14 {sb.append(line + "\n");
15 }

Antworten