Aufruf eines Webservice
Einleitung
Für die Nutzung von Webservices bietet Android das Paket
org.apache.http
. In diesem ist unter anderem das Paket
org.apache.http.client.methods
. Darin befindet sich die Klasse
HttpGet
. Wie deren Name bereits vermuten lässt, kann damit ein HTTP Get
Request gesendet werden. Dazu instanziiert man ein Objekt dieser
Klasse und übergibt als Parameter die URL des Webservice. Der
folgende Abschnitt zeigt den Aufruf eines Google-Webservice mittels
dieser Methode.
Einfache Google-Maps Anfrage
Für dieses Beispiel wird der Geocoding-Service von Google genutzt. Eine Beschreibung des Service kann unter http://code.google.com/intl/de/apis/maps/documentation/geocoding/ gefunden werden. Der Abschnit Geocodierungsanforderungen erklärt das Format, welches die URL für die Anforderung haben muss. Bevor es an die eigentliche Arbeit mit Webservices geht, erstellen wir eine kleine GUI. Diese soll ein Eingabefeld für die zu suchende Adresse, einen Button zum Senden der Anfrage sowie ein Ausgabefeld für die ermittelten Koordinaten besitzen. Die Benutzerschnittstelle könnte wie folgt aussehen:
Die zu diesem Layout gehörige XML-Datei enthält folgenden Inhalt:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/editTextAddress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0.74" android:hint="@string/ADDRESS_HINT" > <requestFocus /> </EditText> <Button android:id="@+id/buttonGo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/GO" /> </LinearLayout> <TextView android:id="@+id/textViewResponse" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0.41" /> </LinearLayout>
In der Activity muss nun die Methode
onCreate
angepasst werden. Zuerst benötigen wir eine Referenz auf den Button,
damit wir einen
OnClickListener
mit ihm verbinden können:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button go = (Button) findViewById(R.id.buttonGo); go.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { } }); }
Nun kann die Methode
onClick
des Listeners implementiert werden. Diese muss eine URL für die
Anfrage generieren. Dazu wird der Inhalt des Eingabefeldes benötigt.
Mit der erzeugten URL kann nun das
HttpGet
-Objekt erzeugt werden. Um den Http GET Request auszuführen, muss
noch ein Objekt der Klasse
HttpClient
erstellt werden. Bis zu diesem Punkt sollte der Code in etwa wie
folgt aussehen:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button go = (Button) findViewById(R.id.buttonGo); final EditText input = (EditText) findViewById(R.id.editTextAddress); go.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String address = input.getText().toString(); String PROTOCOL = "http"; String HOST = "maps.google.com"; String PATH = "/maps/api/geocode/"; String OUTPUT = "xml"; String PARAMETERS = "?sensor=false&address="; String url = PROTOCOL + "://" + HOST + PATH + OUTPUT + PARAMETERS; HttpClient client = new DefaultHttpClient(); try { HttpGet request = new HttpGet(url + URLEncoder.encode(address, "UTF-8")); HttpResponse response = client.execute(request); } catch (Exception e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } } }); }
Wichtig: Der Wert des Adress-Parameters muss kodiert werden, da die URL sonst sehr wahrscheinlich ungültige Zeichen enthält.
Wenn man die Anwendung nun startet und versucht, eine Abfrage zu
senden, so wird eine
IOException
geworfen. Diese hat ihren Ursprung in den Berechtigungen. Eine
Anwendung muss explizit angeben, dass sie Zugriff auf das Internet
benötigt. Diese Einstellung nimmt man in der Datei
AndroidManifest.xml
vor. Dort erstellt man innerhalb des
manifest
-Elementes folgendes Kindelement:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Damit erlaubt man der Anwendung, das Internet zu nutzen. Ein erneuter Versuch einer Anfrage sollte nun erfolgreich sein.
Die Daten, welche der Webservice zurück sendet befinden sich in einem
Objekt vom Typ
Entity
. Dieses erhält man mittels
response.getEntity()
. Die Helferklasse
EntityUtils
kann den Inhalt des
Entity
-Objekts extrahieren und als String zurückliefern. Dieser String kann
nun in unserem Ausgabefeld angezeigt werden.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button go = (Button) findViewById(R.id.buttonGo); final EditText input = (EditText) findViewById(R.id.editTextAddress); final TextView responseText = (TextView) findViewById(R.id.textViewResponse); ... }
HttpEntity entity = response.getEntity(); String data = EntityUtils.toString(entity); responseText.setText(data);
Anpassung der Ausgabe
Bisher wird einfach der vom Webservice gelieferte XML-Code angezeigt.
Aus diesem werden wir nun mittels XPath den Längen- und Breitengrad
herausfiltern und für die Anzeige verwenden. Dafür nutzen wir die
Klassen
org.xml.sax.InputSource
,
javax.xml.xpath.XPath
und einige weitere. Die Antwort des Webservice entählt einen Status.
Bevor die Positionsdaten extrahiert werden können, sollte dieser
überprüft werden:
@Override public void onClick(View v) { String address = input.getText().toString(); String PROTOCOL = "http"; String HOST = "maps.google.com"; String PATH = "/maps/api/geocode/"; String OUTPUT = "xml"; String PARAMETERS = "?sensor=false&address="; String url = PROTOCOL + "://" + HOST + PATH + OUTPUT + PARAMETERS; HttpClient client = new DefaultHttpClient(); try { HttpGet request = new HttpGet(url + URLEncoder.encode(address, "UTF-8")); HttpResponse response = client.execute(request); HttpEntity entity = response.getEntity(); String data = EntityUtils.toString(entity); InputSource source = new InputSource(new StringReader(data)); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(source); XPath xpath = XPathFactory.newInstance().newXPath(); String status = (String) xpath.evaluate("//status/text()", doc, XPathConstants.STRING); if (!status.equals("OK")) { responseText.setText(status); return; } } catch (Exception e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } }
Die Positionsdaten befinden sich im Element result/geometry/location
.
Diese werden nun mittels XPath ermittelt und im Textfeld ausgegeben.
String lat = (String) xpath.evaluate("//geometry/location/lat/text()", doc, XPathConstants.STRING); String lng = (String) xpath.evaluate("//geometry/location/lng/text()", doc, XPathConstants.STRING); responseText.setText(String.format("Latitude: %s\nLongitude: %s", lat, lng));
Der gesamte Quelltext der Activity sollte nun in etwa wie folgt aussehen:
package de.hszigr.mobileapps; import java.io.StringReader; import java.net.URLEncoder; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.w3c.dom.Document; import org.xml.sax.InputSource; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class GMQueryActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button go = (Button) findViewById(R.id.buttonGo); final EditText input = (EditText) findViewById(R.id.editTextAddress); final TextView responseText = (TextView) findViewById(R.id.textViewResponse); go.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String address = input.getText().toString(); String PROTOCOL = "http"; String HOST = "maps.google.com"; String PATH = "/maps/api/geocode/"; String OUTPUT = "xml"; String PARAMETERS = "?sensor=false&address="; String url = PROTOCOL + "://" + HOST + PATH + OUTPUT + PARAMETERS; HttpClient client = new DefaultHttpClient(); try { HttpGet request = new HttpGet(url + URLEncoder.encode(address, "UTF-8")); HttpResponse response = client.execute(request); HttpEntity entity = response.getEntity(); String data = EntityUtils.toString(entity); InputSource source = new InputSource(new StringReader(data)); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(source); XPath xpath = XPathFactory.newInstance().newXPath(); String status = (String) xpath.evaluate("//status/text()", doc, XPathConstants.STRING); if (!status.equals("OK")) { responseText.setText(status); return; } String lat = (String) xpath.evaluate("//geometry/location/lat/text()", doc, XPathConstants.STRING); String lng = (String) xpath.evaluate("//geometry/location/lng/text()", doc, XPathConstants.STRING); responseText.setText(String.format("Latitude: %s\nLongitude: %s", lat, lng)); } catch (Exception e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG) .show(); } } }); } }
AsyncTask
Das oben vorgestellte Beispiel funktioniert bis zur Android Version 2.3. In der aktuellen Version 4.x können sogenannte Hintergrundaufgaben, beispielsweise eine GET Anfrage an einen Webservice, nicht mehr direkt im Hauptthread (UI) ausgeführt werden. Das macht Sinn, denn dauert die Abfrage eines Webservices mal länger, würde im oberen Bsp. das UI einfrieren. Auch kann kein Status zum aktuellen Geschehen abfragt werden.
Google stellt mit den AsyncTask ein Mittel für die Ausführung und Steuerung von kleinen Hintergrundaufgaben bereit. Dabei wird einen Threading-Rahmen mittels Klasse bereitgestellt.
Für die Ausführung des Tasks sind drei Parameter notwendig:
- Parameter, Typ der Eingangsparameter
- Fortschritt , Typ der Fortschrittsdaten, die während der Ausführung verarbeitet werden (Progressbar)
- Ergebnis, Typ des Rückgabewertes
private class GetDataFromService extends AsyncTask<Void, Void, String>
Weiterhin besitz der AsyncTask 4 Methoden, die Schrittweise ausgeführt werden:
-
onPreExecute()
Einrichtung und Start des AsyncTasks -
doInBackground()
wird unmittelbar nach onPreExecute aufgerufen und führt die eigentliche Aufgabe (Abfrage WebService) durch onProgressUpdate(Progress...)
wird während der Abarbeitung aufgerufen. Wird für die Anzeige des Fortschritts verwendetonPostExecute(Result)
wird nach der Abarbeitung aufgerufen und aktualisiert das UI
private class GetDataFromService extends AsyncTask<Void, Void, String> { protected String doInBackground(Void... arg0) { //mache etwas.... HttpGet request = new HttpGet(url + address); HttpResponse response = client.execute(request); HttpEntity entity = response.getEntity(); data = EntityUtils.toString(entity); ..... } return ergebnis; } protected void onProgressUpdate(Integer... progress) { //Operationen während der Ausführung (Bsp. Progressbar) setProgressPercent(progress[0]); } protected void onPostExecute(Long result) { //Aktualisierung des UI mit den Ergebnisdaten TextView responseText = (TextView) findViewById(R.id.textViewResponse); responseText.setText(result.toString()); } }
Das folgende Beispiel implementiert den bereits oben gezeigten Aufruf eines Webservice mittels AsyncTask.
package de.hszg.webserviceclient; import java.io.StringReader; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.w3c.dom.Document; import org.xml.sax.InputSource; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button go = (Button) findViewById(R.id.buttonGo); go.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new GetDataFromService().execute(); } }); } private class GetDataFromService extends AsyncTask<Void, Void, String> { private String PROTOCOL = "http"; private String HOST = "maps.google.com"; private String PATH = "/maps/api/geocode/"; private String OUTPUT = "xml"; private String PARAMETERS = "?sensor=false&address="; private String url = PROTOCOL + "://" + HOST + PATH + OUTPUT + PARAMETERS; private String data; private String result = null; @Override protected String doInBackground(Void... arg0) { EditText input = (EditText) findViewById(R.id.editTextAddress); String address = input.getText().toString(); HttpClient client = new DefaultHttpClient(); try { HttpGet request = new HttpGet(url + address); HttpResponse response = client.execute(request); HttpEntity entity = response.getEntity(); data = EntityUtils.toString(entity); InputSource source = new InputSource(new StringReader(data)); DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(source); XPath xpath = XPathFactory.newInstance().newXPath(); String status = (String) xpath.evaluate("//status/text()", doc, XPathConstants.STRING); if (!status.equals("OK")) { return status; } String lat = (String) xpath.evaluate( "//geometry/location/lat/text()", doc, XPathConstants.STRING); String lng = (String) xpath.evaluate( "//geometry/location/lng/text()", doc, XPathConstants.STRING); result = String.format("Latitude: %s\nLongitude: %s", lat, lng); } catch (Exception e) { Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show(); } return result; } @Override protected void onPostExecute(String result) { TextView responseText = (TextView) findViewById(R.id.textViewResponse); responseText.setText(result.toString()); } } }