Zugriff auf Online-mySQL-Datenbank von Android-App

  • Antworten:28
  • Bentwortet
Sebastian P.
  • Forum-Beiträge: 72

01.02.2013, 17:10:28 via Website

Hallo,

ich bin neu in der Android-Entwicklung und versuche, in meine App einen Datenbankzugriff einzubauen.
Dieser soll aus einer Webdatenbank (mySql) entsprechende Daten auslesen. Auf der Website, die die gleiche Datenbank verwendet, sieht das so aus:

1<?PHP
2 $dbhost = "localhost";
3 $dbname = "usr_web512_4";
4 $query = "SELECT * FROM `news_list` ORDER BY `id` DESC";
5 mysql_connect($dbhost, '******', '********');
6 mysql_select_db($dbname);
7 //...
8?>

Die Daten sollen von der App abgefragt werden und dann in einem TableLayout angezeigt werden.
Wie kann ich das realisieren?

Antworten
Christian
  • Forum-Beiträge: 307

01.02.2013, 18:09:55 via Website

Hi Sebastian,

du könntest dein PHP-Script aus dem App per HTTP-GET oder HTTP-POST ansprechen. Das Script holt dann die Daten aus der DB und schreibt das Erbebnis in ne XML oder JSON. Diese gibst du dann als Antwort an dein Handy zurück. Dort parst du dann die XML oder JSON und zeigst die Daten auf dem Bildschirm an.

Mfg Christian

Antworten
Sebastian P.
  • Forum-Beiträge: 72

01.02.2013, 18:29:46 via Website

Danke, aber genau das hatte ich schon in einigen anderen Threads hier gelesen. Wie genau ich die Rückgabe realisieren soll, verstehe ich aber nicht.

Antworten
Christian
  • Forum-Beiträge: 307

01.02.2013, 18:45:05 via Website

Hi,

vielleicht wird es mit einem kleinem Beispiel klarer.

Aufruf einer Url mit Get-Parametern:

1http://www.meine_homepage.de/erstelleXML.php?name=Christian&zuname=Hempe

Das Script(erstelleXML.php) nimmt nun die Anfrage entgegen und baut die Antwort zusammen:
1<?php
2 echo "<xml><name>" + $_GET["name"] + "</name><zuname>"+ $_GET["zuname"] +"</zuname></xml>";
3?>

Anrtwort an dein App:
1<xml><name>Christian</name><zuname>Hempe</zuname></xml>

Verstehst du was ich meine?

Mfg Christian

— geändert am 01.02.2013, 18:47:14

Antworten
Sebastian P.
  • Forum-Beiträge: 72

01.02.2013, 18:47:42 via Website

Das ist mir schon klar, mir geht es eher darum, das XML-Skript an die App zurückzugeben und dort zu verarbeiten.

(Btw. das erste </zuname> sollte ohne / sein oder?)

Antworten
Christian
  • Forum-Beiträge: 307

01.02.2013, 18:51:47 via Website

na die XML parst du dann mit einem Parser die Standards dafür sind SAX, DOM und unter Android gibt es noch XmlPullParser

(Btw. das erste </zuname> sollte ohne / sein oder?)
Ja natürlich. Tippfehler :) habs geändert

— geändert am 01.02.2013, 18:54:06

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 14:09:00 via Website

Soweit schonmal danke.
Jetzt besteht noch die Frage, wie genau (Welche Java/Android-Klassen/Funktionen) ich das PHP-Skript anspreche und die Antwort an den Parser weiterleite.

EDIT: Ich hoffe es macht nichts, wenn die auszulesenden Daten HTML-Tags enthalten (aktuell: <ul>,<li>,<br/>)

— geändert am 02.02.2013, 14:31:21

Antworten
Christian
  • Forum-Beiträge: 307

02.02.2013, 14:39:28 via Website

Hi,

am einfachsten wäre das hier:

Get Beispiel:
1URL url = new URL("http://www.meine_homepage.de/erstelleXML.php?name=Christian&zuname=Hempe");
2InputStream is = url.openStream();

Post Beispiel:
1String body = "name=" + URLEncoder.encode( "Christian", "UTF-8" ) + "&" +"zuname=" + URLEncoder.encode( "Hempe", "UTF-8" );
2
3URL url = new URL( "http://www.meine_homepage.de/erstelleXML.php" );
4HttpURLConnection connection = (HttpURLConnection) url.openConnection();
5connection.setRequestMethod( "POST" );
6connection.setDoInput( true );
7connection.setDoOutput( true );
8connection.setUseCaches( false );
9connection.setRequestProperty( "Content-Type","application/x-www-form-urlencoded" );
10connection.setRequestProperty( "Content-Length", String.valueOf(body.length()) );
11
12OutputStreamWriter writer = new OutputStreamWriter( connection.getOutputStream() );
13writer.write( body );
14writer.flush();
15
16BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()) );
Beispiel aus "Java ist auch eine Insel"

den jeweiligen Parser fütterst du nun mit dem Inputstream und abgeht es.

mfg Christian

p.s. Da das scheinbar immer wieder zu Problem führt sei es hier nochmal gesagt ab der Android Version 3.0 müssen Netzwerkaufrufe in einem separaten Thread laufen ist das nicht so wird ne Exception von System geworfen

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 14:49:42 via Website

Also Multithreading oder einfach ne seperate Funktion?

Antworten
Christian
  • Forum-Beiträge: 307

02.02.2013, 14:54:28 via Website

Wenn du mit "Mulitthreading" meinst das du aus dem Main - bzw UI-Thread einen anderen Thread startest dann ja.

— geändert am 02.02.2013, 14:54:49

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 15:37:51 via Website

Danke, ich werde es mal austesten. Jedoch habe ich noch folgendes Problem:

1try {
2 if (!InetAddress.getByName("( http: )//www (.) freakhall (.) ch/").isReachable(10000)) {
3 throw new TimeoutException();
4 }
5}

resultiert in eine Unknown Host Exception, wenn ich nur "(www.)freakhall.ch" (Die anzusprechende Seite) verwende, eine Timeout Exception.

PS: Ich habe im Manifest <uses-permission android:name="android.permission.INTERNET" /> verwendet, sollte noch eine andere Permission bemötigt werden, bitte ich auch hier um Hilfe.

PS: Die Klammern sind wegen dem Linkverbot

— geändert am 02.02.2013, 15:38:42

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 15:51:33 via Website

Das Problem ist gelöst: Habe eine Alternative bei stackoverflow gefunden. Und das Multithreading kann ich vorerst umgehen da ich Android 2.3.6 verwende (Gibt kein Update mehr :( )

Antworten
Christian
  • Forum-Beiträge: 307

02.02.2013, 16:15:58 via Website

nd das Multithreading kann ich vorerst umgehen da ich Android 2.3.6 verwende
das solltest du nicht machen.
Wenn das Abarbeiten der Netzwerkfunktion zu lange dauert (zbsp. schlechte Datenverbindung) erhält dein User eine Nachricht in Form eines Dialogs.
Dieser sagt ihm das irgendwas mit der Anwendung nicht stimmt und gibt ihm dann die Möglichkeit deine App zu beenden oder zu warten bis deine App fertig ist mit dem was sie da macht.

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 17:37:50 via Website

Das Programm gibt folgenden Fehler im LogCat aus:

102-02 17:26:51.906: E/NewsScreen /parser(13734): Error: org.xmlpull.v1.XmlPullParserException: Error parsing document. (position:line -1, column -1) caused by: org.apache.harmony.xml.ExpatParser$ParseException: At line 3, column 28: not well-formed (invalid token)

Die XML sieht so aus:

1<xml>
2 <entry>
3 <titel>Küchenumbau am 19.01.2013</titel>
4 <text>Am 19.Januar haben wir die Küche umgebaut:<br />
5<!--Rest weggelassen-->

Line 3 column 28 ist das Ende des <titel>-Tags. Der entsprechende Code folgt:

1public List<Entry> entries = new List<Entry>();
2//...
3try {
4
5 XmlPullParser parser = Xml.newPullParser();
6 parser.setInput(is, null);
7 //parser.require(XmlPullParser.START_TAG, null, "entry");
8 while (parser.next() != XmlPullParser.END_TAG) {
9 if (parser.getEventType() != XmlPullParser.END_TAG)
10 continue;
11 String name = parser.getName();
12 if (name.equalsIgnoreCase("entry")) {
13 entries.add(Entry.readEntry(parser));
14 } else {
15 Entry.skip(parser);
16 }
17 }
18
19 } catch (Throwable t) {
20 Log.e("NewsScreen /parser", "Error: "+ t.toString());
21 return 2;
22 }

Der Code ist von der developer DOT android DOT com-Seite kopiert und entsprechend abgeändert. Die Entry-Klasse ist ebenfalls von dort.
developer DOT android DOT com/training/basics/network-ops/xml DOT html

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

02.02.2013, 18:32:51 via Website

Der Fehler liegt nicht in Deinem Code sondern im XML Dokument. Das ist nicht wellformed .. sagt doch schon die Fehlermeldung.

Ausserdem möchte ich mal behaupten, dass der eigentliche Fehler in Deiner Zeile 4 liegt. Die XML-Delklaration <xml> wird durch den Parser nicht berücksichtigt.

— geändert am 02.02.2013, 18:36:26

lg Voss

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 18:42:35 via Website

Woran liegt das? Hier mal das komplette XML:

1<xml>
2 <entry>
3 <titel>Küchenumbau am 19.01.2013</titel>
4 <text>Am 19.Januar haben wir die Küche umgebaut:<br />
5<ul><br />
6<li>Der Herd sowie einige Schränke wurden entfernt.<br />
7<li>Ein Durchgang von der Bar zur Küche wurde gesägt.<br />
8<li>Eine neue Wand zwischen Aufenthaltsraum und Küche wurde gebaut.<br />
9</ul></text>
10 </entry>
11
12 <entry>
13 <titel>Neue Facebookseite</titel>
14 <text>Zwei Anläufe sind gescheitert, daher nun der dritte: Das Freakhall hat ab heute eine neue <a href="unwichtiger Link den ich hier noch nicht posten darf">Facebookseite</a>. Diese soll aktueller gehalten werden und in naher Zukunft auch mit dieser Website verknüpft werden.</text>
15 </entry>
16 </xml>

Liegt das an den HTML-Tags in den <text>-</text>-Blocks oder woran liegt das?

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

02.02.2013, 18:55:59 via Website

Genau.

Das ist kein valides XML ...Du musst die "<" und ">" Zeichen umformen. '<' by '&lt;' and '>' by '&gt;'

lg Voss

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 19:02:57 via Website

OK, danke, aber wie kann ich das <ul><li>...</ul> verarbeiten?

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

02.02.2013, 19:12:57 via Website

Du musst JEDEN HTML TAG derart umformen ...

in so fern auch die sogenannten <ullis> :cold:

lg Voss

Antworten
Sebastian P.
  • Forum-Beiträge: 72

02.02.2013, 19:43:23 via Website

Das ist mir schon klar, aber wie genau soll ich die dann hinterher verarbeiten?
Soll dann hinterher &lt;ul&gt; dastehen? Und die <br />'s ersetze ich am Besten durch \n oder?

EDIT: Die Fehlermeldung ist immer noch die gleiche

1<xml>
2 <entry>
3 <titel>Küchenumbau am 19.01.2013</titel>
4 <text>Am 19.Januar haben wir die Küche umgebaut:&lt;br /&gt

— geändert am 02.02.2013, 19:49:00

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

02.02.2013, 20:18:23 via Website

Dann wird der Fehler auch noch der gleiche sein ..

Keine Ahnung was du mit den <br /> machst .. ich weiß ja nicht was da später mit dem xml passieren soll..
Jedenfalls solltest Du statt des einfachen <xml> tags eher : <?xml version="1.0" encoding="UTF-8"?> schreiben. Das macht dem Parser das ganze wesentlich leichter und deklariert das XML korrekt. So werden auch Fehlermeldungen vermieden die aus der nicht korrekten Erkennung des Dokuments her rühren.

— geändert am 03.02.2013, 10:52:20

lg Voss

Antworten
Sebastian P.
  • Forum-Beiträge: 72

03.02.2013, 15:31:50 via Website

Jetzt sieht das Ganze so aus:

1<?xml version="1.0" encoding="UTF-8"?>
2 <entry>
3 <titel>Küchenumbau am 19.01.2013</titel>
4 <text>Am 19.Januar haben wir die Küche umgebaut:\n\n - Der Herd sowie einige Schränke wurden entfernt.\n - Ein Durchgang von der Bar zur Küche wurde gesägt.\n - Eine neue Wand zwischen Aufenthaltsraum und Küche wurde gebaut.\n</text>
5 </entry>
6 <entry>
7 <titel>Neue Facebookseite</titel>
8 <text>Zwei Anläufe sind gescheitert, daher nun der dritte: Das Freakhall hat ab heute eine neue Facebookseite. Diese soll aktueller gehalten werden und in naher Zukunft auch mit dieser Website verknüpft werden.</text>
9 </entry>

aber der Fehler besteht weiterhin. Immer noch "not well-formed (invalid token)" Da ich die Einrückung etwas verbessert habe, heisst es nun Line 3 Column 16, was das 'm' in 'Am 19. Januar...' ist. Ich habe jetzt auch in parser.require das Encoding auf UTF-8 eingestellt, aber immer noch keine Verbesserung.

ich weiß ja nicht was da später mit dem xml passieren soll..
Der Text soll in einem LinearLayout angezeigt werden:
1LinearLayout layout = new LinearLayout(this);
2layout.setOrientation(LinearLayout.VERTICAL);
3for (int i = 0; i < entries.size(); i++) {
4 TextView t1 = new TextView(this);
5 t1.setText(entries.get(i).title);
6 t1.setTextSize(30);
7 layout.addView(t1);
8 TextView t2 = new TextView(this);
9 t2.setText(entries.get(i).text);
10 t2.setTextSize(20);
11 layout.addView(t2);
12}
13setContentView(layout);


EDIT: Jetzt habe ich Umlaute (ÄäÖöÜü) durch die entsprechenden Escapesequenzen (\uXXXX) ersetzt. Der Fehler mit dem invalid token besteht nun nicht mehr. Jetzt gibt es aber einen neuen Fehler:
At line 11, column 4: junk after document element

Line 11 (eigentlich 12) ist der 2. <titel>-Tag.

Was soll das schon wieder heissen?

— geändert am 03.02.2013, 17:23:07

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

03.02.2013, 17:59:43 via Website

OK versuchen wir es mal anders. Ruf mal folgende Seite auf und lass Dein XML dort parsen.

http://xml.online-toolz.com/tools/xml-validator.php

Du wirst im unteren Feld eine Ausgabe erhalten, die lautet:
Invalid:
Reason:DOMDocument::loadXML() [<a href='domdocument.loadxml'>domdocument.loadxml</a>]: Validation failed: no DTD found !Extra content at the end of the document in Entity, line: 6

Bau mal folgendes mit ein:
<!DOCTYPE entrys [
<!ELEMENT entrys (entry)+>
<!ELEMENT entry (titel,text)>
<!ELEMENT titel (#PCDATA)>
<!ELEMENT text (#PCDATA)>
]>

Damit definierst Du, dass das Element <entry> mehrmals vorkommen darf. Was Du da siehst ist die sogenannte DTD (Document Type Definition) welche die Struktur Deines XML definiert.

Insgesamt schaut Dein XML dann so aus:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE entrys [
<!ELEMENT entrys (entry)+>
<!ELEMENT entry (titel,text)>
<!ELEMENT titel (#PCDATA)>
<!ELEMENT text (#PCDATA)>
]>
<entrys>

<entry>
<titel>Küchenumbau am 19.01.2013</titel>
<text>Am 19.Januar haben wir die Küche umgebaut:\n\n - Der Herd sowie einige Schränke wurden entfernt.\n - Ein Durchgang von der Bar zur Küche wurde gesägt.\n - Eine neue Wand zwischen Aufenthaltsraum und Küche wurde gebaut.\n</text>
</entry>

<entry>
<titel>Neue Facebookseite</titel>
<text>Zwei Anläufe sind gescheitert, daher nun der dritte: Das Freakhall hat ab heute eine neue Facebookseite. Diese soll aktueller gehalten werden und in naher Zukunft auch mit dieser Website verknüpft werden.</text>
</entry>

</entrys>
Dieses Dokument wird dann auch valide geparsed. Somit ist Dein XML einerseits "wellformed" und auch noch "valid" obendrein.

Die DTD kannst Du theoretisch auch weglassen. Ich hab sie hier mal zu Hilfe genommen, um Dir zu zeigen wie Du valide XML Dokumente erzeugen kannst, weil es dabei hilft, die Strukturen zu verstehen und bessere Fehlermeldungen produziert. Sie zwingt Dich sozusagen die selbstauferlegten Strukturen einzuhalten und andererseits auch es aus der Überlegung heraus auch richtig zu machen.

— geändert am 03.02.2013, 18:05:08

lg Voss

Sebastian P.

Antworten
Sebastian P.
  • Forum-Beiträge: 72

03.02.2013, 19:21:53 via Website

So jetzt ist der XML-Fehler verschwunden, die Funktion entries.size() gibt aber 0 zurück.

1//Ausschnitt aus NewsScreen.java:
2
3public List<Entry> entries = new ArrayList<Entry>();
4
5 //0=success 1=connection error 2=Parsing error
6 public int updateNews() {
7
8 //Create stream -> returns XML
9 URL url;
10 InputStream is;
11 try {
12 url = new URL("http://www.freakhall.ch/androidapp.php?cat=news");
13 is = url.openStream();
14 } catch (Throwable t) {
15 return 1;
16 }
17
18 //Parsing the XML
19 try {
20
21 XmlPullParser parser = Xml.newPullParser();
22 parser.setInput(is, null);
23 while (parser.next() != XmlPullParser.END_TAG) {
24 if (parser.getEventType() != XmlPullParser.END_TAG)
25 continue;
26 String name = parser.getName();
27 if (name.equalsIgnoreCase("entry")) {
28 entries.add(Entry.readEntry(parser));
29 } else {
30 Entry.skip(parser);
31 }
32 }
33
34 } catch (Throwable t) {
35 Log.e("NewsScreen /parser", "Error: "+ t.toString());
36 return 2;
37 }
38 return 0;
39 }

Was ist jetzt der Fehler? Der Code ist schliesslich von developer.android.com abgeleitet, viel falsch machen kann ich da ja nicht, oder?

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

03.02.2013, 20:13:58 via Website

Versuch es doch mal selber .. bau dir Log Ausgaben ein und analysiere mal wo der Fehler liegt. Wenn andere Dir die Arbeit abnehmen wirst Du es nicht lernen.

lg Voss

Sebastian P.

Antworten
Sebastian P.
  • Forum-Beiträge: 72

04.02.2013, 15:55:01 via Website

Danke... aber es geht jetzt. Der Fehler: ich hab bei einem Mal irgendwo im Code "title" statt "titel" geschrieben. Und den neuen Tag entries musste ich auch noch mit continue; behandeln.

Hier das Ergebnis:


Jetzt führe ich das mal weiter: das XML soll zuerst abgespeichert werden (Cache) und bei fehlender Internetverbindung geladen werden. Wie mache ich das und welche zusätzliche permission ausser android.permission.INTERNET muss ich noch definieren?


EDIT: Hab das ganze jetzt noch so umgebaut, dass die Netzwerkaktivitäten in einem seperaten Thread laufen:

1public InputStream is;
2 public int parseRes =0;
3
4 //0=success 1=connection error 2=Parsing error
5 public int updateNews() {
6
7 Thread thread = new Thread () {
8 @Override
9 public void run () {
10 try {
11 URL url = new URL("http://www.freakhall.ch/androidapp.php?cat=news");
12 is = url.openStream();
13 } catch (Throwable t) {
14 Log.e("NewsScreen /ucon", t.toString());
15 parseRes = 1;
16 return;
17 }
18 return;
19 }
20 };
21
22 thread.run();
23 if (parseRes == 1)
24 return 1;
25
26 //Parsing the XML..
27//Rest eher unwichtig für Problem

Wenn ich die App mit einem 4.2 AVD ausführe, schreibt mir LogCat:
NewsScreen /ucon android.os.NetworkOnMainThreadException

Wo finden da Netzwerkaktivitäten im MainThread statt? V.a. weil der Fehler ja im Netzwerkthread ausgegeben wird. Auf meinem 2.3.6-Gerät läuft alles problemlos.

— geändert am 04.02.2013, 17:58:02

Antworten
Sebastian P.
  • Forum-Beiträge: 72

05.02.2013, 19:01:38 via Website

So, jetzt funktioniert das öffnen der URL in den InputStream auch auf dem Emulator, nachdem ich die google developers Seite ein wenig durchsucht habe:

1package me.the_Seppi.freakhall;
2
3import java.io.InputStream;
4import java.net.URL;
5
6import android.os.AsyncTask;
7
8@SuppressWarnings("rawtypes")
9public class DownloadTask extends AsyncTask {
10
11 public InputStream is;
12
13 @Override
14 protected Object doInBackground(Object... args) {
15
16 try {
17 URL url = new URL((String) args[0]);
18 is = url.openStream();
19 } catch (Throwable t) {
20 return null;
21 }
22 return is;
23 }
24}


NewsScreen.java
1DownloadTask dl = new DownloadTask();
2 dl.execute("http://www.freakhall.ch/androidapp.php?cat=news");
3 try {
4 is = (InputStream) dl.get(10, TimeUnit.SECONDS);
5 } catch (Throwable t) {
6 Log.e("NewsScreen /ucon", t.toString());
7 return 1;
8 }
9
10 //Parsing the XML
11//.....

Aber ich bekomme tortzdem noch eine Fehlermeldung:
102-05 17:54:27.698: E/NewsScreen /parser(794): Error: org.xmlpull.v1.XmlPullParserException: Invalid stream or encoding: android.os.NetworkOnMainThreadException (position:START_DOCUMENT null@1:1 in java.io.InputStreamReader@40de9368) caused by: android.os.NetworkOnMainThreadException
202-05 17:54:27.698: E/NewsScreen(794): Failed parsing XML: 2

Heisst das, dass das parsen auch noch eine Netzwerkaktivität ist? Dann müsste ich den kompletten Parsingprozess in DownloadTask ausführen und als Rückgabewert die Liste der geparsten Einträge verwenden, oder?

Btw. ist das Timeout von 10s angemessen?

— geändert am 05.02.2013, 20:36:22

Antworten
Sebastian P.
  • Forum-Beiträge: 72

05.02.2013, 19:21:28 via Website

Sebastian P.
Heisst das, dass das parsen auch noch eine Netzwerkaktivität ist? Dann müsste ich den kompletten Parsingprozess in DownloadTask ausführen und als Rückgabewert die Liste der geparsten Einträge verwenden, oder?

das hiess es... ich habs getestet und es funktioniert. Jetzt muss ich das ganze noch auf die EventScreen.java anwenden und das Problem ist beseitigt.

— geändert am 07.02.2013, 20:19:30

Antworten
Sebastian P.
  • Forum-Beiträge: 72

08.02.2013, 18:52:57 via Website

Zum Abschluss:

Den Plan mit dem Abspeichern habe ich selbst hinbekommen (endlich etwas, was ich nur aufgrund der Informationen von android.com machen konne :D )
Ich habe das Problem so gelöst:

Wenn Interneterbindung vorhanden:
- Daten parsen
- Geparste Resultate
-->speichern (SharedPreferences)
-->anzeigen
Wenn keine Verbindung:
-Gespeicherte Daten einlesen und anzeigen.

Das jetzt noch als Abschluss, damit ist das Thema geklärt und beantwortet.

Antworten