Wenn eine Anwendungskomponente gestartet wird und in der Anwendung keine anderen Komponenten 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, dem Hauptthread.
Wenn eine Anwendungskomponente gestartet wird und bereits ein Prozess für diese Anwendung vorhanden ist, weil eine andere Komponente der Anwendung bereits gestartet wurde, wird die Komponente in diesem Prozess gestartet und verwendet denselben Ausführungsthread. Sie können jedoch festlegen, dass verschiedene Komponenten in Ihrer Anwendung in separaten Prozessen ausgeführt werden, und Sie können für jeden Prozess zusätzliche Threads erstellen.
In diesem Dokument wird beschrieben, wie Prozesse und Threads in einer Android-App funktionieren.
Prozesse
Standardmäßig werden alle Komponenten einer Anwendung im selben Prozess ausgeführt. Die meisten Anwendungen ändern diese Einstellung nicht. Wenn Sie jedoch festlegen möchten, zu welchem Prozess eine bestimmte Komponente gehört, können Sie dies in der Manifestdatei tun.
Der Manifesteintrag für jeden Komponententyp – <activity>, <service>, <receiver> und <provider> – unterstützt ein android:process-Attribut, mit dem ein
Prozess angegeben werden kann, in dem die Komponente ausgeführt wird. Sie können dieses Attribut so festlegen, dass jede Komponente in einem eigenen Prozess ausgeführt wird oder dass einige Komponenten einen Prozess gemeinsam nutzen, andere jedoch 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 <application>
Element unterstützt auch ein android:process Attribut, mit dem Sie einen
Standardwert festlegen können, der für alle Komponenten gilt.
Android kann sich entscheiden, einen Prozess zu beenden, wenn Ressourcen von anderen Prozessen benötigt werden, die den Nutzer direkter bedienen. Anwendungskomponenten, die in dem beendeten Prozess ausgeführt werden, werden daher zerstört. Für diese Komponenten wird wieder ein Prozess gestartet, wenn Aufgaben für sie anfallen.
Bei der Entscheidung, welche Prozesse beendet werden sollen, berücksichtigt das Android-System ihre relative Bedeutung für den Nutzer. So wird beispielsweise ein Prozess, in dem Aktivitäten gehostet werden, die nicht mehr auf dem Bildschirm sichtbar sind, eher beendet als ein Prozess, in dem sichtbare Aktivitäten gehostet werden. Die Entscheidung, ob ein Prozess beendet werden soll, hängt daher vom Status der Komponenten ab, die in diesem Prozess ausgeführt werden.
Details zum Prozesslebenszyklus und seiner Beziehung zu Anwendungsstatus finden Sie unter Prozesse und App-Lebenszyklus.
Threads
Wenn eine Anwendung gestartet wird, erstellt das System einen Ausführungsthread für die Anwendung, den Hauptthread. Dieser Thread ist sehr wichtig, da er für das Senden von Ereignissen an die entsprechenden Widgets der Benutzeroberfläche zuständig ist, einschließlich Zeichenereignissen. Außerdem ist er fast immer der Thread, in dem Ihre Anwendung mit Komponenten aus den Paketen android.widget und android.view des Android-UI-Toolkits interagiert.
Aus diesem Grund wird der Hauptthread manchmal auch UI-Thread genannt. Unter besonderen Umständen ist der Hauptthread einer App jedoch möglicherweise nicht ihr UI-Thread. Weitere Informationen finden Sie unter Thread
Annotationen.
Das System erstellt keinen separaten Thread für jede Instanz einer Komponente. Alle Komponenten, die im selben Prozess ausgeführt werden, werden im UI-Thread instanziiert und Systemaufrufe an jede Komponente werden von diesem Thread gesendet. Daher werden Methoden, die auf System-Callbacks reagieren, z. B. onKeyDown(), um Nutzeraktionen zu melden, oder eine Lebenszyklus-Callback-Methode, immer im UI-Thread des Prozesses ausgeführt.
Wenn der Nutzer beispielsweise auf einen Button auf dem Bildschirm tippt, sendet der UI-Thread Ihrer App das Touch-Ereignis an das Widget, das wiederum seinen gedrückten Status festlegt und eine Anfrage zum Ungültigmachen an die Ereigniswarteschlange sendet. Der UI-Thread entfernt die Anfrage aus der Warteschlange und benachrichtigt das Widget, sich neu zu zeichnen.
Wenn Sie Ihre Anwendung nicht ordnungsgemäß implementieren, kann dieses Single-Thread-Modell zu einer schlechten Leistung führen, wenn Ihre App als Reaktion auf die Nutzerinteraktion intensive Aufgaben ausführt. Lange Vorgänge im UI-Thread, z. B. Netzwerkzugriff oder Datenbankabfragen, blockieren die gesamte Benutzeroberfläche. Wenn der Thread blockiert ist, können keine Ereignisse gesendet werden, einschließlich Zeichenereignissen.
Aus Nutzersicht reagiert die Anwendung nicht mehr. Noch schlimmer: Wenn der UI-Thread länger als einige Sekunden blockiert ist, wird dem Nutzer das Dialogfeld „Anwendung reagiert nicht“ (ANR) angezeigt. Der Nutzer kann dann entscheiden, Ihre Anwendung zu beenden oder sogar zu deinstallieren.
Beachten Sie, dass das Android-UI-Toolkit nicht threadsicher ist. Bearbeiten Sie die Benutzeroberfläche daher nicht über einen Arbeitsthread. Alle Änderungen an der Benutzeroberfläche müssen über den UI-Thread erfolgen. Für das Single-Thread-Modell von Android gelten zwei Regeln:
- Blockieren Sie den UI-Thread nicht.
- Greifen Sie nicht von außerhalb des UI-Threads auf das Android-UI-Toolkit zu.
Arbeitsthreads
Aufgrund dieses Single-Thread-Modells ist es für die Reaktionsfähigkeit der Benutzeroberfläche Ihrer Anwendung entscheidend, dass Sie den UI-Thread nicht blockieren. Wenn Sie Vorgänge ausführen müssen, die nicht sofort abgeschlossen werden können, sollten Sie sie in separaten Hintergrund - oder Arbeitsthreads ausführen. Beachten Sie, dass Sie die Benutzeroberfläche nur über den UI-Thread oder den Hauptthread aktualisieren können.
Damit Sie diese Regeln einhalten können, bietet Android mehrere Möglichkeiten, von anderen Threads aus auf den UI-Thread zuzugreifen. Hier eine Liste der Methoden, die Ihnen dabei helfen können:
In den folgenden Beispielen wird gezeigt, wie Sie eine Aufgabe an einen Hintergrundthread auslagern und den UI-Thread aktualisieren, sobald die Aufgabe abgeschlossen ist:
Kotlin
// Kotlin coroutines implementation. fun onClick(v: View) { // Launch a coroutine in the lifecycle scope (e.g., in an Activity or Fragment). lifecycleScope.launch { // Run the blocking task on the IO dispatcher. val bitmap = withContext(Dispatchers.IO) { BitmapFactory.decodeFile("image.png") } // Back on the main thread, update the UI. imageView.setImageBitmap(bitmap) } }
Java
// Java Executor implementation. // (executorService is assumed to be defined elsewhere). public void onClick(View v) { executorService.execute(() -> { // Run the heavy task on a background thread. Bitmap bitmap = BitmapFactory.decodeFile("image.png"); // Update the View on the UI thread. imageView.post(() -> imageView.setImageBitmap(bitmap)); }); }
Diese Implementierung ist threadsicher, da der Hintergrundvorgang über einen separaten Thread ausgeführt wird, während die ImageView immer über den UI-Thread bearbeitet wird.
Mit zunehmender Komplexität des Vorgangs kann diese Art von Code jedoch kompliziert und schwer zu warten werden. Für komplexere Interaktionen mit einem Arbeitsthread können Sie in Ihrem Arbeitsthread einen Handler verwenden, um Nachrichten zu verarbeiten, die vom UI-Thread gesendet werden. Eine ausführliche Erklärung zum Planen von Aufgaben in Hintergrundthreads und zur Kommunikation mit dem UI-Thread finden Sie unter
Hintergrundaufgaben.
Threadsichere Methoden
In einigen Fällen werden die von Ihnen implementierten Methoden von mehr als einem Thread aufgerufen und müssen daher threadsicher sein.
Dies gilt hauptsächlich für Methoden, die remote aufgerufen werden können, z. B. Methoden in einem gebundenen Dienst. Wenn ein Aufruf einer
Methode, die in einem IBinder implementiert ist, aus demselben Prozess stammt, in dem die
IBinder ausgeführt wird, wird die Methode im Thread des Aufrufers ausgeführt.
Wenn der Aufruf jedoch aus einem anderen Prozess stammt, wird die Methode in einem Thread ausgeführt, der aus einem Threadpool ausgewählt wird, den das System im selben Prozess wie der IBinder verwaltet.
Sie wird nicht im UI-Thread des Prozesses ausgeführt.
Während beispielsweise die Methode
onBind() eines Dienstes über den UI-Thread des
Dienstprozesses aufgerufen wird, werden Methoden, die im Objekt implementiert sind, das von onBind() zurückgegeben wird, z. B. eine
Unterklasse, die RPC-Methoden (Remote Procedure Call) implementiert, über Threads
im Pool aufgerufen. Da ein Dienst mehrere Clients haben kann, können mehrere Poolthreads gleichzeitig dieselbe IBinder-Methode verwenden. Daher müssen IBinder-Methoden threadsicher implementiert werden.
Ähnlich kann ein Contentanbieter Datenanfragen erhalten, die aus anderen Prozessen stammen.
Die ContentResolver und ContentProvider
Klassen verbergen die Details der IPC-Verwaltung (Interprocess Communication).
Die ContentProvider-Methoden, die auf diese Anfragen reagieren – die Methoden
query(),
insert(),
delete(),
update(),
und getType() – werden
jedoch über einen Threadpool im Prozess des Contentanbieters aufgerufen, nicht über den UI
Thread des Prozesses. Da diese Methoden gleichzeitig von einer beliebigen Anzahl von Threads aufgerufen werden können, müssen auch sie threadsicher implementiert werden.
Interprozesskommunikation
Android bietet einen Mechanismus für die IPC mithilfe von RPCs, bei dem eine Methode von einer Aktivität oder einer anderen Anwendungskomponente aufgerufen, aber remote in einem anderen Prozess ausgeführt wird. Alle Ergebnisse werden an den Aufrufer zurückgegeben. Dazu muss ein Methodenaufruf und seine Daten so zerlegt werden, dass das Betriebssystem sie verstehen kann. Anschließend werden sie vom lokalen Prozess und Adressraum zum Remote-Prozess und Adressraum übertragen und dort wieder zusammengesetzt und ausgeführt.
Rückgabewerte werden dann in die entgegengesetzte Richtung übertragen. Android stellt den gesamten Code für diese IPC-Transaktionen bereit, sodass Sie sich auf das Definieren und Implementieren der RPC-Programmierschnittstelle konzentrieren können.
Für die IPC muss Ihre Anwendung mit bindService() an einen Dienst gebunden werden. Weitere Informationen finden Sie unter Übersicht über Dienste.