Prozesse und Threads – Übersicht

Wenn eine Anwendungskomponente gestartet wird und keine anderen Komponenten in der Anwendung ausgeführt werden, startet das Android-System einen neuen Linux-Prozess für die Anwendung mit einem einzelnen Ausführungsthread. Standardmäßig werden alle Komponenten derselben Anwendung im selben Prozess und Thread ausgeführt. Dieser wird auch als main-Thread bezeichnet.

Wenn eine Anwendungskomponente gestartet wird und bereits ein Prozess für diese Anwendung vorhanden ist, weil eine andere Komponente der Anwendung bereits gestartet wurde, beginnt die Komponente innerhalb dieses Prozesses und verwendet denselben Ausführungsthread. Sie können jedoch dafür sorgen, dass verschiedene Komponenten Ihrer Anwendung in separaten Prozessen ausgeführt werden, und für jeden Prozess zusätzliche Threads erstellen.

In diesem Dokument wird die Funktionsweise von Prozessen und Threads in einer Android-Anwendung erläutert.

Prozesse

Standardmäßig werden alle Komponenten einer Anwendung im selben Prozess ausgeführt und die meisten Anwendungen ändern dies nicht. Wenn Sie jedoch feststellen müssen, zu welchem Prozess eine bestimmte Komponente gehört, können Sie dies in der Manifestdatei festlegen.

Der Manifesteintrag für jeden Typ von Komponentenelement – <activity>, <service>, <receiver> und <provider> – unterstützt ein android:process-Attribut, das einen Prozess angeben kann, in dem die Komponente ausgeführt wird. Sie können dieses Attribut so festlegen, dass jede Komponente in ihrem eigenen Prozess ausgeführt wird oder dass einige Komponenten einen Prozess gemeinsam nutzen und andere nicht.

Sie können android:process auch so festlegen, dass Komponenten verschiedener Anwendungen im selben Prozess ausgeführt werden, sofern die Anwendungen dieselbe Linux-Nutzer-ID haben und mit denselben Zertifikaten signiert sind.

Das Element <application> unterstützt auch das Attribut android:process, mit dem Sie einen Standardwert festlegen können, der für alle Komponenten gilt.

Android kann einen Prozess irgendwann beenden, wenn Ressourcen für andere Prozesse benötigt werden, die dem Nutzer unmittelbarer zur Verfügung stehen. Anwendungskomponenten, die während des heruntergefahrenen Prozesses ausgeführt werden, werden daraufhin gelöscht. Für diese Komponenten wird ein Prozess neu gestartet, sobald wieder etwas zu tun ist.

Bei der Entscheidung, welche Prozesse herunterzufahren sind, gewichtet das Android-System deren relative Bedeutung für den Nutzer. Beispielsweise wird ein Prozess mit Aktivitäten, die nicht mehr auf dem Bildschirm sichtbar sind, einfacher beendet als ein Prozess, der sichtbare Aktivitäten hostet. Die Entscheidung, ob ein Prozess beendet wird, hängt daher vom Status der Komponenten ab, die in diesem Prozess ausgeführt werden.

Details zum Prozesslebenszyklus und seiner Beziehung zu Anwendungsstatus werden unter Prozesse und Anwendungslebenszyklus erläutert.

Unterhaltungen

Wenn eine Anwendung gestartet wird, erstellt das System einen Ausführungsthread für die Anwendung, den sogenannten Hauptthread. Dieser Thread ist sehr wichtig, da er für das Senden von Ereignissen an die entsprechenden Benutzeroberflächen-Widgets zuständig ist, einschließlich Zeichenereignissen. Er ist außerdem fast immer der Thread, in dem Ihre Anwendung mit Komponenten aus den android.widget- und android.view-Paketen des Android-UI-Toolkits interagiert. Aus diesem Grund wird der Hauptthread manchmal als UI-Thread bezeichnet. Unter bestimmten Umständen ist der Hauptthread einer Anwendung jedoch möglicherweise nicht ihr UI-Thread. Weitere Informationen finden Sie unter Thread-Anmerkungen.

Das System erstellt nicht für jede Instanz einer Komponente einen separaten Thread. Alle Komponenten, die im selben Prozess ausgeführt werden, werden im UI-Thread instanziiert. Von diesem Thread aus werden Systemaufrufe an die einzelnen Komponenten gesendet. Daher werden Methoden, die auf System-Callbacks reagieren – z. B. onKeyDown() zum Melden von Nutzeraktionen oder eine Lebenszyklus-Callback-Methode – immer im UI-Thread des Prozesses ausgeführt.

Wenn der Nutzer beispielsweise auf eine Schaltfläche auf dem Bildschirm tippt, sendet der UI-Thread der App das Touch-Ereignis an das Widget, das wiederum den Betätigungsstatus festlegt und eine Entwertungsanfrage an die Ereigniswarteschlange sendet. Der UI-Thread entfernt die Anfrage aus der Warteschlange und benachrichtigt das Widget, damit es neu gezeichnet wird.

Wenn Sie die Anwendung nicht ordnungsgemäß implementieren, kann dieses Single-Thread-Modell zu einer schlechten Leistung führen, wenn Ihre Anwendung als Reaktion auf Nutzerinteraktionen intensive Arbeit ausführt. Durch lange Vorgänge im UI-Thread wie Netzwerkzugriff oder Datenbankabfragen wird die gesamte UI blockiert. Wenn der Thread blockiert ist, können keine Ereignisse ausgelöst werden, auch keine Zeichenereignisse.

Aus der Perspektive der Nutzenden scheint die Anwendung aufzuhängen. Noch schlimmer: Wenn der UI-Thread länger als ein paar Sekunden blockiert ist, wird dem Nutzer das Dialogfeld App antwortet nicht (ANR) angezeigt. Der Nutzer kann dann entscheiden, die Anwendung zu beenden oder sogar zu deinstallieren.

Beachten Sie, dass das Android-UI-Toolkit nicht Thread-sicher ist. Bearbeiten Sie die UI also nicht über einen Worker-Thread. Nehmen Sie alle Änderungen an der Benutzeroberfläche über den UI-Thread vor. Beim Ein-Thread-Modell von Android gibt es zwei Regeln:

  1. Blockieren Sie den UI-Thread nicht.
  2. Greifen Sie nicht von außerhalb des UI-Threads auf das Android-UI-Toolkit zu.

Worker-Threads

Aufgrund dieses Einzelthread-Modells ist es für die Reaktionsfähigkeit der UI Ihrer Anwendung von entscheidender Bedeutung, dass Sie den UI-Thread nicht blockieren. Wenn Sie Vorgänge ausführen müssen, die nicht sofort ausgeführt werden, müssen Sie diese in separaten Hintergrund- oder Worker-Threads ausführen. Denken Sie daran, dass Sie die UI nicht über einen anderen Thread als den UI- oder Hauptthread aktualisieren können.

Damit Sie diese Regeln befolgen können, bietet Android mehrere Möglichkeiten, über andere Threads auf den UI-Thread zuzugreifen. Die folgenden Methoden können Ihnen dabei helfen:

Im folgenden Beispiel wird View.post(Runnable) verwendet:

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // A potentially time consuming task.
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // A potentially time consuming task.
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Diese Implementierung ist Thread-sicher, da der Hintergrundvorgang aus einem separaten Thread ausgeführt wird, während ImageView immer über den UI-Thread bearbeitet wird.

Mit zunehmender Komplexität des Vorgangs kann diese Art von Code jedoch kompliziert und schwer zu pflegen werden. Zur Verarbeitung komplexerer Interaktionen mit einem Worker-Thread können Sie einen Handler in Ihrem Worker-Thread verwenden, um vom UI-Thread zugestellte Nachrichten zu verarbeiten. Eine umfassende Erläuterung dazu, wie Sie Arbeiten an Hintergrundthreads planen und mit dem UI-Thread kommunizieren, finden Sie unter Übersicht über Hintergrundarbeiten.

Thread-sichere Methoden

In einigen Situationen werden die von Ihnen implementierten Methoden von mehr als einem Thread aufgerufen und müssen daher so geschrieben werden, dass sie Thread-sicher ist.

Dies gilt vor allem für Methoden, die remote aufgerufen werden können, z. B. Methoden in einem gebundenen Dienst. Wenn ein Aufruf einer in einem IBinder implementierten Methode aus demselben Prozess stammt, in dem IBinder ausgeführt wird, wird die Methode im Thread des Aufrufers ausgeführt. Wenn der Aufruf jedoch von einem anderen Prozess stammt, wird die Methode in einem Thread ausgeführt, der aus einem Pool von Threads ausgewählt wurde, die das System im selben Prozess wie IBinder unterhält. Sie wird nicht im UI-Thread des Prozesses ausgeführt.

Während die Methode onBind() eines Dienstes beispielsweise aus dem UI-Thread des Dienstprozesses aufgerufen wird, werden Methoden, die in dem von onBind() zurückgegebenen Objekt implementiert sind, z. B. eine Unterklasse zur Implementierung von RPC-Methoden (Remote Procedure Call), aus Threads im Pool aufgerufen. Da ein Dienst mehr als einen Client haben kann, können mehrere Poolthreads dieselbe IBinder-Methode gleichzeitig verwenden. Daher müssen IBinder-Methoden implementiert werden, damit sie Thread-sicher sind.

Ebenso kann ein Contentanbieter Datenanfragen erhalten, die von anderen Prozessen stammen. Die Klassen ContentResolver und ContentProvider verstecken die Details zur Verwaltung der Interprocess Communication (IPC). Die ContentProvider-Methoden, die auf diese Anfragen antworten (query(), insert(), delete(), update() und getType()), werden jedoch aus einem Pool von Threads im Prozess des Contentanbieters aufgerufen, nicht aus dem UI-Thread für den Prozess. Da diese Methoden von einer beliebigen Anzahl von Threads gleichzeitig aufgerufen werden können, müssen sie ebenfalls implementiert werden, um ihre Thread-Sicherheit zu gewährleisten.

Interprocess Communication

Android bietet einen Mechanismus für IPC mit RPCs, bei dem eine Methode von einer Aktivität oder einer anderen Anwendungskomponente aufgerufen, aber in einem anderen Prozess remote ausgeführt wird, wobei jedes Ergebnis an den Aufrufer zurückgegeben wird. Dazu müssen Sie einen Methodenaufruf und seine Daten auf eine Ebene zerlegen, die vom Betriebssystem verstanden werden kann. Diese werden dann vom lokalen Prozess und Adressbereich an den Remote-Prozess und den Adressbereich übertragen und dort dann wieder zusammengesetzt und reproduziert.

Rückgabewerte werden dann in die entgegengesetzte Richtung übertragen. Android stellt den gesamten Code für diese IPC-Transaktionen bereit, sodass Sie sich auf die Definition und Implementierung der RPC-Programmierschnittstelle konzentrieren können.

Zum Ausführen eines IPC muss Ihre Anwendung mithilfe von bindService() an einen Dienst gebunden werden. Weitere Informationen finden Sie in der Übersicht über die Dienste.