Dienstbindungen und Prozessstatus

App-Prozesse unter Android existieren nicht isoliert. Anwendungen sind oft auf Dienste angewiesen, die von anderen Anwendungen oder dem System selbst bereitgestellt werden. Wenn sich ein Prozess über eine Dienstbindung mit einem anderen verbindet, entsteht eine Abhängigkeit, die sich erheblich auf die Art und Weise auswirkt, wie das Android-Framework den Arbeitsspeicher verwaltet.

Prozessstatus und OOM-Scores

Das Android-Framework verwendet Prozessstatus , um die Wichtigkeit der einzelnen laufenden Prozesse zu verfolgen. Diese Status werden dann vom OomAdjuster verwendet, um einen Wert für die OOM-Score-Anpassung (oom_score_adj) zuzuweisen, der zwischen -1000 und 1000 liegt.

Ein niedrigerer Wert für oom_score_adj bedeutet, dass der Prozess wichtiger ist und weniger wahrscheinlich vom Low Memory Killer (LMK) beendet wird.

Häufige Prozessstatus

In der folgenden Tabelle sind einige der häufigsten Prozessstatus und ihre typischen oom_score_adj-Werte aufgeführt. Eine vollständige und aktuelle Liste finden Sie im Android-Quellcode unter android.app.ActivityManager und com.android.server.am.psc.Constants.

Prozessstatus (Abk.) Beschreibung Typischer oom_score_adj-Wert
PER (Persistent) Systemprozesse, die immer ausgeführt werden müssen (z. B. Telefonie) -800
TOP Der Prozess, mit dem der Nutzer gerade interagiert 0
VIS (Visible) Der Prozess hat eine sichtbare Aktivität (z.B. hinter einem durchscheinenden Dialogfeld). 100
PERC (Perceptible) Hintergrundprozess, der für den Nutzer wahrnehmbar ist (z. B. Musikwiedergabe) 200
FGS Prozess, der einen Vordergrunddienst hostet 0 bis 200 (variiert)
BTOP (Bound Top) Prozess, der an eine TOP-Anwendung gebunden ist 100
BFGS Gebundener Vordergrunddienst (in der Regel systemgebunden) 0
PREV (Previous) Der letzte Prozess, in dem der Nutzer vor dem aktuellen war 700
CACHED Hintergrund-Apps, die sicher beendet werden können 900 bis 999

Auswirkungen von Dienstbindungen

Wenn ein Clientprozess (z.B. eine App im Status TOP) an einen Dienst in einem Serverprozess gebunden ist, erbt der Serverprozess oft eine höhere Priorität. So wird sichergestellt, dass der Dienst so lange verfügbar bleibt, wie der Client ihn benötigt.

Diagramm, das zeigt, wie Prozess A (TOP) bindService() über system_server aufruft und Prozess B zu BTOP hochstuft

Vererbung mit BIND-Flags steuern

Die Vererbung ist das Standardverhalten bei Verwendung von Context.BIND_AUTO_CREATE. Entwickler können jedoch mit verschiedenen Flags in bindService() steuern, wie sich die Bindung auf die Wichtigkeit des Zielprozesses auswirkt.

Wichtige BIND-Flags für den OOM-Score

Die folgenden Flags sind am wichtigsten, wenn der systemweite Speicherdruck verwaltet wird:

  • BIND_AUTO_CREATE: Das häufigste Flag. Es sorgt dafür, dass der Dienstprozess gestartet und so lange aktiv gehalten wird, wie die Bindung besteht. Standardmäßig wird auch die Priorität des Serverprozesses so erhöht, dass sie mit der des Clients übereinstimmt.
  • BIND_NOT_FOREGROUND: Verhindert, dass der Prozess des Zieldienstes auf die Planungspriorität im Vordergrund (CPU-Priorität) erhöht wird. Die Speicherpriorität (oom_score_adj) kann jedoch weiterhin erhöht werden. Dies ist nützlich für Hintergrundaufgaben, die nicht mit der UI um CPU-Zyklen konkurrieren, aber trotzdem vor dem Beenden geschützt werden sollen.
  • BIND_WAIVE_PRIORITY: Ein sehr starkes Flag, das das System anweist, die Priorität für die Planung oder Speicherverwaltung des Zielprozesses nicht zu beeinflussen. Der Dienstprozess wird so verwaltet, als wäre er ein normaler Hintergrundprozess in der LRU-Liste. Er kann also auch dann vom LMK beendet werden, wenn er gebunden ist.
  • BIND_ABOVE_CLIENT: Gibt an, dass der Dienst wichtiger ist als die Client-App selbst. Wenn das System Arbeitsspeicher freigeben muss, wird es die Client-App lieber beenden als den gebundenen Dienst. Dies ist „stärker“ als BIND_AUTO_CREATE, da es eine zusätzliche Schutzebene für den Dienst auf Kosten des Clients bietet.
  • BIND_NOT_PERCEPTIBLE: Verringert die Wichtigkeit des Zieldienstes auf unter das PERCEPTIBLE Niveau. So kann das System den Arbeitsspeicher freigeben, um Platz für wichtigere, für den Nutzer wahrnehmbare Prozesse zu schaffen.

Praktische Übung: Auswirkungen von Bindungen beobachten

Wir verwenden die MemoryLab -Anwendung, um zu veranschaulichen, wie sich eine Bindung von einer TOP-App auf den Status eines separaten Prozesses auswirkt.

1. MemoryLab starten

Mit dem folgenden Befehl wird die App gestartet. Nachdem sie geöffnet wurde, muss sie im Vordergrund bleiben (drücken Sie noch nicht die Home-Taste oder wechseln Sie noch nicht zu anderen Apps).

adb shell am start -n com.android.memorylab/.MainActivity

2. Prozesse identifizieren

Prüfen Sie die Prozessstatus vor der Bindung. MemoryLab führt die Haupt-UI in einem Prozess aus und hat einen RemoteService, der in einem :remote-Prozess ausgeführt wird.

adb shell dumpsys activity processes com.android.memorylab

Beispielausgabe:

  Process OOM control (154 total, non-act at 7, non-svc at 7):
    Proc #0: fg       T/A/TOP  LCMNFUATI  t: 0 13470:com.android.memorylab/u0a417 (top-activity)
        oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
        state: cur=TOP  set=TOP   lastRss=0.00 lastCachedRss=0.00

Der Hauptprozess com.android.memorylab hat den Status TOP. Der :remote-Prozess wurde noch nicht gestartet.

3. Bindung auslösen

Senden Sie eine Broadcast-Nachricht an die App, um die Dienstbindung auszulösen:

adb shell am broadcast -a com.android.memorylab.LEAK_BINDER

4. Erhöhten Status beobachten

Prüfen Sie die Prozessstatus noch einmal:

adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"

Beispielausgabe:

  Proc #  1: vis      F/ /BTOP ---NFUATI  t: 0 13560:com.android.memorylab:remote/u0a417 (service)
    com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
    oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
    state: cur=BTOP set=BTOP  lastRss=0.00 lastCachedRss=0.00

Der :remote-Prozess wird jetzt ausgeführt und hat den Status BTOP (Bound TOP) mit einem oom_score_adj -Wert von 100. Er ist also deutlich besser geschützt als ein typischer Hintergrunddienst (der einen Wert von 500 oder höher hätte). Die Notation <=Proc{...} zeigt, welcher Prozess für diese Prioritätserhöhung verantwortlich ist.

5. In den Hintergrund senden

Drücken Sie die HOME -Taste auf dem Gerät. Prüfen Sie die Status noch einmal:

adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"

Beispielausgabe:

    Proc #  2: prev     b/ /LAST --------I  t: 0 13560:com.android.memorylab:remote/u0a417 (service)
        com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=0.00 lastCachedRss=0.00
    Proc #  1: prev     b/ /LAST --------I  t: 0 13470:com.android.memorylab/u0a417 (previous)
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=209MB lastCachedRss=0.00

Jetzt haben beide Prozesse einen niedrigeren Prioritätsstatus (PREV / oom_score_adj 700), da der Clientprozess nicht mehr TOP ist. Hinweis: LAST im Status-Dump bezieht sich auf den LAST_ACTIVITY internen Status, der allgemeinen Zusammenfassungen PREV entspricht.

Mit procstats analysieren

Das Tool procstats bietet eine historische Ansicht dieser Status.

# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab

Beispielausgabe:

  *   com.android.memorylab / u0a417 / v37:
      *   Prc com.android.memorylab / u0a417 / v37:
             TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
               Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
      *   Prc com.android.memorylab:remote / u0a417 / v37:
             TOTAL: 0.19%
           Bnd Top: 0.19%

Hier gibt Bnd Top den Prozentsatz der Zeit an, in der der Remote-Prozess an eine Anwendung im Status TOP gebunden war.

Bindungen mit Perfetto erfassen und analysieren

dumpsys liefert eine Momentaufnahme, mit Perfetto können Sie jedoch genau sehen, wann eine Bindung erfolgt und wie sich der OOM-Score in Echtzeit ändert.

1. Trace aufzeichnen

Verwenden Sie eine Konfiguration, die linux.process_stats und die atrace-Kategorie am enthält:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
    config {
        name: "linux.process_stats"
        process_stats_config { proc_stats_poll_ms: 100 }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config { ftrace_events: "am/am_proc_bound" }
    }
}
duration_ms: 15000
EOF

2. OOM-Score-Übergänge abfragen

Mit PerfettoSQL können Sie sehen, wie sich der OOM-Score des Remote-Prozesses im Verhältnis zum UI-Prozess geändert hat:

SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
  AND t.name = 'oom_score_adj'
ORDER BY ts;

3. Bindungsereignisse identifizieren

Mit dieser Abfrage können Sie genau sehen, wann eine Bindungsabhängigkeit hergestellt wurde und welcher Prozess sie initiiert hat:

SELECT
    s.ts,
    p.name AS process_name,
    t.name AS thread_name,
    s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';

System-zu-App-Bindungen

Das Android-System selbst bindet oft an Dienste in Drittanbieter-Apps, um Hauptfunktionen bereitzustellen. Ziel dieser Bindungen ist oft die Verringerung der Latenz. Indem ein Prozess aktiv und im Arbeitsspeicher gehalten wird, vermeidet das System den kostspieligen Aufwand eines „Kaltstarts“ (Laden der APK, Initialisieren der Laufzeit und Erstellen des Application-Objekts), wenn eine kritische Nutzerinteraktion erfolgt. Es gibt auch andere Bindungen, um häufige Cold Starts für Apps zu vermeiden, die Streams von Hintergrundereignissen verarbeiten müssen.

Hier sind einige Beispiele aus der Praxis, die Sie auf einem typischen Gerät beobachten können:

VoiceInteractor

Nutzer erwarten, dass ein digitaler Assistent in ihr Smartphone-Betriebssystem eingebettet ist, dass sie ihn sofort mit einem gesprochenen Aktivierungswort oder einer schnellen Eingabegeste aufrufen können und dass die Interaktion reibungslos und nahtlos verläuft.

Wenn ein Assistententrigger ausgelöst wird (z. B. das Aktivierungswort „Hey Google“ auf Google Pixel-Smartphones), muss der digitale Assistent sofort reagieren. Um dies zu gewährleisten, system_server unterhält eine permanente Bindung an den vom Nutzer ausgewählten Dienst für die Sprachinteraktion.

Diagramm, das die Bindung von „system_server“ an den Interactor-Prozess der Google-App zeigt

Wenn Sie die Prozessstatus prüfen (z.B. mit dumpsys activity processes), sehen Sie möglicherweise einen Prozess wie com.google.android.googlequicksearchbox:interactor im Status BFGS (Bound Foreground Service), der durch eine Bindung von system_server (UID 1000) aktiv gehalten wird.

NotificationListenerService

Bei einigen System-zu-App-Bindungen ist das Ziel nicht die Latenz, sondern die Vermeidung häufiger Cold Starts. NotificationListenerService, ein Dienst, der Anrufe vom System empfängt, wenn neue Benachrichtigungen veröffentlicht oder entfernt werden, ist ein gutes Beispiel. Ein typischer Smartphone-Nutzer erhält im Laufe des Tages Hunderte von Benachrichtigungen. Wenn das System die Bindung an einen Benachrichtigungslistener aufhebt, wechselt der Prozess dieser App wahrscheinlich in den Cache-Status und wird möglicherweise vom LMK beendet.

Wenn die nächste Benachrichtigung eintrifft – möglicherweise nur Sekunden später – muss das System den Prozess der App noch einmal per Kaltstart neu starten, nur um das Ereignis zu liefern. Dieser ständige Zyklus aus Beenden und Neustarten würde viel mehr CPU- und Akkuleistung verbrauchen, als den Prozess einfach im Hintergrund gebunden und aktiv zu halten.

Der „-1“-Bildschirm des Launchers (Nachrichten-Feed)

Moderne Launcher-Apps kombinieren in der Regel die wichtigsten Navigationsfunktionen (Startbildschirmsymbole und Widgets) mit einem Nachrichten-Feed, der auf einem der Launcher-Bildschirme verfügbar ist und nahtlos in die Launcher-UX integriert ist. Der Nachrichten-Feed kann von einer anderen App bereitgestellt werden. Auf Google Pixel-Smartphones ist der Launcher beispielsweise in einen Feed integriert, der von der Google App bereitgestellt wird.

Wenn Sie auf dem Startbildschirm nach links wischen, um den Nachrichten-Feed aufzurufen, muss der Übergang reibungslos sein. Der Launcher erreicht dies, indem er an eine Dienstschnittstelle in der App gebunden wird, die den Nachrichten-Feed bereitstellt, und diese Bindung so lange aufrechterhält, wie der Launcher aktiv ist. So bleiben die Feedinhalte gerendert und im Arbeitsspeicher verfügbar, auch wenn Sie sie nicht ansehen.

Weitere häufige Beispiele

  • Der Launcher (HOME_APP_ADJ): Die Launcher-App (Startbildschirm) hat einen eigenen speziellen Platz in der Prioritätsliste. Sie ist zwar nicht immer an einen Dienst gebunden, erhält aber die Anpassung HOME_APP_ADJ (in der Regel 600). Das System hält den Launcher lieber aktiv, da der Nutzer häufig zu ihm zurückkehrt. Tatsächlich würde das System die zuvor verwendete App (PREV_APP_ADJ = 700) lieber beenden als den Launcher, da das Beenden des Launchers zu einer trägen Nutzererfahrung beim Beenden einer App führen würde, weil der Nutzer warten müsste, bis der Launcher neu gestartet wurde.
  • Eingabemethoden-Editor (IME): Wenn Sie etwas eingeben, bindet sich das System an die von Ihnen ausgewählte Tastatur-App (z.B. Gboard). So bleibt der Tastaturprozess in einem erhöhten Status, auch wenn die Tastatur vorübergehend ausgeblendet ist. So kann die Tastatur sofort wieder angezeigt werden, wenn Sie auf ein anderes Textfeld tippen.
  • NFC-Zahlungen: Wenn Sie Ihr Smartphone zum Bezahlen verwenden, bindet sich das System an den NFC-Zahlungsdienst (z.B. Google Wallet). Für diese Transaktionen gelten oft strenge Echtzeitanforderungen vom Händlerterminal. Wenn die Zahlungs-App einen Kaltstart durchführen müsste, könnte die Transaktion eine Zeitüberschreitung verursachen und fehlschlagen.

Kompromisse und der Leistungseinbruch

Bindungen sind zwar für die Leistung und Korrektheit erforderlich, haben aber Auswirkungen auf den Arbeitsspeicher des Systems.

  • Geringere Flexibilität: Jeder gebundene Prozess ist ein Prozess, der vom LMK nicht einfach beendet werden kann. Dadurch wird der „Puffer“ an im Cache gespeicherten Prozessen verringert, die das System verwenden kann, um bei Bedarf Arbeitsspeicher freizugeben.
  • Verschärfung des Leistungseinbruchs: Wenn zu viele Prozesse gebunden sind, hat das System möglicherweise fast keine beendbaren Hintergrundprozesse mehr. Wenn der Speicherdruck steigt, kommt es viel schneller zu einem Leistungseinbruch, da das System gezwungen ist, wichtigere Prozesse zu beenden oder den Seitencache zu überlasten.

← Lokalität | ↑ Nach oben | Systemweit →