Durch den sinnvollen Einsatz von Threads unter Android kannst du die Leistung. Auf dieser Seite werden mehrere Aspekte der Arbeit mit Threads erläutert: mit der Benutzeroberfläche oder dem Hauptthread arbeiten; zwischen App-Lebenszyklus und Thread-Priorität; Methoden, die die Plattform bietet, um Threads zu verwalten und Komplexität. In jedem dieser Bereiche werden auf dieser Seite potenzielle Fallstricke beschrieben und Strategien, um sie zu vermeiden.
Hauptthread
Wenn der Nutzer Ihre App startet, erstellt Android ein neues Linux- zusammen mit einem Ausführungs-Thread. In diesem Hauptthread auch UI-Thread genannt, ist für alles, was passiert, zu präsentieren. Wenn Sie die Funktionsweise verstehen, können Sie Ihre App für die Nutzung der Hauptthread für die bestmögliche Leistung.
Interna
Der Hauptthread ist sehr einfach aufgebaut: Es gibt nur die Aufgabe, aus einer Thread-sicheren Arbeitswarteschlange bis zur Beendigung ihrer Anwendung zu trennen. Die Framework einige dieser Arbeitsblöcke von verschiedenen Stellen aus generiert. Diese z. B. Callbacks, die mit Lebenszyklusinformationen verknüpft sind, oder Ereignisse aus anderen Apps und Prozessen. Außerdem kann die App Blöcke explizit in die Warteschlange zu stellen, ohne das Framework zu verwenden.
Fast alle Codeblock, den Ihre App ausführt, ist mit einem Ereignis-Callback wie einer Eingabe, Layout-Inflation oder Zeichnen. Wenn etwas ein Ereignis auslöst, wird der Thread, in dem das Ereignis passiert, verschiebt das Ereignis aus sich selbst und in die Nachricht des Hauptthreads. in die Warteschlange stellen. Der Hauptthread kann dann das Ereignis verarbeiten.
Während eine Animation oder ein Bildschirmupdate ausgeführt wird, versucht das System, eine der für das Zeichnen des Bildschirms verantwortlich ist, alle 16 ms, um bei 60° flüssig zu rendern, Bilder pro Sekunde. Damit das System dieses Ziel erreichen kann, muss die Hierarchie der Benutzeroberfläche/Ansicht muss im Hauptthread aktualisiert werden. Wenn die Nachrichtenwarteschlange des Hauptthreads enthält Aufgaben, die entweder zu viele oder zu lang sind, sodass der Hauptthread nicht sollte das Update schnell genug abgeschlossen werden, sollte die App diese Arbeit auf einen Worker Diskussions-Thread. Wenn der Hauptthread die Ausführung von Arbeitsblöcken nicht innerhalb von 16 ms fertigstellen kann, kann es zu Verzögerungen, Verzögerungen oder einer mangelnden Reaktionsfähigkeit der UI bei der Eingabe kommen. Wenn der Hauptthread etwa fünf Sekunden lang blockiert wird, zeigt das System die Anwendung Dialogfeld „Keine Reaktion“ (ANR), über das der Nutzer die App direkt schließen kann.
Verschieben zahlreicher oder langer Aufgaben aus dem Hauptthread, damit sie nicht gestört werden mit flüssigem Rendering und schneller Reaktion auf Nutzereingaben. warum Sie Threading in Ihrer App verwenden sollten.
Threads und UI-Objektverweise
Android View-Objekte sind nicht threadsicher. Es wird erwartet, dass eine App UI-Objekte im Hauptthread löschen. Wenn Sie versuchen, oder auf ein UI-Objekt in einem anderen Thread als dem Hauptthread verweisen, können Ausnahmen, Fehler im Hintergrund, Abstürze und anderes undefiniertes Fehlverhalten sein.
Probleme mit Referenzen lassen sich in zwei Kategorien unterteilen: explizite Referenzen. und implizite Verweise.
Explizite Verweise
Viele Aufgaben in Nicht-Haupt-Threads haben letztendlich das Ziel, UI-Objekte zu aktualisieren. Wenn jedoch einer dieser Threads auf ein Objekt in der Ansichtshierarchie zugreift, Anwendungsinstabilität kann auftreten: Wenn ein Worker-Thread die Attribute eines und jeder andere Thread auf das Objekt verweist, sind die Ergebnisse nicht definiert.
Stellen Sie sich zum Beispiel eine App vor, die einen direkten Verweis auf ein UI-Objekt auf einem
Worker-Thread. Das Objekt im Worker-Thread kann einen Verweis auf einen
View
; Aber bevor die Arbeit abgeschlossen ist, wird der View
aus der Ansichtshierarchie entfernt. Wenn diese beiden Aktionen gleichzeitig stattfinden,
Mit der Referenz wird das View
-Objekt im Arbeitsspeicher gespeichert und es werden Attribute dafür festgelegt.
Der Nutzer sieht jedoch
und die App löscht das Objekt, sobald der Verweis darauf verschwunden ist.
In einem anderen Beispiel enthalten View
-Objekte Verweise auf die Aktivität
dem sie gehören. Wenn
dass die Aktivität zerstört wird,
aber es bleibt ein Arbeitsblock mit einem Gewinde,
direkt oder indirekt darauf verweist, erfasst die automatische Speicherbereinigung keine
bis dieser Arbeitsblock
ausgeführt ist.
Dieses Szenario kann in Situationen, in denen Threads mit Threads verbunden sind, zu einem Problem führen.
während ein bestimmtes Ereignis im Lebenszyklus der Aktivität auftritt, etwa eine Bildschirmdrehung.
Das System kann erst dann eine automatische Speicherbereinigung ausführen, wenn
abgeschlossene Arbeit. Daher können zwei Activity
-Objekte in
bis die automatische Speicherbereinigung stattfinden kann.
Bei solchen Szenarien empfehlen wir, dass Ihre App keine expliziten Verweise auf UI-Objekte in Arbeitsaufgaben mit Threads Die Vermeidung solcher Verweise hilft Ihnen, diese Arten von Speicherlecks vorbeugen, und gleichzeitig Threading-Konflikte vermeiden.
In allen Fällen sollte Ihre Anwendung UI-Objekte nur im Hauptthread aktualisieren. Dieses sollten Sie eine Verhandlungsrichtlinie erstellen, die es mehreren Threads ermöglicht, die Arbeit zurück an den Hauptthread senden, der die höchste Aktivität oder mit der Aktualisierung des eigentlichen UI-Objekts.
Implizite Verweise
Ein häufig auftretender Code-Design-Fehler bei Thread-Objekten ist im Snippet des Code unten:
Kotlin
class MainActivity : Activity() { // ... inner class MyAsyncTask : AsyncTask<Unit, Unit, String>() { override fun doInBackground(vararg params: Unit): String {...} override fun onPostExecute(result: String) {...} } }
Java
public class MainActivity extends Activity { // ... public class MyAsyncTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) {...} @Override protected void onPostExecute(String result) {...} } }
Der Nachteil in diesem Snippet ist, dass der Code das Threading-Objekt deklariert.
MyAsyncTask
als nicht statische innere Klasse einer Aktivität (oder eine innere Klasse)
in Kotlin). Diese Deklaration erstellt einen impliziten Verweis auf das einschließende Activity
Instanz. Das Objekt enthält daraufhin so lange einen Verweis auf die Aktivität, bis das Objekt
mit Threads zu arbeiten, was zu einer Verzögerung beim Löschen der referenzierten Aktivität führt.
Diese Verzögerung übt wiederum mehr Druck auf den Speicher aus.
Eine direkte Lösung für dieses Problem wäre, die überlastete Klasse zu definieren. Instanzen entweder als statische Klassen oder in ihren eigenen Dateien, sodass die implizite Referenz.
Eine weitere Lösung wäre, Hintergrundaufgaben immer in der entsprechenden
Activity
-Lebenszyklus-Callback, z. B. onDestroy
. Dieser Ansatz kann
mühsam und fehleranfällig sind. Grundsätzlich sollten Sie keine komplexe Nicht-UI-Logik
direkt in Aktivitäten. Außerdem wurde AsyncTask
eingestellt und ist nun
wird nicht zur Verwendung in neuem Code empfohlen. Siehe Threading unter Android
.
Threads und App-Aktivitätslebenszyklen
Der Anwendungslebenszyklus kann sich darauf auswirken, wie Threading in Ihrer Anwendung funktioniert. Möglicherweise müssen Sie entscheiden, ob ein Thread nach einer Aktivitäten vernichtet. Sie sollten auch die Beziehung zwischen der Thread-Priorisierung und ob eine Aktivität im Vordergrund oder Hintergrund.
Persistente Threads
Threads bleiben über die gesamte Lebensdauer der Aktivitäten hinaus bestehen, die sie hervorgebracht haben. Fäden ohne Unterbrechung weiter ausgeführt werden, -Aktivitäten, auch wenn diese zusammen mit dem Bewerbungsprozess beendet werden, sobald keine aktivere Anwendungskomponenten. In einigen Fällen ist diese Persistenz wünschenswert.
Stellen Sie sich einen Fall vor, bei dem eine Aktivität eine Reihe von Arbeitsblöcken mit Threads erzeugt. wird dann gelöscht, bevor ein Worker-Thread die Blöcke ausführen kann. Was sollte der mit den aktiven Blocks zu tun?
Es gibt keinen Grund, wenn die Blockierungen eine Benutzeroberfläche aktualisieren, die nicht mehr existiert. damit die Arbeit fortgesetzt werden kann. Wenn es z. B. darum geht, Nutzerinformationen aus einer Datenbank löschen und dann Ansichten aktualisieren, wird der Thread nicht mehr benötigt.
Im Gegensatz dazu haben die Arbeitspakete möglicherweise einen Vorteil, der nicht ausschließlich mit dem
UI. In diesem Fall sollten Sie den Thread dauerhaft speichern. Zum Beispiel können die Pakete
warten darauf, ein Image herunterzuladen, es im Cache auf der Festplatte zu speichern und die zugehörigen
View
-Objekt. Obwohl das Objekt nicht mehr existiert,
Das Speichern des Bildes im Cache kann trotzdem hilfreich sein, falls der Nutzer zur
zerstörte Aktivitäten.
Das manuelle Verwalten von Lebenszyklusantworten für alle Threading-Objekte kann zu
extrem komplex. Wenn Sie sie nicht richtig verwalten, kann Ihre App
Speicherkonflikte und Leistungsprobleme. Wird kombiniert
ViewModel
mit LiveData
ermöglicht Ihnen,
um Daten zu laden und bei Änderungen
ohne sich Gedanken
über den Lebenszyklus machen zu müssen.
ViewModel
-Objekte sind
eine Lösung für dieses Problem. ViewModels werden über alle Konfigurationsänderungen hinweg
bietet eine einfache Möglichkeit, Ihre Ansichtsdaten zu speichern. Weitere Informationen zu ViewModels finden Sie in der
ViewModel-Anleitung und weitere Informationen zu
LiveData finden Sie im LiveData-Leitfaden. Wenn Sie
auch mehr über die Anwendungsarchitektur,
Leitfaden zur Anwendungsarchitektur.
Threadpriorität
Wie unter Prozesse beschrieben und dem Anwendungslebenszyklus, also der Priorität, die die Threads Ihrer App erhalten hängt teilweise davon ab, wo sich die App im App-Lebenszyklus befindet. Wenn Sie beim Erstellen und Threads in Ihrer Anwendung verwalten möchten, ist es wichtig, deren Priorität festzulegen, die richtigen Threads zur richtigen Zeit die richtigen Prioritäten erhalten. Ist der Wert zu hoch, kann Ihr Thread den UI-Thread und RenderThread unterbrechen, wodurch Ihre App Frames entfernen. Ist dieser Wert zu niedrig, können Sie die asynchronen Aufgaben (z. B. langsamer laden, als sie sein müssen.
Jedes Mal, wenn Sie einen Thread erstellen, sollten Sie Folgendes aufrufen:
setThreadPriority()
Der Thread des Systems
Planer bevorzugt Threads mit hohen Prioritäten und gleicht diese aus.
und Prioritäten setzen, um letztendlich die gesamte Arbeit zu erledigen. Im Allgemeinen sind Threads
im Vordergrund
der Gruppe erhalten etwa 95% der gesamten Ausführungszeit vom Gerät,
einer Hintergrundgruppe bei etwa 5%.
Außerdem weist das System jedem Thread einen eigenen Prioritätswert zu. Dabei werden die Parameter
Klasse Process
.
Standardmäßig legt das System für die Priorität eines Threads dieselbe Priorität und Gruppe fest
Mitgliedschaften als hervorbringender Thread. Ihre Anwendung kann jedoch explizit
die Thread-Priorität mithilfe von
setThreadPriority()
Das Process
-Klasse reduziert die Komplexität bei der Zuweisung von Prioritätswerten, indem eine
eine Reihe von Konstanten, mit denen Ihre Anwendung Thread-Prioritäten festlegen kann. Beispiel:
THREAD_PRIORITY_DEFAULT
stellt den Standardwert für einen Thread dar. Ihre App sollte die Priorität des Threads auf
THREAD_PRIORITY_BACKGROUND
für Threads, die weniger dringende Arbeit ausführen.
Deine App kann die THREAD_PRIORITY_LESS_FAVORABLE
verwenden
und THREAD_PRIORITY_MORE_FAVORABLE
als Inkrementierer verwenden, um relative Prioritäten festzulegen. Eine Liste mit
Thread-Prioritäten finden Sie in der
THREAD_PRIORITY
Konstanten in
die Klasse Process
.
Weitere Informationen zu
finden Sie in der Referenzdokumentation zu
Klassen Thread
und Process
.
Hilfsklassen für Threading
Entwicklern, die Kotlin als Hauptsprache verwenden, empfehlen wir die Verwendung von Coroutinen. Koroutinen bieten eine Reihe von Vorteilen, darunter das Schreiben von asynchronem Code ohne Callbacks sowie strukturierte Nebenläufigkeit für den Umfang, die Stornierung und die Fehlerbehandlung
Das Framework bietet auch dieselben Java-Klassen und -Primitive,
Threading, z. B. Thread
, Runnable
, und Executors
Klassen,
sowie zusätzliche Optionen wie HandlerThread
.
Weitere Informationen finden Sie unter Threading unter Android.
Die HandlerThread-Klasse
Ein Handler-Thread ist im Grunde ein lang andauernder Thread, der Arbeit aus einer Warteschlange holt und .
Eine häufige Herausforderung beim Abrufen von Vorschau-Frames
Camera
-Objekt.
Wenn Sie die Kameravorschau-Frames registrieren, erhalten Sie sie im
onPreviewFrame()
Callback, das im Ereignis-Thread aufgerufen wird, aus dem er aufgerufen wurde. Wenn dieses
im Benutzeroberflächen-Thread aufgerufen wurden, der Aufgabe,
dass Arrays die Rendering- und
Ereignisverarbeitung beeinträchtigen würden.
Wenn Ihre App in diesem Beispiel den Befehl Camera.open()
an einen
des Handler-Threads, der zugehörigen
onPreviewFrame()
Rückruf
landet im Handler-Thread statt im UI-Thread. Wenn Sie also eine lang andauernde
an den Pixeln arbeiten, könnte dies eine bessere Lösung für Sie sein.
Wenn deine App einen Thread mit HandlerThread
erstellt, solltest du Folgendes nicht tun:
nicht vergessen, die
<ph type="x-smartling-placeholder"></ph>
je nach Art der Arbeit, die sie leisten. CPUs können nur
und eine kleine Anzahl von Threads
gleichzeitig zu bearbeiten. Das Festlegen der Priorität hilft,
Das System weiß, wie diese Arbeit geplant werden kann, wenn alle anderen Threads
die um Aufmerksamkeit kämpfen.
Die ThreadPoolExecutor-Klasse
Es gibt bestimmte Arten von Arbeiten, die auf
sehr parallele Arbeiten reduziert werden können,
für verteilte Aufgaben. Dazu gehört beispielsweise das Berechnen eines Filters für
8-x-8-Block eines 8-Megapixel-Bildes Bei der schieren Menge an Arbeitspaketen
erstellt wird, ist HandlerThread
nicht die zu verwendende Klasse.
ThreadPoolExecutor
ist eine Hilfsklasse, die erstellt werden kann
diesen Prozess zu vereinfachen. Diese Klasse verwaltet das Erstellen einer Gruppe von Threads, Sets
Prioritäten zu setzen und die Verteilung der Arbeit zwischen diesen Threads zu verwalten.
Wenn die Arbeitslast zu- oder abnimmt, startet die Klasse oder zerstört mehr Threads
an die Arbeitsbelastung anzupassen.
Diese Klasse trägt auch dazu bei, dass Ihre App eine optimale Anzahl von Threads erzeugt. Wenn es
erstellt eine ThreadPoolExecutor
legt die App einen Mindest- und Höchstwert fest,
Anzahl der Threads. Da die Arbeitslast
ThreadPoolExecutor
steigt an,
übernimmt die Klasse die initialisierte minimale und maximale Thread-Anzahl
und überlegen Sie, wie viel Arbeit noch erledigt werden muss. Basierend auf diesen
berücksichtigt, entscheidet ThreadPoolExecutor
, wie viele
sollten immer aktiv sein.
Wie viele Threads sollten Sie erstellen?
Auf Softwareebene kann Ihr Code jedoch Hunderte von Threads führt dies zu Leistungsproblemen. Deine App nutzt eingeschränkte CPU-Leistung mit Hintergrunddiensten, dem Renderer, der Audio-Engine, Networking und vieles mehr. CPUs haben wirklich nur die die Fähigkeit, eine kleine Anzahl von Threads parallel zu verarbeiten; und alle darüber liegenden Elemente Prioritäts- und Zeitplanproblem einordnen. Daher ist es wichtig, nur eine so viele Threads wie Ihre Arbeitslast benötigt.
In der Praxis sind dafür verschiedene Variablen verantwortlich, einen Wert auswählen (z. B. 4, zu Beginn) und testen ihn mit Systrace ist wie jede andere eine solide Strategie. Durch Ausprobieren können Sie herausfinden, Mindestanzahl von Threads verwenden, die ohne Probleme verwendet werden können.
Ein weiterer Aspekt bei der Entscheidung, wie viele Threads Sie haben sollten, sind nicht kostenlos: Sie verbrauchen viel Speicherplatz. Jeder Thread kostet mindestens 64.000 Arbeitsspeicher. Dies summiert sich schnell bei den vielen auf einem Gerät installierten Apps, insbesondere in in denen die Aufrufstacks erheblich zunehmen.
Viele Systemprozesse und Bibliotheken von Drittanbietern starten oft ihre eigenen Threadpools. Wenn deine App einen vorhandenen Threadpool wiederverwenden kann, kann diese Wiederverwendung helfen durch weniger Speicher- und Verarbeitungsressourcen.