21 Multi-Threading

Multi-Threading ist ein Konzept, das in der Softwareentwicklung verwendet wird, um mehrere Aufgaben gleichzeitig auszuführen. In Android ist Multi-Threading besonders wichtig, weil es hilft, die Benutzeroberfläche (UI) reaktionsfähig zu halten.

Android verfügt über einen Hauptthread (auch als UI-Thread bezeichnet), der für die Interaktion mit der Benutzeroberfläche und die Verarbeitung von Benutzereingaben verantwortlich ist. Lange laufende Operationen, wie Netzwerkanfragen oder Datenbankoperationen, können den Hauptthread blockieren und die App unresponsive machen. Daher ist es wichtig, solche Operationen auf einem separaten Thread auszuführen.

Hier sind einige der wichtigsten Konzepte und Techniken für Multi-Threading in Android:

  1. Handler: Ein Handler ermöglicht es Ihnen, Aufgaben an den Hauptthread oder einen anderen Thread zu senden. Sie können einen Handler verwenden, um eine Aufgabe sofort auszuführen, oder um sie nach einer bestimmten Verzögerung auszuführen.

  2. AsyncTask: AsyncTask ist eine abstrakte Klasse, die Ihnen hilft, Hintergrundaufgaben auszuführen und die Ergebnisse auf dem Hauptthread zu veröffentlichen. Sie implementieren die Methode doInBackground(), um den Code auszuführen, der im Hintergrundthread laufen soll, und die Methode onPostExecute(), um den Code auszuführen, der auf dem Hauptthread laufen soll.

  3. Threads und Runnables: Sie können auch direkt Java-Threads und Runnables verwenden, um Hintergrundaufgaben auszuführen. Ein Runnable ist ein Objekt, das einen Codeblock repräsentiert, der auf einem Thread ausgeführt werden kann, und ein Thread ist ein Prozess, der Runnables ausführen kann.

  4. Coroutines (Kotlin): Wenn Sie Kotlin verwenden, bieten Coroutines eine leistungsfähige und flexible Möglichkeit, asynchrone Aufgaben zu handhaben. Mit Coroutines können Sie asynchronen Code auf eine Weise schreiben, die ähnlich aussieht wie synchroner Code, was den Code einfacher zu lesen und zu verstehen macht.

  5. WorkManager: Für aufgaben, die über den Lebenszyklus Ihrer App hinaus ausgeführt werden müssen oder bestimmten Bedingungen erfüllen müssen, bietet Android den WorkManager. Der WorkManager ermöglicht es Ihnen, Aufgaben zu definieren und wann sie ausgeführt werden sollen, und Android kümmert sich um die tatsächliche Ausführung der Aufgabe, auch wenn die App nicht im Vordergrund ist oder das Gerät neu gestartet wird.

Es ist wichtig zu beachten, dass die Arbeit mit mehreren Threads komplex sein kann und zu Problemen wie Rennbedingungen und Speicherkonsistenzfehlern führen kann. Daher ist es wichtig, die Grundlagen der Nebenläufigkeit und der Thread-Sicherheit zu verstehen und die geeigneten Synchronisationsmechanismen zu verwenden, wenn Sie auf gemeinsam genutzte Ressourcen zugreifen.

In Android ist Multithreading besonders wichtig, da lang andauernde Operationen, die auf dem Hauptthread (auch UI-Thread genannt) ausgeführt werden, die Benutzeroberfläche blockieren und die App unansprechbar machen können.

Eine gängige Methode zur Implementierung von Multithreading in Android ist die Verwendung der Klasse AsyncTask. Diese Klasse ermöglicht es Ihnen, Operationen im Hintergrund auszuführen und die Ergebnisse auf dem UI-Thread zu veröffentlichen. Hier ist ein einfaches Beispiel:

private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
    
    @Override
    protected Void doInBackground(Void... params) {
        // Hier wird der Code ausgeführt, der im Hintergrund 
        // laufen soll. Zum Beispiel: Daten von einem Server 
        // abrufen, eine Datenbankabfrage durchführen, usw.
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        // Dieser Code wird auf dem UI-Thread ausgeführt, 
        // nachdem doInBackground abgeschlossen ist.
        // Hier können Sie beispielsweise ein UI-Element 
        // aktualisieren, um die Ergebnisse der 
        // Hintergrundoperation anzuzeigen.
    }
}

Und so starten Sie die Ausführung der AsyncTask:

new MyAsyncTask().execute();

Es ist wichtig zu beachten, dass AsyncTask einige Nachteile hat und seit API 30 veraltet ist. Alternativen sind unter anderem die Klassen Handler und Looper, ThreadPoolExecutor oder die Verwendung von Bibliotheken wie RxJava oder Coroutines in Kotlin. Hier ist ein Beispiel für die Verwendung von Coroutines in Kotlin:

import kotlinx.coroutines.*

fun main() = runBlocking { 
    launch { 
        delay(1000L) 
        println("Task from runBlocking")
    }
    
    coroutineScope { 
        launch {
            delay(500L)
            println("Task from nested launch")
        }
    
        delay(200L) 
        println("Task from coroutine scope") 
    }

    println("Coroutine scope is over") 
}

In diesem Beispiel erstellt runBlocking eine neue Coroutine, die das Hauptprogramm blockiert, bis alle Coroutinen in ihrem Scope beendet sind. Der launch-Aufruf innerhalb von runBlocking und coroutineScope startet jeweils eine neue Coroutine in diesem Scope. Der delay-Aufruf ist eine spezielle suspendierende Funktion, die eine Coroutine für eine gegebene Zeit verzögert, ohne den gesamten Thread zu blockieren.