Was ist ein AsyncTask?
Bei einem AsyncTask handelt es sich um eine (Helfer-)Klasse aus der Standard-Bibliothek von Android, die eine vereinfachte Abwicklung von länger andauernden und asynchronen Aufgaben im Hintergrund ermöglicht.
In Android-Apps werden standardmäßig alle Methoden im sogenannten Main-Thread ausgeführt, der auch UI-Thread genannt wird, da hier unter anderem die Ausgabe der Bildschirmoberfläche verarbeitet wird. Das nun die Benutzeroberfläche (GUI) nicht unnötig durch komplexe Berechnungen blockiert wird, sollten zeitintensive Methoden vom Main-Thread auf einen Worker-Thread ausgelagert werden, um den Main-Thread zu entlasten. Im Worker-Thread wird der Code dann abgearbeitet und am Bearbeitungsende das Ergebnis an den Main-Thread zurückgeliefert. Dieses Outsourcing von Code kann in Android entweder duch Java-Threads oder die AsyncTask-Klasse realisiert werden.
AsyncTasks können z.B. aus folgenden Gründen eingesetzt werden:
- Für kurze oder unterbrechbare Tasks
- Für Tasks mit niedriger Priorität, die unvollendet bleiben können
- Für Tasks, die sich nicht an die Benutzeroberfläche oder den Benutzer zurückmelden müssen
In diesem Blog-Artikel beschränken wir uns auf die Erklärung der AsyncTask-Klasse und deren Verwendung unter Android, gehen jedoch nicht näher auf Threads ein.
Info: Im Programmier-Umfeld handelt es sich bei einem Thread um einen Ausführungsstrang, der zur Abarbeitung von Code dient. Wird Code auf mehrere Threads verteilt, so spricht man von Multi-Threading und der Code wird parallel abgearbeitet. In Android stellt der Main-Thread den hauptsächlichen und ein Worker-Thread einen nebensächlichen Ausführungsstrang dar.
Wie wird ein AsyncTask verwendet?
Für die Verwendung der AsyncTask-Klasse erstellst du innerhalb einer Activity zunächst eine innere Klasse mit beliebigem Namen (z.B. ExampleTask) und leitest von AsyncTask ab. In der „onCreate()„-Methode der Activity erzeugst du dann eine neue Instanz dieser Klasse und rufst den Task mittels der „execute()„-Methode auf:
private class MainActivity extends Activity { private ExampleTask myExampleTask; //Diese ProgressBar wird später noch verwendet ProgressBar myProgressBar = findViewById(R.id.progressBar); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /* Mit execute() wird der erste Parameter "Params" festgelegt, wobei hier eine beliebige Anzahl Parameter übergeben werden kann */ myExampleTask = new ExampleTask(); myExampleTask.execute("Hier wird Text übergeben"); } class ExampleTask extends AsyncTask<String, Integer, String> { //Hier werden alle Methoden von AsyncTask implementiert (siehe Schaubild) } } Erklärung der AsyncTask-Parameter: Für die Oberklasse "AsyncTask" müssen Datentypen für drei generische Parameter definiert werden. Info: Falls du einen Parameter nicht verwenden möchtest, dann lege einfach den Datentyp "Void" fest. AsyncTask<Params, Progress, Result> -> Params: Über den ersten Parameter "Params" wird die Anzahl der Parameter und deren Datentyp über die "execute()"-Methode festgelegt und als Array an die "doInBackground()"-Methode übergeben. -> Progress: Der zweite Parameter "Progress" gibt den Datentyp an, der zuerst an die "publishProgress()"-Methode im Worker-Thread und später an die "onProgressUpdate()"-Methode im Main-Thread übergeben wird. -> Result: Mit dem dritten Parameter "Result" wird der Datentyp festgelegt, der von der "doInBackground()"-Methode zurückgegeben wird. Dieser Parameter wird automatisch an die Methode "onPostExecute()" auf dem UI-Thread übergeben. Parameter-Erklärung am Beispiel: Hier nochmal unser Beispiel von oben: ExampleTask extends AsyncTask<String, Integer, String> -> Params: Für den ersten Parametertyp wird ein "String" als Datentyp festgelegt, was bedeutet, dass in der "doInBackground()"-Methode Strings als Parameter entgegengenommen werden. -> Progress: Für den zweiten Paramertyp wird ein "Integer" als Datentyp festgelegt, was bedeutet, dass die Methoden "publishProgress()" und "onProgressUpdate()" einen Integer als Parameter entgegennehmen. -> Result: Für den dritten Parametertyp wird erneut ein "String" genommen und somit gibt die "doInBackground()"-Methode der Klasse "ExampleTask" einen String zurück, welcher am Ende an die "onPostExecute()"-Methode übergeben wird.
Innerhalb der inneren Klasse „ExampleTask“ müssen nun mehrere Methoden implementiert werden, die wir dir unterhalb des nachfolgenden Schaubildes beschreiben.
1) onPreExecute(): Diese Methode wird vor dem Task-Start im Main-Thread ausgeführt und in der Regel für vorbereitende Maßnahmen verwendet. Hier könntest du z.B. einen Fortschrittsbalken für die spätere Ausführung der Aufgabe im Worker-Thread sichtbar machen. Beispiel: protected void onPreExecute() { super.onPreExecute(); myProgressBar.setVisibility(ProgressBar.VISIBLE); myProgressBar.setProgress(0); }
2) doInBackground(): Diese Methode wird direkt nach der "onPreExecute()"-Methode aufgerufen und vollständig entkoppelt vom Main-Thread auf dem nebenläufigen Worker-Thread im Hintergrund ausgeführt. Innerhalb von "doInBackground()" werden Berechnungen durchgeführt und am Ende das Resultat an die "onPostExecute()"-Methode des Main-Threads zurückgeliefert. Außerdem kann die "doInBackground()"-Methode die "publishProgress()"-Methode aufrufen, um den aktuellen Fortschritt zu veröffentlichen. Beispiel: protected String doInBackground(String... params) { String s_string = params[0]; for(int i = 0; i <= 1000; i++) { for(int j = 0; j <= 1000; j++) { /* Hier wird die "publishProgress()"-Methode aufgerufen und das Ergebnis aus i*j als Übergabeparameter an die "onProgressUpate()"-Methode übergeben */ publishProgress(i * j); } } return "Dieser Text wird am Berechnungsende an die "onPostExecute()"-Methode geliefert"; }
3) publishProgress(): Diese Methode läuft im Worker-Thread ab und wird innerhalb der "doInBackground()"-Methode gestartet. Die "publishProgress()"-Methode ruft auf dem Main-Thread die Methode "onProgressUpdate()" auf und dient zur Veröffentlichung des aktuellen Fortschritts auf dem UI-Thread, während die Hintergrundberechnung noch läuft.
4) onProgressUpdate(): Diese Methode wird im Main-Thread eingesetzt, um während der Hintergrundberechnung auf dem Worker-Thread über den aktuellen Stand der Berechnungen zu informieren. Gestartet wird die Methode innerhalb der "doInBackground()"-Methode von "publishProgress()". Beispiel: //Der Übergabeparameter "values" stammt von der "publishProgress()"-Methode protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); myProgressBar.setProgress(values[0]); }
5) onPostExecute(): Diese Methode wird gestartet, sobald alle Berechnungen innerhalb der "doInBackground()"-Methode abgeschlossen sind. Die "onPostExecute()"-Methode bekommt alle Ergebnisse vom Worker-Thread als Übergabe-Parameter geliefert und wird auf dem Main-Thread ausgeführt. Beispiel: //Der Übergabeparameter "s_string" ist der Rückgabewert der "doInBackground()"-Methode protected void onPostExecute(String... s_string) { super.onPostExecute(s_string); myProgressBar.setVisibility(ProgressBar.GONE); }
Wie kann ein AsyncTask beendet werden?
Du kannst jederzeit und von jedem Thread aus einen Task über die "cancel()"-Methode der AsyncTask-Klasse abbrechen. In unserem Fall befinden wir uns in einer Activity mit Lebenszyklus und beenden deswegen den Task über die "onDestroy()"-Methode unserer Activity. Beispiel: @Override protected void onDestroy() { super.onDestroy(); if(myExampleTask != null) { //cancel() gibt "false" zurück, wenn der Task nicht beendet werden konnte myExampleTask.cancel(true); } } Um herauszufinden, ob ein Task abgebrochen wurde, musst du an passender Stelle innerhalb der "doInBackground()"-Methode regelmäßig den Rückgabewert der "isCancelled()"-Methode überprüfen. Die Methode "isCancelled()" gibt "true" zurück, wenn der Task abgebrochen wurde, noch bevor er normal beendet wurde. Beispiel: protected String doInBackground(String... params) { //Hier könnte Code stehen if(isCancelled()) { return null; } //Hier könnte Code stehen }