AsyncTask zur Entlastung des Main-Threads

Android Entwicklung: Grundlagen Logo
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.

Android-Entwicklung: Async-Task-Schritte

 

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
}