Android – Kommunikation mit der Außenwelt

Eingetragen bei: Android | 0

Nachdem ich in einem vorangegangenen Artikel die Verwendung einer SQLite Datenbank in einer Android-Anwendung beschrieben habe (siehe http://martinzimmermann1979.wordpress.com/2011/11/17/verwendung-einer-sqlite-datenbank-in-einer-android-app/), möchte ich mich in diesem Artikel der Kommunikation mit einem REST-Service widmen. Hierfür kann die im Android-SDK integrierte Apache HttpClient Library verwendet werden. Als REST-Schnittstelle soll das Beispiel aus meinem letzten Artikel (siehe http://martinzimmermann1979.wordpress.com/2012/02/29/rest-services-mit-spring-3/) verwendet werden. Hier wurde exemplarisch gezeigt wie man auf der REST-Ressource MyBean die Operationen GET, POST, PUT und DELETE ausführt. Neben XML- wurden auch JSON-Inhalte ausgetauscht. Letzteres Datenformat wird im folgenden Beispiel benötigt. Hier nun die Java-Repräsentation der MyBean-Ressource:

public class MyBean {
private String id;
private String message;
// getter and setter for id and message
}

Im ersten Schritt soll die Android-Anwendung per GET-Request alle MyBean-Einträge erfragen und den erhaltenen JSON-Inhalt in MyBean-Objekte transformieren. Anschließend wird die Funktionalität sukzessive um die REST-Operationen POST, PUT und DELETE erweitert, wobei der POST- und PUT-Request entsprechenden JSON-Inhalt mitliefern muss.

Bevor es an die REST-Kommunikation geht, muss die Android-Anwendung die Erlaubnis zum Zugriff aufs Internet und damit folgenden Eintrag im Android-Manifest erhalten:

<uses-permission android:name=“android.permission.INTERNET“ />

Anschließend wird eine Hilfsklasse erstellt, welche die Verbindung zum REST-Service aufbaut, den erhaltenden InputStream in einen String umwandelt und für alle Arten von REST-Operationen genutzt werden kann:

public String connect(HttpRequestBase httpRequest) {
HttpClient httpclient = new DefaultHttpClient();
httpRequest.addHeader(„Accept“, „application/json“);
try {
HttpResponse response = httpclient.execute(httpRequest);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
String result = convertStreamToString(inputStream);
inputStream.close();
return result;
}
} catch (Exception e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
return „“;
}

Die übergebene HttpRequestBase abstrahiert die jeweilige Http-Operation, die bereits die URI der Rest-Ressource und gegebenenfalls weitere Inhalte enthält. Die Kommunikation sieht bei allen Operationen gleich aus. Zunächst wird ein HttpClient benötigt – für dieses Beispiel reicht der Standard-HttpClient aus. Da der Http-Response vom Server JSON enthalten soll, wird die Header-Information Accept: application/json hinzugefügt. Sofern der Server außer dem Status – response.getStatusLine() – mit weiteren Daten antwortet, befinden sich diese in der HttpEntity in Form eines InputStreams. Da auf mobilen Endgeräten nur begrenzt Speicher vorhanden ist, können nicht beliebig Libraries verwendet werden. So kommt man nicht Drumherum, den InputStream selbstständig in einen String zu transformieren:

private String convertStreamToString(InputStream inputStream) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder(50);
String line = null;
try {
while ((line = reader.readLine()) != null) {
stringBuilder.append(line + „n“);
}
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
} finally {
try {
inputStream.close();
} catch (IOException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}
return stringBuilder.toString();
}

Der InputStreamReader dient dem InputStream als wrapper, der wiederrum von einem BufferedReader gewrappt wird. Letzterer kann zeilenweise ausgelesen und der Inhalt einem StringBuilder hinzugefügt werden. Sofern die Antwort des Servers voraussichtlich mehr als 16 Zeichen beträgt, empfiehlt es sich den StringBuilder mit einem höheren Wert zu initialisieren. Andernfalls muss der interne Buffer des StringBuilders sukzessive erhöht werden.

Nun soll die GET-Operation erstellt und die oben gezeigte connect-Methode aufgerufen werden:

HttpRequestBase httpGet = new HttpGet(REST_RESSOURCE_URI);
String result = connect(httpGet);

Anschließend enthält das result einen JSON-String folgender Art:

{„object“:{„myview“:[{„id“:“1″,“message“:“Some text“},{„id“:“2″,“message“:“Some more text“}]}}

Dieser String soll  nun geparst und der JSON-Inhalt in entsprechende MyBean-Objekte transformiert werden. Hierfür wird der komplette String zunächst einem JSONTokener übergeben. In unserem Fall liefert dessen nextValue-Methode ein JSONObject zurück, grundsätzlich wären aber auch ein JSONArray, String, NULL oder weitere Java-Typen möglich. Das so erhaltene JSONObject enthält wiederrum ein JSONObject mit dem Schlüsselwert „object“. Erst jetzt kommt man an das JSONArray „myview“, welches die gesammelten MyBean-Einträge enthält. Jeder dieser Einträge ist in einem JSONObject hinterlegt, welches die MyBean-Properties id und message enthält. Diese Werte können nun einem neu zu erzeugenden MyBean-Objekt übergeben werden:

.JSONTokener token = new JSONTokener(jsonContent);
while (token.more()) {
Object object = token.nextValue();
if (object instanceof JSONObject) {
JSONObject jsonRoot = (JSONObject) object;
try {
JSONObject jsonObject = jsonRoot.getJSONObject(„object“);
if (jsonObject != null) {
JSONArray jsonBeans = jsonObject.getJSONArray(„myview“);
if (jsonBeans != null) {
for (int i = 0; i < jsonBeans.length(); i++) {
JSONObject jsonBean = jsonBeans.getJSONObject(i);
int id = jsonBean.getInt(„id“);
String subject = jsonBean.getString(„message“);
MyBean myBean = new MyBean(id, message);
// adding myBean to a myBeans list
}
}
}
} catch (JSONException e) {
// do nothing, wrong JSON content
}
}

Soll nun eine POST- oder PUT-Operation getätigt werden, muss der umgekehrte Weg gegangen werden. Es wird nicht empfangener JSON-Inhalt deserialisiert, sondern ein MyBean-Objekt in JSON-Inhalt umgewandelt und an den Server gesendet. Hierfür wird eine Implementierung des HttpEntity-Interfaces benötigt:

public class MyHttpEntity implements HttpEntity {

private JSONObject jsonObject;

public MyHttpEntity(MyBean myBean) {
try {
jsonObject = new JSONObject();
jsonObject.put(„id“, myBean.getId());
jsonObject.put(„message“, myBean.getMessage());
} catch (JSONException e) {
Log.e(„rest“, e.getMessage(), e.getCause());
}
}

@Override
public InputStream getContent() throws IOException, IllegalStateException {
return new ByteArrayInputStream(jsonObject.toString().getBytes());
}

@Override
public long getContentLength() {
return jsonObject.toString().length();
}

@Override
public Header getContentType() {
return new BasicHeader(„Content-Type“, „application/json“);
}

@Override
public void writeTo(OutputStream outstream) throws IOException {
outstream.write(jsonObject.toString().getBytes());
}

// … further methods, which are not required
//    for this example …
}

Im Konstruktor wird das zu transformierende MyBean-Objekt übergeben und dessen Eigenschaften in einem zu erstellenden JSONObject hinterlegt. Anschließend sind folgende Methoden zu erstellen:

  • getContent – Liefert den JSON-Inhalt als InputStream
  • getContentLength – Enthält die Länge des JSON-Inhaltes
  • getContentType – Fügt die Header Information „Content-Type: application/json“ hinzu. Hierfür wird ein BasicHeader verwendet.
  • writeTo – Der JSON-Inhalt wird in einen OutputStream gespeichert.

Das Interface HttpEntity enthält noch weitere zu implementierende Methoden, deren Funktionalität spielt für dieses Beispiel aber keine Rolle. Nachdem der Serialisierung einer MyBean nichts mehr im Wege steht, kann jetzt die eigentliche POST-Operation ausgeführt werden:

HttpPost post = new HttpPost(REST_RESSOURCE_URI);
post.setEntity(new MyHttpEntity(myBean));
String result = connect(post);

Äquivalent hierzu ist der PUT-Request, nur dass dieser auf einer bestehenden MyBean getätigt wird. Daher wird in der URI noch eine id zur Identifikation der MyBean übergeben:

HttpPut put = new HttpPut(REST_RESSOURCE_URI + „/myBean/“ + myBean.getId());
put.setEntity(new MyHttpEntity(myBean));
connect(put);

Einfach gestaltet sich die DELETE-Operation, da der Request außer der zu löschenden Ressource keine weiteren Informationen benötigt. Die id der zum Löschen auserwählten MyBean wird im Pfad übergeben:

HttpDelete delete = new HttpDelete(REST_RESSOURCE_URL + „/myBean/“ + id);
connect(delete);

Ist die Hilfsklasse für die Kommunikation erst einmal geschrieben, sind die einzelnen REST-Operationen schnell umgesetzt. Lediglich bei der Serialisierung bzw. Deserialisierung von und nach JSON handelt es sich um lästige Fleißarbeit. Zumal hier jeder Fehler dank des recht langsamen Android-Emulators zusätzlich Zeit kostet.

Verwendung einer SQLite Datenbank in einer Android App

Eingetragen bei: Android, Java | 0

Will man in einer Android Anwendung Daten speichern, kann hierzu bequem die in das Android SDK integrierte SQLite Datenbank verwendet werden. Diese ist besonders für den mobilen Anwendungsbereich konzipiert, da sie mit gerade mal 250 KByte extrem genügsam im Bezug auf Speicherplatz ist.

Im folgenden Beispiel sollen die Einstellungen einer Anwendung in der Datenbank gespeichert werden. Hierzu wird eine SettingsActivity erstellt – nicht vergessen, die Activity im Android Manifest zu definieren –  wobei das Layout in einer XML-Datei definiert ist. Als variables Feld erhält die Activity einen Benutzer-Namen, welcher vom Benutzer editiert werden kann. Außerdem gibt es den obligatorischen Speichern-Button, damit die Änderungen vom Benutzer auch gespeichert werden können:

<EditText
android:id=“@+id/name“
android:layout_width=“fill_parent“
android:layout_height=“wrap_content“  android:background=“@android:drawable/editbox_background“
android:text=““
android:padding=“3dip“ />

<Button
android:layout_column=“1″
android:id=“@+id/save“
android:layout_width=“wrap_content“
android:layout_height=“wrap_content“
android:layout_alignParentRight=“true“
android:layout_marginLeft=“10dip“
android:text=“Speichern“ />

Um in den Activities der Anwendung direkten Zugriff auf die UI-Komponenten zu haben, wird das Dependency Injection Framework roboguice verwendet, welches zur Zeit in Version 1.2.2 unter folgendem Link zum Download bereit liegt: http://code.google.com/p/roboguice/wiki/Downloads

Zusätzlich zu roboguice wird noch die Google Library guice benötigt, die man unter folgendem Link erhält: http://code.google.com/p/google-guice/downloads/list

Hierbei ist zu beachten, dass nicht die aktuelle Version 3.0, sondern guice-2.0-no_aop.jar benötigt wird. Beide Libraries werden dem Classpath hinzugefügt. Nun wird im Android Manifest unter Application der Name roboguice.application.RoboApplication gesetzt. Dies ist nötig, da die Activities nun nicht mehr von Activity oder ListActivity direkt erben, sondern von RoboActivity bzw. RoboListActivity.

In der folgenden SettingsActivity wird über die Annotation @InjectView per Dependency Injection auf die im XML definierten UI-Elemente per Id zugegriffen:

public class SettingActivity extends RoboActivity {

private DatabaseManager databaseManager;
@InjectView(R.id.name)
private EditText nameText;
@InjectView(R.id.save)
private Button saveButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setting);
databaseManager = new DatabaseManager();
final Settings settings = databaseManager.fetchSetting();
nameText.setText(settings.getName());
saveButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
settings.setName(nameText.getText().toString());
if (settings.getId() == null) {
databaseManager.createSettings(settings);
} else {
databaseManager.updateSettings(settings);
}
}
});
}

@Override
protected void onDestroy() {
super.onDestroy();
databaseManager.close();
}
}

In der onCreate-Methode wird ein DatabaseManager erzeugt, der sich um die Datenbank Connection kümmert, aber hierzu später noch mehr. Ebenfalls über den DatabaseManager werden die Settings bezogen, die in der UI dargestellt werden. Hierzu wird dem nameText, der Name aus den Settings zugewiesen. Nun wird noch dem Save-Button ein onClickListener hinzugefügt, der die Änderungen der UI dem Settings Objekt übergibt und dieses anschließend in der Datenbank speichert.

Wichtig ist, dass in der onDestroy Methode der Activity, die Datenbank-Connection wieder geschlossen wird – verlässt der Benutzer also die Einstellungen wird automatisch die Datenbank Verbindung zurück gesetzt.

Nun zur eigentlichen Interaktion mit der Datenbank über den DatabaseManager:

public class DatabaseManager {

private static final String DB_FILE = „/data/data/myapp.db“;
private SQLiteDatabase database;

public DatabaseManager() {
File file = new File(DB_FILE);
if (!file.exists()) {
database = SQLiteDatabase.openOrCreateDatabase(file, null);
database.execSQL(„create table Settings (id integer primary key autoincrement not null, name text);“);
} else {
database = SQLiteDatabase.openOrCreateDatabase(file, null);
}
}

public void close() {
database.close();
}

public Settings createSettings(Settings settings) {
ContentValues initialValues = createContentValues(settings);
long id = database.insert(„Settings“, null, initialValues);
settings.setId((int) id);
return settings;
}

public long updateSettings(Settings settings) {
ContentValues updateValues = createContentValues(settings);
return database.update(„Settings“, updateValues, „id=“ + settings.getId(), null);
}

public Settings fetchSetting() {
try {
Cursor cursor = database.query(„Settings“,
new String[] {
„id“, „name“
}, null, null, null, null, null);
cursor.moveToFirst();
if (!cursor.isAfterLast()) {
Integer id = cursor.getInt(cursor.getColumnIndexOrThrow(„id“));
String name = cursor.getString(cursor.getColumnIndexOrThrow(„name“);
Settings settings = new Settings(id, name);
return settings;
} else {
return new Settings();
}
} catch (SQLiteException e) {
return new Settings();
}
}

private ContentValues createContentValues(Settings settings) {
ContentValues values = new ContentValues();
values.put(„name“, settings.getName());
return values;
}
}

Im Konstruktor, der in der onCreate-Methode der SettingsActivity aufgerufen wurde, wird zunächst überprüft, ob die Datenbank-Datei bereits angelegt wurde. Ist dies nicht der Fall, werden die Settings der Anwendung Initial gespeichert und zusätzlich zum Öffnen der Datenbank muss die Settings Tabelle mittels SQL-Statement generiert werden.

Der nächste Aufruf der SettingsActivity lädt die bereits gespeicherten Settings über die fetchSettings-Methode. Hier wird auf der Datenbank eine Query gefeuert, der die gewünschten Properties mitgegeben werden und die sonst keinerlei Einschränkung enthält, es werden also wirklich alle Settings geladen. Das Ergebnis ist über einen Cursor zugreifbar, der zunächst über die moveToFirst-Methode auf die erste Position gesetzt werden muss. Die über den Cursor zugreifbaren Properties werden in einem Settings Objekt gespeichert, bzw. falls die Datenbank noch keinen Eintrag enthält wird ein neues Settings Objekt erzeugt und der SettingsActivity geliefert.

Werden die Einstellungen vom Benutzer geändert und der Speicher-Button betätigt, wird das Settings-Objekt entweder Initial in der Datenbank gespeichert oder aktualisiert. Dies hängt davon ab, ob die Settings bereits eine eindeutige Id enthalten. Bei dem Update-Statement wird eine Where Clause benötigt, die eine Einschränkung auf die gegebene Id – also den Primär Schlüssel – enthält. Andernfalls wird das Objekt neu erzeugt und die von der Datenbank vergebene Id in dem Settings-Objekt gespeichert.

Beim Insert bzw. Update wird nicht das Settings-Objekt selber verwendet sondern das generische Objekt ContentValues, dem per put-Befehl die einzelnen Properties zugewiesen werden können.

Sobald man nun noch die Permissions im Android-Manifest richtig gesetzt hat, steht dem ersten Probelauf jetzt nichts mehr im Wege.

<uses-permission android:name= „android.permission.WRITE_EXTERNAL_STORAGE“/>
<permission-group android:name=“android.permission-group.STORAGE“/>

Insgesamt lässt sich sagen, dass der Zugriff auf die SQLite Datenbank bei Android Anwendungen recht komfortable gestaltet ist, gerade weil hier alle benötigten Libraries bereits gegeben sind. Nur Schade, dass man nicht direkt mit Entities arbeiten kann, sondern dass diese auf die Datenbank Inhalte gemappt werden müssen. Es wäre angenehmer auf die Cursor zu verzichten und besser direkt über die Entity an die gewünschten Properties zu gelangen – ebenso wie beim Mappen der Properties auf die ContentValues ein zusätzlicher Schritt benötigt wird.

Erstellung einer Android Anwendung

Eingetragen bei: Android, Java | 0

Um ein Android-Projekt zu Erstellen benötigt man zunächst das Android SDK, welches zur Zeit in der aktuellen Version 3.2 zum Download unter http://developer.android.com/sdk/index.html bereit liegt.
Weiterhin wird das ADT-Plugin für Eclipse benötigt, dass die Erstellung eines Android Projektes extrem vereinfacht und das Debuggen ermöglicht. Zusätzlich kann die Anwendung zertifiziert und als apk-Datei exportiert werden, so dass diese auf einem Android Gerät installiert werden kann. Die aktuelle Version des ADT-Plugins ist 12.0.0 und kann unter folgender Update Site https://dl-ssl.google.com/android/eclipse/ installiert werden.
Anschließend stellt man nun in Eclipse unter Window, Preference, Android die SDK Location ein.

Im Anschluss kann das erste Android-Projekt erstellt werden. Hierzu reich ein Projekt- und Applikations-Name, ein Package und das gewünschte Android SDK. Bei letzterem Punkt sollte man beachten, dass für das eigene Android-Gerät meist noch nicht die neuste Firmware-Version zum Download bereit liegt. Für das Samsung Galaxy ist die Version 2.3.3 verfügbar, während man bereits das Android SDK 3.2 verwenden kann.
Sofern man sich eine Activity generieren lässt, kann nun das Projekt direkt im Emulator ausgeführt werden und man bekommt einen HelloWorld Text zu Gesicht.

Das Design der Anwendung, sofern man bei der HelloWorld Anwendung von Design reden kann, befindet sich unter /res/layout/main.xml. Es wird ein einfaches LinearLayout verwendet, welches vertikal ausgerichtet ist und die komplette Größe einnimmt. Das Layout wird einer TextView zugewiesen, die für die HelloWorld Ausgabe zuständig ist.

<?xml version=“1.0″ encoding=“utf-8″?>
<LinearLayout    xmlns:android=“http://schemas.android…“
android:orientation=“vertical“
android:layout_width=“fill_parent“
android:layout_height=“fill_parent“   >
<TextView
android:layout_width=“fill_parent“
android:layout_height=“wrap_content“
android:text=“@string/hello“
/>
</LinearLayout>

Der Text selbst ist externalisiert und befindet sich unter /res/values/strings.xml.

Schaut man sich den Erzeugten Code an, so befindet sich in der onCreate-Methode der erstellten Activity folgende Zeile:

setContentView(R.layout.main);

Die hier verwendete Klasse R wird vom ADT-Plugin angelegt und verwaltet – werden weitere grafische Komponenten im XML angelegt, werden diese hier gepflegt. Das Design kann zwar auch im Code verändert werden, aufgrund einer klaren Trennung sind die XML-Dateien aber vorzuziehen.

Will man die Anwendung nicht nur im Emulator testen, sondern auf seinem Android-Gerät, kann man diese bequem als apk-Datei exportieren, auf sein Handy einspielen und installieren.

Auch wenn es sich hierbei nur um eine HelloWorld Anwendung handelt, ist es doch erstaunlich, wie schnell man diese erstellt, auf sein Handy aufgespielt und getestet hat. Lediglich die Langsamkeit des Emulators treibt einen ab und an in die Verzweiflung. Aber der erste Start ist schnell gemacht und man kann sich um die erste richtige Anwendungen kümmern.