Android_Tutorial_Lektion23_fotolia_RA_Studio_46292813

Programmier Tutorial: Apps für Android entwickeln – Teil 23: Hintergrundberechnungen mit einem AsyncTask ausführen

Das Aussehen unserer Android App haben wir bereits zu großen Teilen definiert. Unsere Anwendung kann auch schon auf Eingaben des Benutzers reagieren. Was ihr jedoch momentan noch komplett fehlt, sind echte Daten, die von einem Webserver angefordert werden.

Dies werden wir in den nächsten vier Lektionen ändern und unsere App so überarbeiten, dass sie Daten von einem Webserver über das Internet anfragen, verarbeiten und darstellen kann.


Dazu werden wir in dieser Lektion die Basis für die online Datenanfrage legen und einen asynchronen Task für unsere Android App implementieren. Mit Hilfe dieses Tasks werden wir später Zitat-Daten von einem Webserver über das Internet anfragen. Diese Anfrage wird im Hintergrund ablaufen, wodurch eine flüssige Bedienbarkeit der App sichergestellt ist.

Im theoretischen Teil dieser Lektion werden wir betrachten, was Prozesse und Treads in Android sind. Dabei werden wir erfahren, was ein asynchroner Task ist und warum er für die Anfrage der online Daten zwingend benötigt wird. In diesem Zusammenhang werden wir auch die Klasse AsyncTask näher kennenlernen.

Anschließend werden wir im praktischen Teil dieser Lektion einen asynchronen Task mit Hilfe der AsyncTask-Klasse implementieren. Mit diesem Task werden wir später die Zitat-Daten von dem Webserver laden und auch verarbeiten lassen. Er wird im Hintergrund die erforderlichen Berechnungen und Anfragen durchführen.

Abschließend werden wir unsere Android App im Emulator auf einem Android Virtual Device ausführen und die Funktionsweise des AsyncTasks überprüfen.

1. Prozesse und Threads in Android

Prozesse und Threads besitzen in Android eine elementare Bedeutung. Bis auf wenige Ausnahmen wird jede Android App innerhalb eines eigenen Prozesses ausgeführt. Die dafür notwendigen Rechenoperationen werden von einem thread of execution (Ausführungsstrang oder Ausführungsreihenfolge) abgearbeitet, der dem Prozess zugewiesen wurde. Ein Prozess kann sogar mehrere Threads besitzen, wodurch sich Nebenläufigkeiten ergeben, die schnell zu kritischen Fehlern führen können.

Um robuste und stabil laufende Android Anwendungen entwickeln zu können, ist es daher für App-Entwickler unerlässlich, sich grundlegenden Kenntnisse über Prozesse und Threads zu erlangen.

Wir werden nun dieses spannende Thema von Android ausführlich behandeln. Zuerst werden wir unsere Betrachtungen auf Prozesse legen und dabei erfahren, was ein Prozess ist und welche Arten von Prozessen es in Android gibt. Danach werden wir uns den Threads zuwenden und lernen, warum es für die Benutzererfahrung so wichtig ist, bestimmte Berechnungen im Hintergrund ausführen zu lassen.

1.1 Prozesse in Android

Fast jede Android Anwendung wird in ihrem eigenen Linux Prozess ausgeführt. Dieser Prozess wird automatisch vom Android System für die App angelegt, wenn Teile ihres Quellcodes ausgeführt werden sollen. Der Prozess bleibt solange erhalten, bis er nicht mehr benötigt wird und der von ihm verwendete Speicher, vom Android Speicher-Management freigeräumt werden muss.

Dieses ungewöhnliche und grundlegende Merkmal von Android führt dazu, dass die Lebensdauer eines Anwendungsprozesses nicht direkt von der Anwendung selbst gesteuert wird. Stattdessen wird die Lebensdauer eines Prozesses von mehreren Faktoren bestimmt, die vom Android System ausgewertet werden. Diese sind unter anderem:

  • Welche anderen Komponenten der Anwendung noch ausgeführt werden
  • Wie wichtig die anderen Komponenten für den Benutzer sind
  • Wie viel Gesamtspeicher in dem System verfügbar ist

Es ist daher wichtig, dass App-Entwickler verstehen, wie sich verschiedene Anwendungskomponenten auf die Lebensdauer des Prozesses der Anwendung auswirken. Wenn diese Komponenten nicht korrekt verwendet werden, kann dies dazu führen, dass das System den Anwendungsprozess beendet, während wichtige Aufgaben von ihm ausgeführt werden.

Um zu bestimmen, welche Prozesse bei wenig Arbeitsspeicher beendet werden sollen, weist das Android System jeden Prozess eine Prioritätsstufe zu, die sich aus seinem Prozesstyp ergibt.

In Android sind folgende Prozesstypen definiert:

  1. Vordergrund Prozesse – Ein Vordergrund Prozess wird benötigt, für etwas das der Benutzer gerade ausführt. Dies kann ein Prozess sein, der eine Activity ausführt, mit der der Nutzer gerade interagiert. Oder ein Prozess, der einen Service ausführt, dessen wichtigen Callback-Methoden gerade ausgeführt werden.

    Es wird immer nur ein paar solcher Prozesse im System geben, und diese werden nur als letzter Ausweg beendet, wenn der Speicher so niedrig ist, dass nicht einmal diese Prozesse selbst weiterlaufen können.

  2. Sichtbare Prozesse – Ein sichtbarer Prozess führt Arbeiten aus, die der Nutzer direkt wahrnimmt. Beenden eines solchen Prozesses würde spürbare negative Auswirkungen auf die Benutzererfahrung besitzen. Ein solcher Prozess kann eine Activity ausführen, die der Nutzer zwar sieht, aber von einem Dialog gerade überlagert wird. Beenden der überlagerten Activity, würde dem Nutzer sofort negativ auffallen und sein Vertrauen in die Anwendung verringern.

    Die Anzahl ausgeführter sichtbarer Prozesse ist gering, aber deutlich höher als im Vordergrund ausgeführte Prozesse. Sichtbare Prozesse werden als extrem wichtig betrachtet und nur beendet, wenn es für das Ausführen aller Vordergrund Prozesse notwendig wird.

  3. Service Prozesse – Ein Service Prozess führt einen Service aus, der mit der startService() Methode gestartet wurde. Diese Prozesse sind zwar nicht direkt für den Benutzer sichtbar, führen aber dennoch wichtige Arbeiten im Hintergrund aus, wie Netzwerkdaten Up– oder Download, die für den Nutzer von großem Interesse sind.

    Das Android System wird Service Prozesse nur beenden, wenn nicht mehr genügend Speicher für die gerade ausgeführten Vordergrund und sichtbaren Prozesse vorhanden ist.

  4. Zwischengespeicherte Prozesse – Wird ein Prozess gerade nicht benötigt, speichert ihn das Android System zwischen. Solche Prozesse werden auch als Cached Processes bezeichnet. Das System kann sie nach Belieben beenden, wenn Speicherplatz freigeräumt werden muss. In einem normal arbeitenden System sind dies die einzigen Prozesse, die bei der Speicherverwaltung eine Rolle spielen.

    Ein gut laufendes System wird über mehrere zwischengespeicherte Prozesse verfügen (um effizienter zwischen Anwendungen zu wechseln) und regelmäßig die ältesten nach Bedarf löschen. Nur in sehr kritischen (und unerwünschten) Situationen wird das System zu einem Punkt kommen, an dem alle zwischengespeicherten Prozesse beendet wurden, und es beginnen muss, Service Prozesse zu schließen.

    Die zwischengespeicherten Prozesse enthalten oft eine oder mehrere Activity-Instanzen, welche momentan für den Benutzer nicht sichtbar sind (die onStop() Methode der Activity wurde aufgerufen). Wenn das System nun solche Prozesse beendet, wird dies keine negativen Auswirkungen auf die Benutzererfahrung haben. Die vorherigen Zustände können wiederhergestellt werden, wenn eine vormals geschlossene Activity in einem neuen Prozess wieder gestartet wird, vorausgesetzt, der Activity-Lebenszyklus wurde vom Entwickler korrekt implementiert.

Standardmäßig werden alle Komponenten einer App in dem gleichen Prozess ausgeführt. Dieses Vorgehen ist für die meisten Anwendungen auch die beste Wahl. Es ist jedoch möglich bestimmten Komponenten einer Android App unterschiedliche Prozesse zuzuweisen.

Dies erfolgt im App Manifest über die Komponenten-Elemente <activity>, <receiver> und <provider>, die alle das Attribut android:process unterstützen, mit dessen Hilfe eine Prozess-ID zugewiesen werden kann. Auf diese Weise können bestimmte Komponenten einer Android App in ihren eigenen Prozessen ausgeführt werden und andere Prozesse wiederum sich einen Prozess teilen.

1.2 Threads in Android

Prozesse und Threads sind in Android miteinander verbunden. Wird in Android eine App gestartet, legt das Android System einen neuen Prozess für die Anwendung mit einem einzelnen Ausführungsstrang (single thread of execution) an.

Standardmäßig werden alle Komponenten einer App in dem gleiche Prozess von dem gleichen Ausführungsstrang ausgeführt. Dieser wird als Main-Thread bezeichnet und ist für die Anwendung von großer Bedeutung. Er ist bspw. für das Versenden von Events an die entsprechenden User Interface Widgets verantwortlich.

Außerdem interagiert die eigene Anwendung über diesen Thread mit Komponenten des Android UI Toolkits, wie den Komponenten aus den android.widget und android.view Paketen. Daher wird der Main-Thread auch oftmals als UI-Thread der Android Anwendung bezeichnet.

Auch werden alle System calls an die Komponenten aus dem UI-Thread heraus versendet. Daher werden alle Methoden die auf system callbacks antworten (wie die onItemClick() Methode), immer auch in dem Main-Thread bzw. UI-Thread ausgeführt.

Berührt bspw. ein Nutzer einen Button, dann sendet der UI-Thread der App ein Touch-Event an das Widget. Das Widget setzt seinen Zustand auf pressed und legt ein invalidate request in die event queue. Der UI-Thread arbeitet die Ereignis-Warteschlange und somit das invalidate request ab und teilt anschließend dem Widget mit, sich neu zu zeichnen.

Dieses Ein-Thread-Model (single thread model) führt zu einem ernsthaften Problem, sobald eine Anwendung sehr zeitintensive Aufgaben (wie Netzwerkzugriffe oder Datenbankanfragen) in dem UI-Thread ausführt. Dies würde den UI-Thread nämlich blockieren und es könnten keine Events mehr versendet werden, auch keine drawing events.

Auf Benutzereingaben könnte die App dann nicht mehr reagieren und für den Benutzer wie abgestürzt erscheinen. Wird der UI-Thread für mehr als einige Sekunden blockiert (ca. 5 Sekunden), wird der Application not responding-Dialog angezeigt. Dies würde den Benutzer enttäuschen, so dass er im schlimmsten Fall die App deinstalliert. Und das sollte unbedingt vermieden werden.

Zudem ist das Android UI Toolkit nicht thread-safe. Daher darf auf keinen Fall von einem Hintergrund-Thread aus die Benutzeroberfläche gesteuert werden. Alle Manipulationen an dem User Interface müssen in Android immer vom UI-Thread bzw. Main-Thread aus erfolgen.

Daher sollten beim Umgang mit dem Ein-Thread-Model zwei einfache Regeln stets befolgt werden:

  1. Niemals den UI-Thread (Main-Thread) blockieren.
  2. Niemals auf das Android User Interface Toolkit außerhalb vom UI-Thread zugreifen.

1.3 Hintergrund- bzw. Arbeits-Threads (worker thread) in Android

Aufgrund des oben beschriebenen Ein-Thread-Models von Android ist es für die Reaktionsfähigkeit der Benutzeroberfläche der Anwendung sehr wichtig, dass der UI-Thread nicht blockiert wird. Daher müssen zeitintensive Berechnungen, die nicht unmittelbar abgeschlossen werden können, in einem separaten Thread ausgeführt werden.

Solche Threads, die für das Abarbeiten von andauernden Aufgaben verantwortlich sind, werden als Hintergrund- bzw. Arbeits-Threads bezeichnet. Da diese Threads im Hintergrund ausgeführt werden, können von ihnen aus nicht die UI-Elemente aktualisiert werden. Dies kann nur von dem UI-Thread, also dem Main-Thread, aus erfolgen.

Es muss daher immer ein Zusammenspiel zwischen UI-Thread und Hintergrund-Thread erfolgen. Das heißt, der Hintergrund-Thread muss den UI-Thread über den Status der Berechnungen informieren und ihm das Ergebnis übergeben können. Um dieses Problem zu lösen, sind in Android bereits einige Werkzeuge implementiert worden, welche die Kommunikation zwischen UI-Thread und Hintergrund-Thread übernehmen.

Eines der besten Werkzeuge, das Android für diese Aufgabe zur Verfügung stellt, ist die AsyncTask-Klasse, welche die Ausführung von Hintergrundberechnungen vereinfacht und dafür sorgt, dass UI-Thread und Hintergrund-Thread miteinander interagieren können.

Wir werden daher das Anfragen der Webserver-Daten von einem AsyncTask ausführen lassen und dadurch sicherstellen, dass die Benutzeroberfläche unserer Android App immer flüssig bedienbar bleibt.

2. Mit einem AsyncTask Hintergrundberechnungen durchführen

Die Klasse AsyncTask ermöglicht das Ausführen einer asynchronen Aufgabe, ohne dabei den UI-Thread zu blockieren. Dabei lässt er die zeitaufwendigen Berechnung in einem eigenen Thread, dem HintergrundThread, ausführen. Nach erfolgter Berechnung gibt der AsyncTask die Ergebnisse automatisch an den UI-Thread weiter.

Um einen AsyncTask nutzen zu können, muss eine eigene Klasse von der AsyncTask-Klasse abgeleitet und in ihr die Methode doInBackground() implementiert werden. Diese Methode wird dann als Arbeiter-Thread in einem Pool von Hintergrund-Threads ausgeführt.

Sobald die Berechnungen abgeschlossen sind, wird die onPostExecute() Methode des AsyncTasks automatisch aufgerufen, in welcher die Ergebnisse weiterverwendet werden können. Das Besondere an der onPostExecute() Methode ist, dass sie im UI-Thread ausgeführt wird und daher von ihr aus die Benutzeroberfläche sicher aktualisiert werden kann.

Gestartet wird die Hintergrundberechnung schließlich aus dem UI-Thread heraus durch Aufrufen der execute() Methode auf der AsyncTask-Instanz. Wie dies genau erfolgt, werden wir nun ausführlich betrachten.

2.1 Die Klasse AsyncTask im Detail

Wie bereits erwähnt, ermöglicht die AsyncTask-Klasse das Ausführen zeitaufwendiger Hintergrundaufgaben in einem Arbeiter-Thread und Weitergeben deren Ergebnisse an den UI-Thread, ohne dass dabei die Threads selbst verwaltet werden müssen.

Die AsyncTask-Klasse ist als eine Helper-Klasse konzipiert, die asynchrone Berechnungen auf einem Hintergrund-Thread ausführen lässt. Für sehr lange Berechnungen ist sie jedoch nicht geeignet, dafür stellt Android mächtigere und komplexere Werkzeuge zur Verfügung, die in dem Paket java.util.concurrent enthalten sind.

Der Klassenname AsyncTask leitet sich von den Wörtern asynchronous task her, die vom Sinn her für eine Berechnung stehen, welche im Hintergrund ausgeführt und deren Ergebnis zu einem unbestimmten Zeitpunkt an den UI-Thread weitergegeben wird.

Schauen wir uns nun einen AsyncTask etwas genauer an:

private class RequestQuotesTask extends AsyncTask<Integer, String, List<Quote>> {

    @Override
    protected List<Quote> doInBackground(Integer... intParams) {
        // Die doInBackground() Methode wird im Hintergrund-Thread ausgeführt
        // und führt die zeitaufwendigen Aufgaben durch.
        int quotesCount = intParams[0];
        List<Quote> newQuoteList = doTimeConsumingCalculation(quotesCount);

        // Mit publishProgress() kann von hier der Fortschritt durchgegeben werden.
        publishProgress("Es wurden bereits 5 Zitate geladen.");        

        // Das Ergebnis der Berechnungen wird zurückgegeben und kann in der
        // onPostExecute() Methode, die im UI-Thread läuft, weiterverwendet werden
        return newQuoteList;
    }

    @Override
    protected void onProgressUpdate(String... stringParams) {
        // Die onProgressUpdate() Methode wird im UI-Thread ausgeführt.
        // Auf dem Bildschirm wird eine Statusmeldung ausgegeben, immer wenn
        // publishProgress() von doInBackground() aufgerufen wird.
        String message = stringParams[0];
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onPostExecute(List<Quote> receivedQuoteList) {
        // Die onPostExecute() Methode wird im UI-Thread ausgeführt.
        // Das Ergebnis von doInBackground() wird hier weiterverwendet, es steht 
        // als Parameter zur Verfügung.
        updateListView(receivedQuoteList);
    }
}

In dem oberen Quellcode ist ein eigener asynchroner Task implementiert worden. Dazu musste eine eigene Klasse von der AsyncTask-Klasse abgeleitet und in dieser die Methoden doInBackground(), onPostExecute() und onProgressUpdate() überschrieben werden.

Um den asynchronen Task zu starten, muss nun noch eine Instanz der oben definierten Klasse erzeugt und auf dieser Instanz die execute() Methode aufgerufen werden. Dieser Methodenaufruf muss unbedingt aus dem UI-Thread heraus erfolgen. In unserem Fall wäre dies die MainActivity, welche in dem UI-Thread bzw. Main-Thread ausgeführt wird:

Mit dem folgenden Quellcode wird der asynchrone Task gestartet:

MainActivity.java

private void startMyAsyncTask() {
    int quoteCount = 10;
    RequestQuotesTask requestQuotesTask = new RequestQuotesTask();
    requestQuotesTask.execute(quoteCount);
}

Hinweis: Es gibt bestimmte Situationen in denen ein Hintergrund-Thread vorzeitig beendet werden kann. Wird die Bildschirmausrichtung von dem Benutzer durch Drehen des Android Geräts verändert, startet die Activity neu. Dies sollte unbedingt berücksichtigt werden, wenn man von einem AsyncTask Aufgaben im Hintergrund ausführen lässt.

2.1.1 Die drei generischen Typen eines asynchronen Tasks

Wie an der Klassendeklaration der im vorherigen Abschnitt definierten RequestQuotesTask-Klasse zu erkennen ist, wird der asynchrone Task mit Hilfe von drei generischen Typen definiert.

Diese sind AsyncTask<Params, Progress, Result>:

  • Params – Der Datentyp der Parameter, die dem Task zu Beginn der Berechnung übergeben werden, also der doInBackground() Methode. In dem oberen Beispiel ist dies der Integer-Datentyp. Somit werden der doInBackground() Methode beim Starten des asynchronen Tasks Integer-Werte als Argumente übergeben. Mit den drei Punkten in doInBackground(Integer… intParams) wird angegeben, dass der Methode beliebig viele Integer-Argumente übergeben werden können.

  • Progress – Der Datentyp der Fortschrittseinheiten (progress units) mit deren Hilfe der aktuelle Fortschritt ausgegeben wird, während im Hintergrund die Berechnung läuft. In dem oberen Beispiel ist dies der String-Datentyp. Somit werden der onProgressUpdate() Methode als Argumente String-Werte übergeben, die über den Status der Berechnungen informieren. Auch diesmal können es wieder beliebig viele Argumente sein.

  • Result – Der Datentyp des Ergebnisses, welches nach Abschluss der Hintergrundberechnung an die onPostExecute() Methode weitergegeben wird. Über ihn wird zum Einen der Rückgabe-Datentyp der doInBackground() Methode vorgegeben, welche im Hintergrund-Thread ausgeführt wird, und zum Anderen der Parameter-Datentyp der onPostExecute() Methode, die das Ergebnis nach Abschluss der Berechnung automatisch erhält und welche im UI-Thread ausgeführt wird.

2.1.2 Die vier Schritte eines asynchronen Task

Beim Ausführen eines asynchronen Tasks werden immer vier Schritte der Reihe nach durchlaufen. Dabei ist jeder der vier Schritte einer eigenen Methode zugeordnet. Dadurch wird ermöglicht, dass die einzelnen Schritte in verschiedenen Threads ausgeführt werden können.

Das muss auch zwingend so sein, da die zeitaufwendige Berechnung in einem Hintergrund-Thread ausgeführt werden muss, jedoch das Aktualisieren der Benutzeroberfläche und Ausgeben des Fortschritts im UI-Thread erfolgen müssen.

Wird ein asynchroner Task ausgeführt, werden die folgenden vier Schritte durchlaufen:

  1. onPreExecute() – Wird von dem UI-Thread aus aufgerufen, bevor der Task ausgeführt wird. Dieser Schritt wird für die Vorbereitung genutzt, um bspw. einen Fortschrittsbalken in der Benutzeroberfläche anzuzeigen und ist optional. In unserem Beispiel haben wir diesen Schritt nicht benötigt und daher übersprungen.

  2. doInBackground(Params…) – Wird vom Hintergrund-Thread aus aufgerufen, unmittelbar nachdem onPreExecute() abgeschlossen wurde. Hierin soll die zeitaufwendige Hintergrundberechnung durchgeführt werden. Die Parameter des asynchronen Tasks werden an diese Stelle übergeben. Das Berechnungsergebnis muss zurückgegeben werden und wird an onPostExecute(Result) weitergereicht.

    Außerdem können von hier Statuswerte, die über den Fortschritt der Berechnung berichten, gesendet werden. Dazu wird die Methode publishProgress() aufgerufen, welche das Ausgeben der Fortschrittsmeldung veranlasst. Die Werte werden dadurch innerhalb des UI-Threads veröffentlicht, mit Hilfe der onProgressUpdate() Methode.

  3. onProgressUpdate(Progress…) – Wird von dem UI-Thread aus aufgerufen, nachdem publishProgress() in doInBackground(Params) aufgerufen wurde. Der Ausführungszeitpunkt ist jedoch unbestimmt. Diese Methode wird verwendet, um den Benutzer über den Fortschritt der Hintergrundberechnung zu informieren, während die Berechnung noch im Hintergrund läuft. Die Anzeige kann als Fortschrittsbalken oder Textmeldungen erfolgen.

  4. onPostExecute(Result) – Wird von dem UI-Thread aus aufgerufen, nachdem die Hintergrundberechnung abgeschlossen wurde. Das Ergebnis der Hintergrundberechnung wird ihr als Parameter übergeben.

2.1.3 Regeln für die Verwendung von der AsyncTask-Klasse

Bei asynchronen Abläufen kann es zu schwerwiegenden Fehlern kommen, daher sollten bei der Verwendung von der AsyncTask-Klasse die folgenden Regeln unbedingt eingehalten werden:

  1. Die AsyncTask-Instanz muss in dem UI-Thread erzeugt werden.
  2. Die Methode execute() muss in dem UI-Thread aufgerufen werden.
  3. Der AsyncTask kann nur einmal ausgeführt werden. Bei Mehrfachausführung wird eine Exception erzeugt.
  4. Die Methoden onPreExecute(), onPostExecute(), doInBackground() und onProgressUpdate() dürfen nicht manuell aufgerufen werden.

Werden diese vier Regeln befolgt, ist die AsyncTask-Klasse ein sehr verlässliches und einfach zu verwendendes Hilfsmittel, für die Durchführung von zeitaufwendigen Hintergrundberechnungen. Davon werden wir uns in dem nächsten Abschnitt selbst überzeugen, wenn wir einen AsyncTask für unsere Android App implementieren werden.

3. Implementieren eines AsyncTasks in Android

Im theoretischen Teil dieser Lektion haben wir erfahren, wie Prozesse und Threads in Android funktionieren. Zudem haben wir die Klasse AsyncTask und ihre Funktion näher kennengelernt. Dieses neue Wissen möchten wir als Nächstes in die Praxis umsetzen und einen asynchronen Task in unsere Android App einfügen.

Mit diesem asynchronen Task werden wir neue Zitat-Daten von unserem Webserver anfordern. Da diese Online-Abfrage durchaus sehr zeitaufwendig sein kann, muss sie in einem Hintergrund-Thread erfolgen, da sonst der UI-Thread durch sie blockiert werden könnte. Somit erfolgt die Datenabfrage über einen eigenen Arbeiter-Thread, der von dem asynchronen Task verwaltet wird.

Um den AsyncTask in unserer App zu implementieren, sind die folgenden Änderungen an der MainActivity-Klasse unseres Android Projekts erforderlich:

  1. Importieren der AsyncTask-Klasse.
  2. Definieren der inneren Klasse RequestQuotesTask, die von der AsyncTask-Klasse abgeleitet ist.
  3. Starten des asynchronen Tasks in der refreshListView() Methode. Dazu entfernen wir den bisherigen Inhalt der refreshListView() Methode und fügen zwei neue Code-Zeilen stattdessen ein.

Um die notwendigen Änderungen durchzuführen, öffnen wir die Klassendatei MainActivity.java im Editor von Android Studio. Dazu klicken wir doppelt auf ihren Dateinamen im Project Tool Window. Die Klassendatei befindet sich im Package-Ordner de.codeyourapp.zitate unseres Projekts.

In dem unten aufgeführten Quellcode der MainActivity.java Datei sind die Änderungen bereits durchgeführt worden:

An dieser Stelle endet der freie Inhalt dieser Lektion. Wir hoffen, sie hat dir bis hierher gefallen! Du kannst sie im geschützten Bereich von ProgrammierenLernenHQ fortsetzen, in welchem sich alle Lektionen unserer Android Online-Kurse befinden.

Unsere Android Kurse bestehen aus insgesamt 43 großen Lektionen und sind unterteilt in 13 frei zugängliche und 30 Premium-Lektionen. Die Premium-Lektionen befinden sich in dem geschützten Bereich und sind nur für Käufer unseres Android Online-Kurs Gesamtpaket zugänglich.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813 An dieser Stelle endet der freie Inhalt dieser Lektion. Wir hoffen, sie hat dir bis hierher gefallen! Du kannst sie durch Kauf unseres Android Online-Kurs Gesamtpakets freischalten.

In unserem Android Online-Kurs Gesamtpaket befinden sich 43 große Lektionen, in denen wir dir schrittweise zeigen, wie voll funktionstüchtige Android Apps programmiert werden.

Diese Lektion ist Teil unseres Android Gesamtpakets. Insgesamt sind unsere Online-Kurse unterteilt in 13 frei zugängliche und 30 Premium-Lektionen.

Die Premium-Lektionen befinden sich in dem geschützten Bereich und sind nur für Käufer des Android Online-Kurs Gesamtpakets zugänglich.



Welche Inhalte befinden sich im Android Online-Kurs Gesamtpaket?

  • Das Gesamtpaket enthält alle Android Online-Kurse von ProgrammierenLernenHQ.
  • Im Paket enthalten ist unser großer Android Apps Programmieren Online-Kurs. Er ist unser Hauptkurs und besteht aus 35 großen Lektionen. Die Grundlagen der Android App Entwicklung praxisnah und verständlich zu lehren, ist das Hauptziel des Android Apps Programmieren Kurses.
  • Im Paket enthalten ist auch unser SQLite Datenbank App Programmieren Online-Kurs. Dieser Spezialkurs besteht aus 8 großen Lektionen und ist als weiterführender Kurs konzipiert worden. Der Kurs schließt an unseren Android Apps Programmieren Hauptkurs an und widmet sich dem speziellen Thema der SQLite Datenbanken.
  • Durch den Kauf erhältst du unbegrenzten Zugang zu allen Inhalten unseres Android Online-Kurs Gesamtpakets. Wir werden in Zukunft weitere Lektionen hinzufügen. Auch auf alle zukünftigen Lektionen erhältst du vollen Zugriff.

Wir hoffen, Dich bald als neuen Kursteilnehmer unserer Android Online-Kurse begrüßen zu dürfen!

Einmal kaufen und dadurch zeitlich unbegrenzten Zugriff auf alle Inhalte unseres Android Online-Kurs Gesamtpakets erhalten.



Hinweis: Der untere Quellcode ist Teil des geschützten Bereichs von ProgrammierenLernenHQ. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte.
Erfahre mehr über unsere Android Online-Kurse.

android_programmieren_lernen_blurry_sourcecode

Der obere Quellcode ist Teil des geschützten Bereichs. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte. Klicke auf die Info-Box, um mehr zu erfahren.

An oben aufgeführtem Quellcode der MainActivity-Klasse wurden an insgesamt drei verschiedenen Stellen Änderungen durchgeführt. Wir werden diese nun der Reihenfolge nach durchgehen und dabei die jeweiligen Codezeilen erläutern.

In Zeile 3 wird die AsyncTask-Klasse mittels Import-Anweisung innerhalb der MainActivity-Klasse sichtbar gemacht, so dass wir sie ohne den Namen ihres Packages verwenden können.

In den Zeilen 165 und 166 wird zuerst eine Instanz der inneren Klasse RequestQuotesTask erzeugt und anschließend der asynchrone Task durch Aufrufen der execute() Methode auf der RequestQuotesTask-Instanz gestartet.

Die innere Klasse RequestQuotesTask wird schließlich mit den Zeilen 169 bis 218 definiert. Sie besteht aus einer String-Konstanten, die wir für Log-Meldungen verwenden werden, und den drei Methoden:

  • doInBackground() – Sie wird vom Hintergrund-Thread aus aufgerufen, kurz nachdem der asynchrone Task mit execute() im UI-Thread gestartet wurde. Über den Fortschritt ihrer Berechnung kann sie mit Hilfe der publishProgress() Methode berichten. Die Statusmeldungen werden dadurch innerhalb des UI-Threads mit Hilfe der onProgressUpdate() Methode veröffentlicht.

    Wir lassen von der doInBackground() Methode später die zeitaufwendige Datenabfrage durchführen. Momentan simulieren wir aber noch die Ladezeit der Anfrage, indem wir die Berechnungen einige Sekunden pausieren lassen. Beim Aufruf der doInBackground() Methode werden ihr die beiden Argumente quotesCount und parsingMethod übergeben, die wir erst in einer späteren Lektion sinnvoll verwenden werden.

    Sobald sie ihre Berechnungen abgeschlossen hat, gibt sie als Ergebnis eine Liste mit Zitat-Objekten zurück. Dieses Ergebnis, also die Zitat-Liste, steht der onPostExecute() Methode zur Verfügung und kann von ihr in dem UI-Thread weiterverarbeitet werden.

  • onProgressUpdate() – Wird von dem UI-Thread aus aufgerufen, nachdem publishProgress() in der doInBackground() Methode aufgerufen wurde. Der Ausführungszeitpunkt ist jedoch unbestimmt. Diese Methode wird verwendet, um den Benutzer über den Fortschritt der Hintergrundberechnung zu informieren, während die Berechnung noch im Hintergrund läuft. Die Anzeige kann als Fortschrittsbalken oder Textmeldungen erfolgen.

    Wir lassen von ihr eine kurze Textmeldung ausgeben, in der wir über den Beginn und das Ende der Datenanfrage informieren. Dazu lassen wir ein Toast-Meldungen auf dem Android Gerät anzeigen.

  • onPostExecute() – Wird von dem UI-Thread aus aufgerufen, nachdem die Hintergrundberechnung abgeschlossen wurde. Das Ergebnis der Hintergrundberechnung wird ihr als Parameter übergeben.

    Wir lassen von der onPostExecute() Methode die Datenquelle unseres ListViews leeren und füllen sie mit den in der doInBackground() Methode erstellten Daten. Anschließend informieren wir den ListView-Adapter darüber, dass sich der Inhalt seiner Datenquelle geändert hat. Danach teilen wir noch dem SwipeRefreshLayout-Objekt mit, dass der Aktualisierungsvorgang abgeschlossen wurde und daher das Lade-Symbol wieder ausgeblendet werden kann.

Auf diese Weise haben wir nun den asynchronen Task in unsere Android App eingefügt und implementiert. Somit sind alle Änderungen an der MainActivity-Klasse abgeschlossen.

In Android Studio sollte die MainActivity.java Klassendatei nun wie folgt aussehen:

android_programmieren_lernen_blurry_sourcecode

Der obere Quellcode ist Teil des geschützten Bereichs. Durch Freischalten unserer Android Online-Kurse erhältst du Zugriff auf alle geschützten Inhalte. Klicke auf die Info-Box, um mehr zu erfahren.

In der oberen Abbildung ist die überarbeitete MainActivity.java Klassendatei dargestellt. Es sind nur diejenigen Methoden aufgeklappt, an denen auch Änderungen vorgenommen wurden. Der Quellcode wurde an drei Stellen erweitert. Welche Bedeutung der jeweilige Code-Block besitzt, ist in der unteren Liste angegeben:

  1. A – Importieren der Klasse AsyncTask, um sie in der MainActivity-Klasse sichtbar zu machen.
  2. B – Starten des asynchronen Tasks in der refreshListView() Methode.
  3. C – Definieren der inneren Klasse RequestQuotesTask, die von der AsyncTask-Klasse abgeleitet ist.

Wir haben nun unsere Android App um einen asynchronen Task erweitert. Sie ist dadurch in der Lage zeitintensive Aufgaben im Hintergrund von einem Arbeiter-Thread ausführen zu lassen.

Führt der Benutzer die Swipe-Down Geste durch oder klickt auf den Action-Button in der App Bar, wird die refreshListView() Methode aufgerufen, von welcher der asynchrone Task gestartet wird. In der aktuellen Entwicklungsstufe unserer App führt der asynchrone Task noch keine sinnvolle Aufgabe durch, sondern simuliert eine Ladezeit von einigen Sekunden.

Wie dies zur Laufzeit auf einem Android Gerät aussieht, werden wir im nächsten Abschnitt erfahren.

4. Ausführen und Testen unserer Android App

Wir werden nun unserer Android App auf einem Android Virtual Device im Emulator ausführen lassen. Auf diese Weise können wir direkt überprüfen, wie unsere App reagiert, wenn der asynchrone Task mit einer Swipe-Down Geste gestartet wird.

Unsere App starten wir dazu wie gewohnt über den Run > Run 'app' Menüeintrag, den wir über die obere Menüleiste erreichen.

In der unteren Abbildung ist unsere Android App auf dem virtuellen Gerät zu sehen:

screenshot_asynctask

Die Beispieldaten wurden vom asynchronen Task geladen und eine Toast-Meldung ausgegeben

In der oberen Abbildung ist unsere Android App zu sehen. Der asynchrone Task hat seine Arbeit gerade abgeschlossen, wovon die noch eingeblendete Toast-Meldung Kenntnis gibt. Unmittelbar vorher hat er eine Liste mit Beispiel-Zitaten von einem Hintergrund-Thread erstellen lassen. Dadurch wurde der UI-Thread bzw. Main-Thread nicht blockiert und unsere Anwendung blieb flüssig bedienbar.

Zusammenfassung

Ziel dieser Lektion war es, einen asynchronen Task für unsere Android App zu implementieren, mit dessen Hilfe zeitaufwendige Berechnungen im Hintergrund durchgeführt werden können.

Im theoretischen Teil dieser Lektion haben wir erfahren, warum bestimmte Aufgaben in Android nur durch eine Hintergrundberechnung ausgeführt werden können. Dabei haben wir betrachtet was Prozesse und Threads in Android sind und wie die AsyncTask-Klasse genutzt werden kann, um zeitintensive Aufgaben im Hintergrund abzuarbeiten.

Anschließend haben wir im praktischen Teil dieser Lektion einen asynchronen Task mit Hilfe der AsyncTask-Klasse implementiert. Mit diesem Task werden wir später die Zitat-Daten von dem Webserver laden und auch verarbeiten lassen. Er wird im Hintergrund die erforderlichen Berechnungen und Anfragen durchführen.

Abschließend haben wir unsere Android App im Emulator auf einem Android Virtual Device ausgeführt und die Funktionsweise des asynchronen Tasks überprüft.

Somit haben wir mit dieser Lektion die Grundlagen für die Abfrage von echten online Daten gelegt. Unsere Android App ist nun in der Lage eine zeitaufwendige Anfrage an einen Webserver zu stellen und auf dessen Antwort zu warten. Momentan wird eine solche Datenabfrage aber noch durch eine Ladezeit von einigen Sekunden simuliert. In der nächsten Lektion werden wir dies ändern und eine echte online Datenanfrage vom asynchronen Task durchführen lassen.

Weiterführende Literatur




Comments 22

  1. Kenne die Videos vom Rheinwerk Verlag und das hier ist besser! Sehr gute Arbeit.

    Genug des Lobs und zu meiner Frage ;P

    Der Adapter wird in onPostExecute(…) neu befüllt. Ist im Hintergrund der GUI dann irgendein Listener, der weiß dass neuer Content da ist und die ListView refresht werden muss? Sehe sonst nämlich keine Anweisung, damit das geschieht.

    Beste Grüße
    Frank

    1. Post
      Author

      Hallo Frank,

      danke für Dein Lob!

      Die Methode add() informiert den verbundenen View selbständig und dieser aktualisiert sich dann auch von selbst. Das ist standardmäßig so in Android implementiert. Mit der Methode setNotifyOnChange() kann dies für den ArrayAdapter deaktiviert werden. Siehe folgender Link:

      Developer.android.com: ArrayAdapter setNotifyOnChange

      Viele Grüße, Chris

  2. Es tut mir leid, das klingt jetzt echt doof, aber ich bin ein ziemlicher Anfänger, und deine Tutorials sind echt super; Aber ich verstehe nicht recht, wo du die Daten nach der Aktualisierung festlegst, also wo ich den Text für ‚Aktie_1,Aktie_2‘ und so weiter, ändern kann^^

    1. Post
      Author

      Hallo Justin,

      die Daten werden in der Methode onPostExecute() dem ArrayAdapter hinzugefügt. Die onPostExecute() Methode wird automatisch aufgerufen, sobald die Daten mit Hilfe der Methode doInBackground() aus dem Internet geladen wurden.

      Dieser Prozess ist asynchron und läuft im Hintergrund ab.

      Ich hoffe meine Anmerkungen helfen Dir etwas weiter.

      Viele Grüße, Chris

    2. Aktie_1 usw kannst du im Aufruf doInBackground(args) ändern. Dort wird das ErgebnisArray befüllt und mittels postExecute „ausgeben“

      ändere den Code in doInBackground(…)

      —–> ergebnisArray[i] = strings[0] + „_“ + (i+1);

  3. Hallo Chris,

    klasse Tutorial. Ich habe endlich den Zusammenhang zwischen (Array)Daten, (Array)Adapter und (List)View verstanden (obwohl ich schon vor über einem Jahr irgendwo ein Beispiel abgekupfert und in einer eigenen App verwendet habe 😀 )

    Danke!

    Gruß, Reinhard

    1. Post
      Author

      Hallo Reinhard,

      danke für die lobenden Worte! Und nochmals vielen Dank für Deine anderen hilfreichen Kommentare in den anderen Beiträgen.

      Es freut mich sehr, wenn die Tutorials verständlich sind. Android kann manchmal ganz schön komplex sein…

      Viele Grüße, Chris

  4. Hi, erstmal danke für das ganze Tutorial, echt klasse!

    Es funktioniert alles einwandfrei, jedoch stürzt die App, nachdem ich eine Aktualisierung vornehme immer am Ende ab. Also es aktualisiert sich, der Status wird ausgegeben (5 von 20 und so weiter) und nach dem „20 von 20 geladen“ erscheint, wird die App angehalten und die Meldung vom System „AktieHQ angehalten“ erscheint. Ich habe die Zeit auch von 600 ms auf 200 ms geändert, das hat allerdings auch nichts gebracht.

    Woran kann es liegen?

    Danke 🙂

    1. Hallo Martin,

      da sollte der DDMS weiterhelfen. Irgendwo in den Tiefen des Logs sollte sich ein Hinweis auf den Absturz finden lassen. Riecht spontan danach, dass deine Version von Chris‘ Code mehr Elemente verarbeiten will, als das Array zur Verfügung stellt (also >20 – enthielt dein Code in der Methode „doInBackground“ evtl. die Zeile „for (int i=0; i <= 20; i++) {"? (also "i <= 20" statt "i<20")

    2. Jo – „i <= 20" erzeugt genau die Meldung "AktieHQ angehalten" 😉

    3. Post
      Author

      Hallo Martin,

      woran es liegt, ist ohne deinen Quellcode schwer zu sagen. Wenn du möchtest, kannst du mir deine Projektdateien als ZIP per E-Mail zusenden. Ich werde dann mal drüber schauen, vielleicht kann ich den Fehler finden. Die E-Mail Adresse kannst du im Impressum nachschauen.

      Viele Grüße, Chris

    4. Zu spät, hab mit dem nächsten Tutorial angefangen und hab jetzt den anderen Code drin. Ist aber nicht schlimm, denn es funktioniert momentan wieder alles 😀

      Danke trotzdem! 🙂

  5. Auch die Schritte dieses Kapitels funktionieren wie beschrieben auf dem Emulator, aber nicht auf meinem angeschlossenen Smartphone.
    Habe die AktieHQ-App mal deinstalliert und wollte dann nochmal mit „Run app“ neu installieren, aber das funktioniert nicht.
    Also seit dem letzten Kapitel wird die App nicht mehr auf mein Gerät übertragen, obwohl das Gerät von adb.exe bzw. dem Gerätemanager bzw. Android Studio einwandfrei erkannt wird.
    Wo könnte es haken?

    1. Habe folgende Fehlermeldung gefunden:
      „DeviceMonitor: Adb rejected connection to client ‚1367‘: closed“ und darüber folgende Diskussion bei stackoverflow:
      http://stackoverflow.com/questions/23794089/android-adb-rejected-connection-to-client
      Da start-server/kill-server keine Lösung des Problems war, habe ich den Ansatz von Andy Boot im selben Chat verfolgt und versucht, über Run–>Edit Configurations den Launch String anzupassen. Leider kann ich die Datei AndroidManifest.xml nicht als Default Activity auswählen und erhalte die Fehlermeldung „Activity Class Not Specified“.
      Ok, hierzu die Diskussion
      http://stackoverflow.com/questions/16612548/project-in-android-studio-wont-start-activity-class-not-specified
      bei stackoverflow gefunden, und m4ch3t3 scheint einen Lösungsansatz zu haben, aber wenn ich das Open Module settings-Fenster öffne, kann ich darin nicht wie beschrieben arbeiten, ich finde darin keine Ordner oder irgendwas, das „src“ oder „gen“ heißt, und bei dem ich per Rechtsklick „Source“ auswählen könnte…
      Es scheint mir, als sei ich nah an der Lösung meines Problems mit der App-Übertragung… was mache ich falsch?

      Viele Grüße,
      Daniela

    2. Post
      Author

      Hallo Daniela,

      das Deinstallieren der App sollte ohne Probleme funktionieren. Warum die App danach bei dir nicht mehr zu installieren war, ist mir rätselhaft. Vorher konntest du sie ja installieren. Eventuell ist der USB-Treiber für dein Android Smartphone nicht vollständig kompatibel mit Android Studio.

      Eine Liste der USB-Treiber findest du hier: http://developer.android.com/tools/extras/oem-usb.html#Drivers

      Viele Grüße, Chris

    3. Hallo Daniela,

      dass „AndroidManifest.xml“ nicht als Default Activity auswählbar ist, ergibt sich daraus, dass es schlicht keine Activity ist. Hier muss eine Klasse ausgewählt werden.

      Was den von dir genannten Beitrag bei stackoverflow angeht (ich meine den von „m4ch3t3“): da scheint mir einiges durcheinander zu gehen, was die Bezeichnungen und Abläufe angeht. Oder die von ihm/ihr verwendete Version ist mit der aktuellen nicht verlgeichbar.

      Jedenfalls beruht deine ursprüngliche Fehlermeldung nicht auf einer „plötzlich“ fehlenden Activity. Eher ein plötzlich fehlendes Device. Ich habe mein Smartphone einfach mal aus dem USB-Port entfernt, als ich solch eine Fehlermeldung hatte (evtl. danach kill-server/start-server, weiß ich aber nicht mehr:-( ) und wieder eingesteckt – dann war es auch wieder erreichbar.

  6. Pingback: Android Tutorial: Das SwipeRefreshLayout in Android

  7. Pingback: Android Tutorial: Activities und Intents in Android

  8. Pingback: Android Tutorial: XML-Daten auslesen und anzeigen in Android

  9. Pingback: Android Tutorial: Daten von einer Webseite abfragen in Android

  10. Pingback: Android Tutorial: Options Menu und Action Bar in Android

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.