Android verbietet es, Methoden, die länger dauern könnten, im GUI-Thread auszuführen. Sei müssen in einem separatem Thread ausgeführt werden. Insbesondere sind fast sämtliche Funktionen betroffen. Android bietet mit der AsyncTask-Klasse eine einfache Möglichkeit, einen Thread mit Parametern aufzurufen und Ergebnisse abzurufen. Leider basiert die Klasse auf einer Reihe weiterer Klassen, die z.T. Teil der Implementierung der Hersteller sind.

Bei der Entwicklung von Netzwerk-Komponenten für den App Inventor ist es jedoch wegen der deutlich besseren Debugging-Optionen von großem Vorteil, wenn man den Kern der Komponenten an einem "normalen" PC austesten kann. Die folgenden drei Klassen können die AsyncTask-Klasse weitgehend ersetzen.


Version Anpassungen
1.0 (2020-07-31) Initiale Version

In­halts­ver­zeich­nis

Download

Paket UrsAsync

Klasse UrsAsyncFunction

Referenz

Implementierung

Klasse UrsAsyncFunctionEx

Referenz

Implementierung

Klasse UrsAsyncTask

Referenz

Download

Das ZIP-Archiv UrsAsync zum Download.

Paket UrsAsync

Das Paket enthält drei abstrakte Klassen. Implementiert sind die Methoden zum Start bzw. Aufruf, zum Stopp, zur Parameter- und Ergebnisübergabe und der Fehlerbehandlung. Vom Anwender ist nur der ausführbare Code (Methode doWork() bzw. loop()) zu implementieren.

Zur Verwendung wird die Klasse abgeleitet und die Methode doWork() überschrieben.

UrsAsyncFunction

Das folgende Beispiel zeigt die Anwendung der Klasse.

// UrsAsyncFunction überschreiben und Rückgabetyp festlegen
class UltimateAnswerClass extends UrsAsyncFunction<Integer> {
    // Arbeitsmethode überschreiben
    @Override
    protected Integer doWork() {
        return 42;
    }
}

// Instanz der Klasse anlegen
UltimateAnswerClass UltimateAnswerGenerator = new UltimateAnswerClass();

public int getUltimateAnswer() {
    // Arbeitsmethode in separatem Thread ausführen
    return UltimateAnswerGenerator.execute();
}

Einfacher geht es meist bei der Verwendung einer Lambda-Funktion.

// Instanz der Klasse mit Rückgabetyp und Lambda-Funktion
UrsAsyncFunction<Integer> UltimateAnswer = new UrsAsyncFunction<Integer>() {
    // Arbeitsmethode per Lambda-Funktion definieren
    @Override
    protected Integer doWork() {
        return 42;
    }
};

public int getUltimateAnswer() {
    // Arbeitsmethode in separatem Thread ausführen
    return UltimateAnswer.execute();
}

Referenz

Element Funktion Anmerkung
public abstract class UrsAsyncFunction<R>
Deklaration der abstrakten Klasse.
V ist der Rückgabetyp.
Primitive Typen wie int, double, etc. sind nicht erlaubt. Stattdessen müssen die zugehörigen Wrapper Klassen Integer, Double, etc. verwand werden. Zu beachten ist, dass dabei nicht alle sonst üblichen impliziten Typ-Konvertierungen funktionieren. 42 z.B. wird nicht automatisch zu Double konvertiert, es muss 42.0 angegeben werden oder entsprechende Konvertierungsoperatoren angegeben werden ((double) in diesem Fall).

Wenn kein Rückgabewert vorgesehen ist, kann Void verwendet werden. Die Methode doWork muss dann mit return null; verlassen werden.
protected abstract R doWork()
Die Arbeitsmethode. Diese Methode muss in der Anwendung mit den entsprechenden Code überschrieben werden. execute wartet auf die Beendigung von doWork. Wenn die Ausführungszeit des Codes zu lange dauert, hat dies Auswirkung auf die Reaktionszeiten des GUI.
public R execute(R defaultValue)
throws RuntimeException
Bewirkt die Ausführung der Methode doWork in einem separatem Thread. Der in doWork ermittelte Wert wird zurück geliefert.
Der Rückgabewert ist der defaultvalue, wenn doWork auf Grund eines Fehlers abgebrochen wurde.
Wenn doWork wegen des Auftretens einer Exception abgebrochen wurde, wirft execute eine Exception vom Typ RunTimeException. Diese Exception muss nicht in einem try-catch-Block abgefangen werden, kann aber. Über getCause() kann die Exception abgerufen werden, die den Abbruch verursacht hat.

Die Methode wartet auf die Beendigung von doWork. Wenn die Ausführungszeit des Codes zu lange dauert, hat dies Auswirkung auf die Reaktionszeiten des GUI.
public R execute()
throws RuntimeException
Wie oben, Der Rückgabewert im Falle eines Fehler ist null.  
public boolean wasAborted()
true: doWork wurde auf Grund einer Exception abgebrochen. Die Exception kann mit getException abgefragt werden.
public Exception getExeption()
Die Exception, die den Abbruch von doWork verursacht hat. null, wenn doWork fehlerfrei ausgeführt wurde.

Implementierung

Kern der Implementierung ist der Thread, der doWork aufruft und die Methode execute, die den Thread startet und auf das Ergebnis wartet.

/**
 * Thread in dem \ref doWorkausgeführt wird.
 */
private class WorkerThread extends Thread {
    R returnValue; // wird in dodoWorkvorbelegt

    @Override
    public void run() {
        try {
            returnValue = doWork();
        } catch (Exception e) {
            threadWasAborted = true;
            executeException = e; // evtl. Exception aufbewahren.
        } finally {
            threadIsRunning = false;
            result = returnValue;
        }
    }
}

Der Thread packt den Aufruf von doWork in einen try-catch-finally-Block um eine evtl. Exception aufzufangen und zwischen zu speichern. threadIsRunning wird auf false gesetzt, wenn der Thread beendet wurde.

/**
 * Führt die Methode \ref doWorkin separatem Thread aus.
 *
 * @param defaultValue Rückgabewert bei Abbruch.
 * @return Rückgabewert von \ref doWorkbzw. defaultValue bei Abbruch.
 * @throws RuntimeException Wenn \ref doWorkauf Grund eines Fehlers
 *                          abgebrochen wurde.
 */
public R execute(R defaultValue) throws RuntimeException {
    threadWasAborted = false;
    executeException = null;
    workerThread = new WorkerThread();
    workerThread.returnValue = defaultValue;
    threadIsRunning = true;
    workerThread.start();

    while (threadIsRunning)
        Thread.yield();

    if (threadWasAborted)
        throw new RuntimeException("Fehler beim Ausführen der Funktion. Siehe getCause()", executeException);
    return result;
}

execute erstellt eine neue Instanz des Threads und startet ihn. Über threadIsRunning wird abgefragt, wann der Thread beendet wurde. Im Falles eines Abbruchs von doWorkwird eine RunTimeException geworfen.

UrsAsyncFunctionEx

Diese Methode entspricht im Wesentlichen UrsAsyncFunction. Der Unterschied ist, dass ein Funktionsparameter mit übergeben werden kann. Zum Beispiel:

// Instanz der Klasse mit Funktionsparameter, Rückgabetyp und Lambda-Funktion
UrsAsyncFunctionEx<Double, Double> SinFromDegrees = new UrsAsyncFunctionEx<Double, Double>() {
    // Arbeitsmethode per Lambda-Funktion definieren
    @Override
    protected Double doWork(double degrees) {
        return Math.sin(degrees / 180 * Math.PI);
     }
};

public int getSinFromDegrees() {
    // Arbeitsmethode in separatem Thread ausführen
    return SinFromDegrees.execute(45.0);
}

Sind mehrere Parameterangaben notwendig, können diese in eine entsprechende Klasse, Array oder Collection  verpackt werden.

Referenz

Element Funktion Anmerkung
public abstract class UrsAsyncFunction<P, R>
Deklaration der abstrakten Klasse.
P ist der Parametertyp.
V ist der Rückgabetyp.
Primitive Typen wie int, double, etc. sind nicht erlaubt. Stattdessen müssen die zugehörigen Wrapper Klassen Integer, Double, etc. verwand werden. Zu beachten ist, dass dabei nicht alle sonst üblichen impliziten Typ-Konvertierungen funktionieren. 42 z.B. wird nicht automatisch zu Double konvertiert, es muss 42.0 angegeben werden oder entsprechende Konvertierungsoperatoren angegeben werden ((double) in diesem Fall).

Wenn kein Rückgabewert vorgesehen ist, kann Void verwendet werden. Die Methode doWork muss dann mit return null; verlassen werden.
protected abstract R doWork(P parameter)
Die Arbeitsmethode. Diese Methode muss in der Anwendung mit den entsprechenden Code überschrieben werden. execute wartet auf die Beendigung von doWork. Wenn die Ausführungszeit des Codes zu lange dauert, hat dies Auswirkung auf die Reaktionszeiten des GUI.
public R execute(P parameter, R defaultValue)
throws RuntimeException
Bewirkt die Ausführung der Methode doWork in einem separatem Thread. Der in doWork ermittelte Wert wird zurück geliefert.
Der Rückgabewert ist der defaultvalue, wenn doWork auf Grund eines Fehlers abgebrochen wurde.
Wenn doWork wegen des Auftretens einer Exception abgebrochen wurde, wirft execute eine Exception vom Typ RunTimeException. Diese Exception muss nicht in einem try-catch-Block abgefangen werden, kann aber. Über getCause() kann die Exception abgerufen werden, die den Abbruch verursacht hat.

Die Methode wartet auf die Beendigung von doWork. Wenn die Ausführungszeit des Codes zu lange dauert, hat dies Auswirkung auf die Reaktionszeiten des GUI.
public R execute(P parameter)
throws RuntimeException
Wie oben, Der Rückgabewert im Falle eines Fehler ist null.  
public boolean wasAborted()
true: doWork wurde auf Grund einer Exception abgebrochen. Die Exception kann mit getException abgefragt werden.
public Exception getExeption()
Die Exception, die den Abbruch von doWork verursacht hat. null, wenn doWork fehlerfrei ausgeführt wurde.

Implementierung

Die entspricht im Wesentlichen der von UrsAsyncFunction.

UrsAsyncTask

Das folgende Beispiel demonstriert den Einsatz der Klasse.

01: package de.ullisroboterseite.ursasync;
02: 
03: //
04: // URS Async Task Test
05: //
06: public class AsyncTaskTest {
07:     static void listenToFireEvent(int counter) {
08:         System.out.println("Counter is " + counter);
09:     }
10: 
11:     public static void main(String[] args) {
12:         System.out.println("URS Async Task Test");
13: 
14:         // Instanz der Klasse mit Funktionsparameter, Rückgabetyp und Lambda-Funktion
15:         UrsAsyncTask FireEvents = new UrsAsyncTask() {
16:             int counter = 0;
17: 
18:             @Override
19:             protected void loop() {
20:                 listenToFireEvent(counter++);
21:                 sleep(1000);
22:             }
23:         };
24: 
25:         FireEvents.start();
26: 
27:         System.out.println("Press enter to stop");
28:         try {
29:             System.in.read();
30:         } catch (Exception e) {
31:             // nothing to do
32:         }
33: 
34:         FireEvents.stop();
35:     }
36: }

FireEvents ist eine Inline-Ableitung der Klasse UrsAsyncTask (Zeile 15). Die Arbeitsmethode loop macht nicht mehr als etwa einmal pro Sekunde eine Zähler zu erhöhen und die Methode listenToFireEvent aufzurufen (Zeile 20). listenToFireEvent (Zeile 7) ist im umgebenden Programm definiert und gibt einfach nur den Zähler auf der Konsole aus. FireEvents.start (Zeile 25) startet den Thread und FireEvents.stop (Zeile 34) stoppt den Thread nach Eingabe von Enter auf der Konsole.

Für solche, in einer Schleife auszuführende Funktionen ist es am einfachsten die Methode loop zu überschreiben. Will man mehr Kontrolle überschreibt man doWork. In der Basisklasse ruft doWork die Methode loop in einer Schleife auf, bis der Thread gestoppt werden soll. Das Stopp-Signal kann über die Methode keepRunning abgefragt werden.

public abstract class UrsAsyncTask {
...
   //
   // Führt loop in Schleife aus.
   //
   protected void doWork() {
      while (keepRunning())
          loop();
   }
...

Will man den Thread mit Parametern versorgen, kann man die Methode start passend überschreiben. Zum Start des Threads muss dann zum Schluss doStart aufgerufen werden. Eine andere Möglichkeit wäre es, Felder in der abgeleiteten Klasse zu definieren und diese vor dem Aufruf von Start zu belegen. Zuletzt bleibt die Option eine Kopie der Klasse anzulegen oder einen Wrapper zu schreiben. Dort kann man dann die entsprechenden Methoden implementieren.

Ansonsten entspricht die Implementierung im Wesentlichen dem bereits Beschriebenen.

Referenz

Element Funktion Anmerkung
public abstract class UrsAsyncTask
Deklaration der abstrakten Klasse.  
protected void loop()
Die Arbeitsmethode. loop wird solange in einer Schleife aufgerufen, bis der Thread gestoppt wird. In der Basisklasse besitzt diese Methode keine Funktion.
protected void doWork()
Die alternative Arbeitsmethode. Erlaubt eine größere Kontrolle über den Thread. In der Basisklasse wird die Methode loop in einer Schleife aufgerufen, bis der Thread gestoppt wird.
public boolean start()
Legt einen neuen Thread an und startet ihn.
Der Rückgabewert ist false, wenn bereits ein Thread aktiv ist.
public void stop()
Stoppt den Thread und wartet drauf, dass der Thread beendet wurde. Der Mehrfachaufruf der Methode ist unkritisch.
public void stopAsync()
Stoppt den Thread. Die Methode kehrt sofort zurück. Es wird nicht drauf gewartet, dass der Thread beendet wurde. Das Thread-Ende kann über isRunning abgefragt werden.

Der Mehrfachaufruf der Methode ist unkritisch.
public boolean wasAborted()
true: doWork (bzw. loop) wurde auf Grund einer Exception abgebrochen. Die Exception kann mit getException abgefragt werden.
public Exception getExeption()
Die Exception, die den Abbruch von doWork (loop) verursacht hat. null, wenn doWork fehlerfrei ausgeführt wurde.
public boolean isRunning()
Gibt an, ob der Thread noch aktiv ist.  
protected boolean keepRunning()
Gibt an, ob der Thread weiter laufen soll. Nach dem Aufruf von stop oder stopAsync liefert die Methode den Wert false. In doWork kann über keepRunning abgeprüft werden, ob der Thread beendet werden soll.
protected void sleep(long ms)
Pausiert den Thread für die angegebene Anzahl an Millisekunden. Die Methode ist ein Wrapper zum vereinfachten Aufruf von Thread.sleep, der die InterruptedException abfängt.