Android-Entwicklung für Einsteiger #02 – Weitere Grundlagen und erste Funktionen

Gastbeitrag

Hat nun doch etwas länger gedauert als ich gedacht habe, aber hier ist nun der zweite Teil der Serie “Android Entwicklung für Einsteiger“ :)

Nachdem wir in der ersten Episode die Entwicklungsumgebung eingerichtet und eine erste App auf das Gerät geladen haben, geht es nun richtig los mit der Entwicklung.

In dieser Episode geht es um die grundlegende Bedienung der Entwicklungsumgebung und wie eine Android Projekt aufgebaut ist. Außerdem ergänzen wir unseren RSS Reader um die Funktion einen Feed auszulesen und die Überschriften inkl. Kurzauszug in einer Liste darzustellen.

Der Projektbaum

Im linken Bereich findet man den Projektbaum mit vielen vom Studio bereits generierten Dateien.

Der Ordner libs beinhaltet vorkompilierten Programmcode in Form von JAR-Dateien. In diesem findet man bereits die Android Support Library.

Die Android Support Library wurde von Google aufgrund der starken Fragmentierung des Android Ökosystems geschaffen. Durch diese externe Bibliothek können Android System Features neuerer Versionen direkt in die App eingebaut werden, um diese auch unter älteren Android Versionen zu nutzen.

Unter src/main/java findet man wie der Name schon sagt, alle Java-Quellcodedateien des Projekts. Hier ist auch die automatisch vom Projektassistenten angelegte Activity namens “MainActivity“ zu finden. Dazu später mehr.

Der Ordner res erfüllt eine sehr wichtige Aufgabe im Projekt. Dieser beinhaltet alle Grafiken (drawable), Layoutdefinitionen (layout), Menüs (menu) und Sonstiges wie Texte, Farbwerte und Styles (values).

Die AndroidManifest.xml ist die zentrale Verwaltungsdatei des Projekts. In dieser werden Werte wie Version, Anwendungsname, Icon, Activities und vieles mehr definiert.

Als letzte interessante Datei wäre die build.gradle des Projekts “RSSReader“ (nicht RSSReaderProject) erwähnt. Diese weist das Gradle-Buildsystem an, wie es das Projekt zu kompilieren hat und wie externe Verweise verarbeitet werden sollen.

Lebenszyklus einer Activity

Wie bereits im ersten Teil dieser Serie erwähnt ist eine Activity das mit der Nutzer interagiert. Diese sorgt dafür, dass das Layout geladen und angezeigt wird und beinhaltet die u.a. die Interaktionslogik.

Jede Activity durchläuft einen Lebenszyklus, der dafür sorgt, dass so viele Anwendungen wie möglich im Arbeitsspeicher gehalten werden, aber auch wenn nötig Anwendungen zwangsbeendet um RAM für neue Apps freizuschaufeln.

In jede der im Bild genannten Funktionen (onCreate(), onStart(), onResume(), onPause(), onRestart(), onStop() und onDestroy()) kann man sich reinklinken indem man die Methode in der Activity überschreibt.

Am häufigsten wird man onCreate() überschreiben, um u.a. das Layout zu laden und initiale Vorbereitungen zu treffen.

Für weitere Informationen ist dieser Artikel aus der Android-Entwicklerdokumentation empfehlenswert.

Fragments

Die erste Version soll nach dem Start einen fest hinterlegten RSS-Feed als Liste darstellen. Bei einem Klick auf einen Listeneintrag soll dieser in einer Detailansicht geöffnet werden.

Möglichkeit Nr.1 wäre zwei Activities anzulegen. Eine für die Liste, eine weitere für die Detailansicht. Klickt man auf einen Listeneintrag wird auf die zweite Activity gewechselt.

Dieses Vorgehen würde zwar funktionieren, bietet aber einen entscheidenden Nachteil, wenn man die App für unterschiedliche Bildschirmgrößen anpassen will.

Hierzu müsste man etwa eine extra Activity für Tablets anlegen um dort die entsprechende Logik für die Behandlung der Listen- und Detailansicht zusammen unterzubringen. Dazu wird man wahrscheinlich Code aus den Smartphone-Activities kopieren. Am Schluss kommt höchstwahrscheinlich eine App mit ziemlich unwartbaren Code und sehr vielen Codeduplikaten raus.

Das muss zum Glück nicht sein, da Google entsprechende Hilfsmittel zur Verfügung stellt, die so genannten Fragmente. Ein Fragment beinhaltet ähnlich wie eine Activity das Layout (z.B. eine Liste) und die dazugehörige Liste, kann aber mit dem Unterschied einer Activity als Element hinzugefügt werden.

Abwärtskompatibilität

Nun gibt es nur ein kleines Problem. Die ganzen coolen Sachen wie Fragmente und die ActionBar gibt es erst in den Versionen nach 2.x. Stellt aber kein Problem dar, da Google für diesen Fall wie oben erwähnt die Android Support Library geschaffen hat.

In unserem Projekt liegt zwar im Ordner libs schon eine Version dieser Bibliothek, bloß ist diese für unsere Zwecke höchstwahrscheinlich veraltet. Deshalb raus aus dem Projekt damit ;)

Nun muss man nur sicherstellen, dass die neuste Version abgerufen werden kann. Hierzu installiert man (falls nicht installiert) im SDK Manager die Einträge:

  • Google Repository
  • Android Support Repository

Nachdem diese beiden Repositories installiert wurden, muss nur noch die build.gradle des Projekts angepasst werden. Dort ist noch unter dependencies ein Verweis auf die alte Support-Bibliothek hinterlegt, den man durch die folgenden Einträge ersetzt:

dependencies {
compile \'com.android.support:support-v4:18.0.+\'
compile \"com.android.support:appcompat-v7:18.0.+\"
}

Der zusätzliche Eintrag mit appcompat im Namen ist eine von Google bereitgestellte Bibliothek um die ActionBar auch unter Android 2.x nutzen zu können.

Nun müssen noch ein paar kleine Anpassungen vorgenommen werden, damit die Bibliothek verwendet wird:

MainActivity.java
Anstatt von Activity muss diese Klasse nun von ActionBarActivity erben.

AndroidManifest.xml
Dort muss man nur festlegen, dass anstatt des Systemstyles der Compat-Library verwendet soll:

Den folgenden Eintrag :

android:theme=\"@style/AppTheme\" >

zu diesen abändern:

android:theme=\"@style/Theme.AppCompat.Light.DarkActionBar\" >

Die erste Activity

Nun genug Basiswissen, schauen wir uns mal den Sourcecode der MainActivity.java an:

In der generierten Datei ist es zunächst noch sehr übersichtlich. Es handelt sich hier um eine ganz normale Javaklasse welche die beiden Methoden onCreate und onCreateOptionsMenu der Oberklasse überschreibt.

In der onCreate Methode wird durch den Aufruf von setContentView(R.layout.activity_main) aus der XML-Datei activity_main.xml das Layout geladen. Hierzu dient die statische Klasse R als Verbindungsbrücke zwischen den XML-Dateien und dem Java-Code. Immer wenn man Änderungen am Projekt vornimmt, läuft im Hintergrund ein Prozess welcher die XML-Dateien im Ordner res analysiert und eine neue Klasse R generiert.

Ein wichtiger Hinweis noch: Es gibt mehr als eine R-Klasse. Zum einen bringt das SDK eine solche Klasse mit, sowie viele Zusatzbibliotheken, wie z.B. die ActionBarCompat-Library. Wenn mal ein Eintrag zu einer XML-Datei nicht gefunden wurde, sicherheitshalber nachschauen, welche R-Klasse als Import definiert wurde ;)

Ein Fragment

Das erste Fragment soll eine Liste von Einträgen aus dem RSS-Feed darstellen. Zunächst erstellt man ein neues Java-Package mit dem Namen de.rssreader.fragments (Der Wert de.rssreader muss evtl. angepasst werden, wenn bei der Erstellung des Projekts ein anderer Packagename gewählt wurde.

In diesem Package erstellt man eine neue Java-Klasse mit dem Namen FeedItemsListFragment.

Diese Klasse lässt man nun von der Oberklasse ListFragment erben (aus dem Package android.support.v4). Dies bietet im Gegensatz zur Klasse Fragment den Vorteil, dass bereits die Liste direkt mitangelegt wird und keine extra Layoutdatei benötigt wird.

Im Editor öffnet man mit [Strg] + [O] das “Method Override“ Menü, mit der man die Methode onCreateView überschreibt. Diese Methode wird aufgerufen wenn das Fragment das Leben erblickt.

Zunächst einmal soll die Liste nur generierte Demodaten anzeigen, damit man wieder was zum ausprobieren hat :)

Listen werden in Android über einen so genannten ListAdapter definiert. Dieser Adapter erbt von der Klasse BaseAdapter und stellt neben den Daten auch das Layout zur Verfügung. Wir werden später dann einen eigenen Adapter schreiben um den RSS-Feed mit Bildern und Co. darstellen zu können. Aber in diesem ersten Versuch reicht ein vom SDK bereitgestellter ArrayAdapter auch aus.

setListAdapter(
new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, items)
);

Dieser benötigt als Argumente zunächst den Context der App an sich. Über den Context kann man auf verschiedenste Werte zugreifen, die auch der Adapter benötigt. Da die aktive Activity den Context repräsentiert, kann man vom Fragment aus ganz simpel über getActivity() darauf zugreifen.

Als zweiten Parameter wird ein int-Wert verlangt der auf eine Ressource verweist welche das Layout abbildet, wie der Listeneintrag auszusehen hat. Hier stellt das SDK schon eine Vorlage für einen einzeiligen Eintrag zur Verfügung, welchen man in der Ressourcen-Klasse des SDKs findet. Da man aber die eigene R-Klasse schon über import eingebunden hat, sollte man hier den vollen Paketnamen angeben.

Der letzte Parameter ist die Liste von Werten die dargestellt werden soll, in unserem Fall die generierten Demowerte.

Über die von der Oberklasse bereitgestellte Methode setListAdapter kann man den erstellten Adapter an die Liste übergeben.

Zum Abschluss muss man das Fragment nur noch im Layout hinterlegen, welches von der MainActivity geladen wird. Hierzu öffnet man die Datei activity_main.xml (zu finden unter res/layout/).

Jede Layoutdatei besitzt wie bei XML üblich ein Wurzelelement bestehend aus einem Layoutcontainer. Das ist in der generierten Datei ein RelativeLayout. Der Vorteil dieses Layouts ist, dass man Elemente wie der Name schon sagt relativ zueinander positionieren kann (z.B. die Textbox mit einem Abstand von 5dip zum Button). Im Layout entfernt man den “Hello World“-TextView und hinterlegt dort wie im Screenshot das Fragment. (Man kann auch hierzu den Designer verwenden, umschaltbar am unteren Bildschirmrand).

XML-Attribute in Layoutdateien

layout_width und layout_height: Geben an, wie hoch und breit die Elemente sind. Neben numerischen Werten (z.B. 100dip) kann man auch die Werte match_parent (Das Element nimmt den ganzen verfügbaren Platz ein) und wrap_content (Das Element nimmt seine minimale Größe ein) verwenden.

padding: Der Abstand zu den inneren Elementen.

id: Eine Id, mit der man das Element im Programmcode ansprechen kann.

name: Spezialwert für das Fragment, damit es weiß, welche Java-Klasse es repräsentieren soll.

Bei der Einheit dip handelt es sich um \“Device independent pixels\“ (Geräteunabhängige Pixel). Ein dip entspricht bei einem Gerät mit einer Pixeldichte von 160dpi genau eine physikalischen Pixel. Der Vorteil dieser Angabe es es, dass diese Werte auf Smartphones und Tablets mit anderen Auflösungen korrekt skaliert werden und so immer noch einen Effekt haben. Wenn man stattdessen einen festen Abstand in Pixel eingibt, kann es auf den eigenen Full-HD-Gerät super ausschauen, aber auf niedriger aufgelösten Geräten schaut das Layout katastrophal aus. Mehr zu diesem Thema gibt es in der nächsten Episode, wo es allgemein mehr über das Layouten von Oberflächen geht.

Hat man das Fragment hinterlegt, kann man nun die App wieder starten und sollte folgendes sehen:

RSS-Feed auslesen

Nachdem man nun mit der Liste etwas herumgespielt haben, geht es nun an das Auslesen des RSS-Feeds. Da unser Lieblings-Tech-Blog auch einen solchen Feed anbietet bedienen wir uns mal einfach dort ;) Der Feed ist direkt unter http://feeds.feedburner.com/mobiFlip/ zu erreichen.

Bevor man sich nun in das Parsen von RSS-Feeds einlest nutzen wir eine der vielen bereits vorhanden RSS Bibliotheken im Internet wie z.B. https://github.com/salendron/Simple-Rss2-Android. Dieser Entwickler bietet sogar eine bereits kompilierte Jar-Datei an, welche man nun direkt in das Projekt einbinden kann.

Dazu legt man im Projektbaum unter “RSSReader“ einen neuen Ordner namens libs in welchen man nun die neue JAR kopiert. Im Anschluss muss dem Gradle-Buildsystem nur noch gesagt werden wo es die Datei findet. Den Bereich dependencies der build.gradle ergänzt man um folgenden Eintrag:

compile files(\'libs/Simple-Rss2-Android.jar\')

Falls sich der Dateiname geändert hat, bzw. man die Datei umbenannt hat, muss hier entsprechend angepasst werden. Die Dependencies sollten auf alle Fälle nun so aussehen:

dependencies {
compile \'com.android.support:support-v4:18.0.+\'
compile \"com.android.support:appcompat-v7:18.0.+\"
compile files(\'libs/Simple-Rss2-Android.jar\')
}

Falls man die App irgendwann veröffentlichen will, muss man die entsprechenden Lizenzen der externen Bibliotheken beachten.

Da man zum Abrufen des Feeds Internetzugriff benötigt, sollte man nun das Manifest anpassen, da ansonsten entsprechende Exceptions auftreten. Um die Permission hinzuzufügen reicht es, wenn im Manifest über den application-Knoten folgende Zeile einfügt:

Zum Abschluss sollte das Projekt über den Menüpunkt Build => “Rebuild Project“ komplett neu gebaut werden, damit die neue Bibliothek auch erkannt wird.

Nun sind Anpassungen am FeedItemsListFragment notwendig. Zuerst löscht man die ganze Demodatengenerierung in der onCreateView, sodass nur das Return-Statement übrigbleibt.

In der Klasse legt man nun eine neue Methode loadFeed an, welche das Laden des Feeds übernimmt. Zunächst wird ein ProgressDialog vorbereitet, welcher – wie so oft gesehen – dem Benutzer durch eine entsprechende Meldung inkl. Animation hinweist, dass gerade was geladen wird.

Daraufhin legen wird eine neue Instanz des SimpleRss2Parser an, welcher zwei Argumente erwartet, die URL des Feeds, welchen man über den Parameter der Methode bekommt, sowie einen Callback der aufgerufen wird, sobald das Laden des Feeds abgeschlossen wurde.

Im onFeedParsed-Callback wird wie im Beispiel zuvor der ListAdapter des Fragments festgelegt. Den FeedItemListAdapter legen wird im nächsten Schritt an.

Falls ein Fehler beim Laden auftreten sollte, wird ein so genannter Toast am Bildschirm eingeblendet (die kleinen Textoverlays die nach einer Zeit wieder verschwinden). Normalerweise sollte kein Exception-Inhalt an den Nutzer weitergegeben werden, aber zur Demonstration kann man es mal machen ;)

Aber das Ganze ist ein Callback der erst später selbstständig aufgerufen wird. Im normalen Programmablauf wird nun der Wartedialog angezeigt und darauf das Laden des Feeds angestoßen, welches asynchron im späteren Programmablauf den Callback aufruft.

Ein kurzer Einschub bezüglich Threads und asynchroner Programmierung:

Es gibt in Android einen UI-Thread, welcher die Elemente anlegt und auch als einziger modifizieren darf.

Aufwendige Berechnungen und auch I/O (Internet, Dateisystem) sollten dringend in Threads ausgelagert werden. Falls dies trotzdem unter dem UI-Thread geschieht, hängt zum Einen die App und zum Anderen bekommt der Benutzer den schönen “Die App reagiert nicht\“-Dialog zu sehen.

Im onCreateView wird die neu angelegte Methode nun nur noch mit der Feed-URL aufgerufen.

Ein eigener ListAdapter

Jetzt brauchen wir nur noch dem im Fragment verwendeten ListAdapter. Dies ist aber kein besonderer Aufwand. Es ist eine ganz normale Klasse die von BaseAdapter erbt, sowie über einen Konstruktor den Kontext und eine Liste von Elementen annimmt. Die Methoden die der abstrakte BaseAdapter implementiert lassen sich hauptsächlich mit Einzeilern ausfüllen.

getCount: Anzahl der Elemente der Liste

getItem: Das Element an der übergebenen Position

getItemId: In ermangelung einer eindeutigen ID, die das Element auszeichnet, geben wir hier einfach das zurück was das System dem Adapter gibt :P

Bei getView wird es schon interessanter. Als erstes holen wir das RSSItem über die an die Methode übergebene Position aus der Liste. Daraufhin rufen wir über den Context den LayoutInflater Service, welcher in der Lage ist aus einer Layout-XML ein View-Objekt zu erzeugen.

Der inflate-Methode übergibt man die Resourcen-ID (über das R-Objekt), das Elternelement an dem die View geladen wird, sowie über den dritten Parameter, dass das erzeugte Element nicht an das Wurzelelement aller Views gehängt werden soll. (Falls hier true steht, gibt es eine Exception, dass der View bereits mehrfach registriert wurde).

Im Anschluss kann über die Methode findViewById die jeweiligen Layoutelemente aus der View geladen werden. Dazu ist ein Cast in den entsprechenden Ziel-Typ notwendig, da findViewById nur die Oberklasse View zurückliefert.

Der Rest ist relativ simpel. Neben ein paar Abprüfungen auf Null werden den TextView-Elementen (stellen statischen Text dar) der Text aus dem RSSItem zugewiesen. In unserem Fall reichen der Titel, sowie die Kurzzusammenfassung.

Am Schluss gibt man den View über return an das System zurück.

Der fertige Code schaut nun so aus:

public class FeedItemListAdapter extends BaseAdapter {
private Context mContext;
private List mItems;
public FeedItemListAdapter(Context c, List items) {
mContext = c;
mItems = items;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
RSSItem rssItem = mItems.get(position);
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.listitem_feeditem, parent, false);
if(view == null) return null;
TextView tvFeedItemHeader = (TextView) view.findViewById(R.id.tvFeedItemHeader);
TextView tvFeedItemDescription = (TextView) view.findViewById(R.id.tvFeedItemDescription);
if(tvFeedItemHeader != null)
tvFeedItemHeader.setText(rssItem.getTitle());
if(tvFeedItemDescription != null)
tvFeedItemDescription.setText(rssItem.getDescription());
return view;
}
}

Das Studio weißt nun einen darauf hin, dass weder das angegebene Layout, noch die IDs der TextViews existieren.

Um das zu beheben legt man im Ordner res/layout ein neues „Layout resource file“ vom Namen listitem_feeditem an.

In diesem Layout wird nur ein einzelner Listeneintrag definiert, welcher in diesem Fall nur aus einer größeren Überschrift und der Kurzzusammenfassung in kleinerer Schrift besteht. Das android:textAppearance sorgt dafür, dass die beiden Texte unterschiedlich aussehen.

Der LinearLayout-Container kann Elemente entweder vertikal oder horizontal untereinander bzw. benebeinander dartellen.

Liste voller Tech-News

Nun wäre wieder der Zeitpunkt um das Werk zu begutachten :) Wenn man alles richtig gemacht hat, sollte nun eine Liste der letzten MobiFlip-Nachrichten auftauchen.

Falls allerdings die App abstürzen sollte, bietet die LogCat (Protokoll-Katze :-)) sehr gute Hilfe. Wenn ein Android-Gerät verbunden ist, kann man indem man die Tasten [Alt] + [6] drückt die LogCat-Ansicht öffnen.

LogCat ist ein kontinuierlicher Fluss von Meldungen aus dem Android-System. Stürzt die App ab, sieht man dort in roter Schrift die Exception-Meldung geschrieben, die einem bei der Problembehebung behilflich sein kann :) Außerdem kann man dort Filter setzen, damit die eigenen Meldungen nicht übersehen werden.

Ich bedanke mich schon mal fürs Lesen und freue mich auf eure Meinungen, Fragen, Anregungen und (konstruktive) Kritik in den Kommentaren. Außerdem würde ich mich wie immer freuen, wenn ihr mir auf Twitter folgt.

Den kompletten Sourcecode findet ihr in meinem GitHub-Repository. Der hier gezeigte Code ist noch nicht komplett fehlerfrei, sowie könnte man an einigen Stellen den Code noch optimieren und schöner machen. Darauf wurde allerdings (bislang) verzichtet, um den Artikel nicht zu überladen.

Info

In der nächsten Episode geht es u.a. an die Detailansicht, sowie an das Tablet-UI. Alle Teile der Serie (und damit auch die noch folgenden Parts) könnt ihr hier gesammelt einsehen:

Android-Entwicklung für Einsteiger


Fehler meldenKommentare

Bitte bleibe freundlich.

  1. Das DISQUS-Kommentarsystem verarbeitet personenbezogene Daten. Das System wird aus diesem Grund erst nach ausdrücklicher Einwilligung über nachfolgende Schaltfläche geladen. Es gilt die Datenschutzerklärung.