Sobes.tech
Junior

Wie führt man Code in einem parallelen (nicht Haupt-)Thread aus?

sobes.tech KI

Antwort von AI

Für die parallele Ausführung von Code auf Android werden verschiedene Mechanismen verwendet, die es ermöglichen, den Haupt- (UI-)Thread nicht zu blockieren und die Reaktionsfähigkeit der Anwendung zu erhalten.

Integrierte Mittel von Android:

  1. Thread: Basisklasse zum Erstellen eines neuen Threads.

    new Thread(new Runnable() {
        @Override
        public void run() {
            // Code zur Ausführung im parallelen Thread
        }
    }).start();
    

    Nachteil: Manuelles Thread-Management kann zu Problemen mit dem Lebenszyklus und Speicherlecks führen.

  2. AsyncTask (Veraltet): Erleichterte Arbeit mit parallelen Threads und Interaktion mit dem UI-Thread.

    private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        @Override
        protected Void doInBackground(Void... params) {
            // Code zur Ausführung im Hintergrund
            return null;
        }
    
        @Override
        protected void onPostExecute(Void result) {
            // Arbeit mit dem UI-Thread nach doInBackground
        }
    }
    // Verwendung: new MyAsyncTask().execute();
    

    Nachteile: Veraltet, hat Einschränkungen und Schwierigkeiten bei der Lebenszyklusverwaltung.

  3. Handler und Looper: Ermöglichen das Erstellen von Threads mit Nachrichtenverarbeitungsschleifen.

    HandlerThread handlerThread = new HandlerThread("MyWorkerThread");
    handlerThread.start();
    Handler handler = new Handler(handlerThread.getLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
            // Code zur Ausführung im Thread mit Looper
        }
    });
    

    Vorteil: Nützlich für wiederkehrende Aufgaben oder die Verarbeitung asynchroner Ereignisse in einem separaten Thread.

  4. ExecutorService / ThreadPoolExecutor: Bieten Thread-Pools, die effizienter sind als das Erstellen eines neuen Thread für jede Aufgabe.

    ExecutorService executor = Executors.newSingleThreadExecutor(); // Oder newCachedThreadPool(), newFixedThreadPool(N)
    executor.execute(new Runnable() {
        @Override
        public void run() {
            // Code zur Ausführung im Pool-Thread
        }
    });
    // Nicht vergessen, executor.shutdown() bei Beendigung aufzurufen
    

    Vorteile: Verwalten die Erstellung und Wiederverwendung von Threads, reduzieren Overhead.

Moderne Ansätze:

  1. Kotlin Coroutines: Leichtgewichtige Threads, die eine einfachere und lesbarere Möglichkeit der asynchronen Programmierung bieten.

    import kotlinx.coroutines.*
    
    // Starten einer Coroutine in einem Thread-Pool (ähnlich einem Hintergrund-Thread)
    GlobalScope.launch(Dispatchers.Default) {
        // Code zur Ausführung im Hintergrund
    }
    
    // Starten einer Coroutine für IO-Operationen
    GlobalScope.launch(Dispatchers.IO) {
        // Code für IO-Operationen (Netzwerk, Dateien etc.)
    }
    
    // Wechsel zum UI-Thread zur Aktualisierung der Benutzeroberfläche
    GlobalScope.launch(Dispatchers.Default) {
        // Hintergrundberechnungen
        withContext(Dispatchers.Main) {
            // UI-Update
        }
    }
    

    Vorteile: Verbesserte Lesbarkeit, effizientere Ressourcennutzung, einfache Verwaltung der Abbruchmöglichkeiten, strukturierte Nebenläufigkeit.

  2. RxJava / RxKotlin: Reaktive Programmierung basierend auf beobachtbaren Datenströmen.

    import io.reactivex.rxjava3.core.Observable;
    import io.reactivex.rxjava3.schedulers.Schedulers;
    
    Observable.just("Ein Ergebnis")
        .subscribeOn(Schedulers.io()) // Ausführen im IO-Thread
        .observeOn(AndroidSchedulers.mainThread()) // Beobachten im UI-Thread
        .subscribe(/* Ergebnisverarbeitung */);
    

    Vorteile: Leistungsstarkes Werkzeug für asynchrone Ereignisse und Datenumwandlung, erfordert jedoch eine steilere Lernkurve.

WorkManager-Bibliothek:

  1. WorkManager: Empfohlene Komponente der Architecture Components für die garantierte Ausführung von Hintergrundaufgaben, auch wenn die Anwendung geschlossen oder das Gerät neu gestartet wird.
    import androidx.work.*
    import java.util.concurrent.TimeUnit
    
    class MyWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext, workerParams) {
        override fun doWork(): Result {
            // Code zur Ausführung der Aufgabe im Hintergrund
            return Result.success() // Oder Result.failure(), Result.retry()
        }
    }
    
    // Erstellen einer Anfrage zur Ausführung der Aufgabe
    val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
        .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) // Optionale Einschränkungen
        .build()
    
    // Aufgabe in die Warteschlange stellen
    WorkManager.getInstance(context).enqueue(workRequest)
    
    Vorteile: Garantierte Ausführung, Unterstützung von Einschränkungen (Netzwerktyp, Akku), Verwaltung von Abbrüchen, verzögerte Ausführung und Wiederholungen.

Die Wahl der konkreten Methode hängt vom Aufgabentyp, Lebenszyklus und Zuverlässigkeitsanforderungen ab. Für die meisten modernen Android-Anwendungen wird empfohlen, Kotlin Coroutines oder WorkManager zu verwenden.