23. März 2013

Android: Exception-Handling in AsyncTasks

Die sogenannten AsyncTasks können genutzt werden um zeitaufwändige Operationen, wie z.B. Netzwerkzugriffe, einer Android-App vom GUI-Thread zu entkoppeln. Dies ist notwendig, da ansonsten die Benutzeroberfläche der App während der Operation einfrieren und nicht mehr auf Benutzereingaben reagieren würde. Die offizielle API-Dokumentation der entsprechenden Klasse android.os.AsyncTask ist unter diesem Link zu finden.

Die eigentliche Ausführung der entsprechenden Operation, die von dem Task asynchron ausgeführt werden soll, geschieht nach diesem Pattern in der Methode doInBackground(Params... p):Result, die anschließende Synchronization mit dem GUI-Thread ist über die Methode void onPostExecute(Result r):void möglich. Die Klasse AsyncTask ist hierbei eine generische Klasse. Bei dem Objekttyp Params handelt es sich um die Klasse, deren Instanzen als Parameter verwendet werden sollen, der Objekttyp Result definiert die Klasse, als deren Instanz das Ergebnis des Threads zurückgeliefert werden soll.

Da AsyncTasks oftmals fehleranfällige Operationen wie Netzwerk- oder Speicherzugriffe ausführen, liegt es nahe über ein mögliches Exception-Handling nachzudenken, bei dem die Fehler nicht nur innerhalb des Tasks abgefangen werden können, sondern auch Exceptions nach außen geworfen werden können, um beispielsweise eine entsprechende Ausgabe auf der GUI zu ermöglichen. Eine Möglichkeit, eine solche Anforderung zu realisieren, soll im Folgenden erläutert werden:

Die Grundlage hierzu bietet eine generische Klasse, die als Rückgabewert des entsprechenden AsyncTasks genutzt wird. Sie kann entweder ein gültiges Ergebnis variablen Typs oder eine Exception-Instanz beinhalten. Eine beispielhafte Implementierung einer solchen Klasse ist im Folgenden zu sehen:
 public class TaskResult<Type> {  
   
     private Exception exception;  
   
     private Type result;  
   
     public TaskResult(final Type result) {  
         this.result = result;  
     }  
   
     public TaskResult(final Exception exception) {  
         this.exception = exception;  
     }  
   
     public final boolean isValid() {  
         return exception == null;  
     }  
   
     public final Exception getException() {  
         return exception;  
     }  
   
     public final Type getResult() {  
         return result;  
     }  
 }  
Die Klasse besitzt hierbei jeweils einen Konstruktor, um ein Task-Ergebnis mit einem gültigen Rückgabewert oder einer Exception zu erzeugen. Auf diese kann anschließend mit Hilfe von Getter-Methoden zugegriffen werden, wobei stets eine der beiden Methoden den Wert null zurückliefert. Außerdem bietet die Klasse noch eine weitere Methode isValid():boolean an, die es erlaubt, zu überprüfen ob das Task-Ergebnis einen gültigen Wert oder einen Fehlerfall repräsentiert.

Der folgende Code soll nun beispielhaft veranschaulichen, wie eine solche Klasse als Rückgabewert eines AsyncTasks genutzt werden kann:
 public class Task extends AsyncTask<Float, Integer, TaskResult<Float>> {

     private Activity activity;

     public Task(Activity activity) {
         this.activity = activity;
     }

     @Override
     protected final TaskResult<Float> doInBackground(final Float... params) {
         try {
             float a = params[0];
             float b = params[1];
             float result = a / b;
             return new TaskResult<Float>(result);
         } catch (ArithmeticException e) {
             return new TaskResult<Float>(e);
         }
     }

     @Override
     protected final void onPostExecute(final TaskResult<Float> result) {
         TextView textView = activity.findViewById(R.id.textView);

         if (result.isValid()) {
             textView.setText(Float.toString(result.getResult()));
         } else {
             textView.setText("Division by zero!");
         }
     }

 } 
Der AsyncTask wurde hierbei so typisiert, dass er Parameter vom Typ Float entgegennimmt, der aktuelle Fortschritt des Tasks von einem Integer-Wert repräsentiert wird, was in diesem Beispiel jedoch nicht genutzt wird, und das Ergebnis ein Objekt der zuvor vorgestellten TaskResult-Klasse ist, das ebenfalls mit dem Datentyp Float typisiert wird.

In der doInBackground-Methode wird als Beispiel eine Division von zwei Float-Werten durchgeführt, die als Parameter erwartet werden. Die try-catch-Schleife fängt dabei mögliche Divisionen durch null ab. Erfolgt die Division erfolgreich, wird ein TaskResult-Objekt zurückgegeben, dass mit dem entsprechenden Ergebniswert initialisiert wird, wird dagegen eine Exception geworfen, wird ein TaskResult-Objekt zurückgegeben, das die entsprechende ArithmeticException enthält.

Nach Abarbeitung der doInBackground-Methode wird impizit die onPostExecute-Methode aufgerufen, die dazu dient, das Ergebnis auf der Benutzeroberfläche auszugeben. Hierzu wird in diesem Beispiel eine TextView referenziert um das Ergebnis der oben beschriebenen Berechnung als Text auszugeben. Um die, in einem XML Layout File definierte View zu referenzieren, ist eine Referenz auf die zugehörige Activity nötig, die dem AsyncTask über den Konstruktor übergeben wurde. Falls das TaskResult-Objekt ein gültiges Ergebnis beinhaltet, wird der entsprechende Wert in der TextView ausgegeben, anderenfalls wird ein Text ausgegeben, der auf eine Division durch null hinweist.

Keine Kommentare:

Kommentar veröffentlichen