
In diesem Artikel werde ich die Architektur und das Funktionsprinzip der Hauptanwendung von Android - SystemUI - analysieren. Ich habe mich für dieses Thema interessiert, weil ich mich frage, wie das System funktioniert, das von einer so großen Anzahl von Benutzern verwendet wird und für das täglich Tausende von Anwendungen auf Google Play oder einfach im Internet heruntergeladen werden. Außerdem interessiert mich das Problem der Informationssicherheit von Android und die dafür erstellten Anwendungen.
Unter Android ist SystemUI eine Anwendung, deren Quellcodepfad sich in
platform_frameworks_base / packages / SystemUI / befindet , auf dem Gerät in system / priv-app / -SystemUI.
Die priv-App ist das Verzeichnis, in dem privilegierte Anwendungen gespeichert sind. Auf dem System- / App-Pfad befinden sich übrigens vorinstallierte Anwendungen, und normale Anwendungen, die wir selbst auf unserem Gerät installieren, werden in Daten / Apps gespeichert.
Dies wirft sofort die Frage auf: Warum können nicht alle vorinstallierten und privilegierten Anwendungen in ein Verzeichnis verschoben werden, warum ist diese Trennung erforderlich?
Tatsache ist, dass einige Anwendungen systematischer sind als andere :) Und diese Trennung ist erforderlich, um die Exploit-Abdeckung von Systemanwendungen zu verringern und Zugriff auf geschützte Vorgänge zu erhalten. Sie können eine Anwendung erstellen, die über eine spezielle ApplicationInfo.FLAG_SYSTEM verfügt und mehr Rechte im System erhält. Eine apk-Datei mit dieser Berechtigung wird jedoch im Systemabschnitt abgelegt.
SystemUI ist also eine APK-Datei, die im Wesentlichen eine normale Anwendung ist. Wenn Sie sich jedoch das komplexe SystemUI-Gerät ansehen, scheint es nicht mehr nur eine einfache Anwendung zu sein, oder?
Diese Anwendung führt sehr wichtige Funktionen aus:
- Navigation
- Aktuelle Anwendungen
- Schnelleinstellungen
- Benachrichtigungsleiste
- Bildschirm sperren
- Lautstärkeregler
- Startbildschirm
- ...
Starten Sie SystemUI
Wie ich oben sagte, ist SystemUI nicht wie eine reguläre Anwendung, so dass der Start nicht mit dem Start von Aktivitäten einhergeht, wie dies bei den meisten Anwendungen der Fall ist. SystemUI ist eine
globale Benutzeroberfläche , die während des Systemstartvorgangs gestartet wird und nicht abgeschlossen werden kann.
<application android:name=".SystemUIApplication" android:persistent="true" android:allowClearUserData="false" android:allowBackup="false" android:hardwareAccelerated="true" android:label="@string/app_label" android:icon="@drawable/icon" android:process="com.android.systemui" android:supportsRtl="true" android:theme="@style/Theme.SystemUI" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory">
Wenn wir uns mit SystemServer befassen, einer der beiden Säulen in der Android-Welt (die zweite ist Zygote, aber ich werde ein anderes Mal darüber sprechen), können wir den Ort finden, an dem
SystemUI startet, wenn das System
startet .
static final void startSystemUi(Context context, WindowManagerService windowManager) { Intent intent = new Intent(); intent.setComponent(new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")); intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
Hier sehen wir, wie der SystemUI-Dienst die nicht öffentliche API startServiceAsUser verwendet. Wenn Sie dies nutzen möchten, müssen Sie auf Reflexion zurückgreifen. Wenn Sie sich jedoch für die Verwendung der Reflection-API in Android entscheiden, überlegen Sie ein paar Mal, ob es sich lohnt.
Denk hundertmal nach :)Hier erstellen wir also einen separaten Prozess für die Anwendung. Tatsächlich ist jeder Abschnitt der
SystemUI ein separater Dienst oder ein unabhängiges Modul.
public abstract class SystemUI implements SysUiServiceProvider { public Context mContext; public Map<Class<?>, Object> mComponents; public abstract void start(); protected void onConfigurationChanged(Configuration newConfig) { } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { } protected void onBootCompleted() { } @SuppressWarnings("unchecked") public <T> T getComponent(Class<T> interfaceType) { return (T) (mComponents != null ? mComponents.get(interfaceType) : null); } public <T, C extends T> void putComponent(Class<T> interfaceType, C component) { if (mComponents != null) { mComponents.put(interfaceType, component); } } public static void overrideNotificationAppName(Context context, Notification.Builder n, boolean system) { final Bundle extras = new Bundle(); String appName = system ? context.getString(com.android.internal.R.string.notification_app_name_system) : context.getString(com.android.internal.R.string.notification_app_name_settings); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName); n.addExtras(extras); } }
Die Methode start () wird aufgerufen, um
jeden Dienst zu starten, die unten aufgeführt sind.
<string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.Dependency</item> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> <item>com.android.systemui.recents.Recents</item> <item>com.android.systemui.volume.VolumeUI</item> <item>com.android.systemui.stackdivider.Divider</item> <item>com.android.systemui.SystemBars</item> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> <item>com.android.systemui.keyboard.KeyboardUI</item> <item>com.android.systemui.pip.PipUI</item> <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> <item>@string/config_systemUIVendorServiceComponent</item> <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> <item>com.android.systemui.SliceBroadcastRelayHandler</item> </string-array>
Lautstärkeregler
Wir verwenden regelmäßig die Lautstärketasten auf unseren Geräten, denken jedoch nicht darüber nach, welche Prozesse im System ablaufen sollen, damit wir den Ton erhöhen oder verringern können. Die Bedienung scheint in Worten recht einfach zu sein, aber wenn Sie sich die VolumeUI ansehen, die sich in einem Unterordner von
SystenUI / volume befindet , hat die Benutzeroberfläche ihre eigenen Variationen in verschiedenen Modi.

Ich habe bereits gesagt, dass SystemUI-Dienste mit der Methode start () gestartet werden. Wenn wir uns die
VolumeUI- Klasse
ansehen , erbt sie auch von SystemUI.
public class VolumeUI extends SystemUI { private static final String TAG = "VolumeUI"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); private final Handler mHandler = new Handler(); private boolean mEnabled; private VolumeDialogComponent mVolumeComponent; @Override public void start() { boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui); boolean enableSafetyWarning = mContext.getResources().getBoolean(R.bool.enable_safety_warning); mEnabled = enableVolumeUi || enableSafetyWarning; if (!mEnabled) return; mVolumeComponent = new VolumeDialogComponent(this, mContext, null); mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning); putComponent(VolumeComponent.class, getVolumeComponent()); setDefaultVolumeController(); } …
Hier sehen wir, dass wir mit mEnabled festlegen, ob wir ein Panel mit Soundeinstellungen anzeigen möchten. Nach der VolumeDialogComponent zeigt VolumeUI die Soundbar als Dialogfeld an. Alle Aktionen zum Drücken der Lautstärketasten werden jedoch in
PhoneWindow ausgeführt .
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) { ... switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_MUTE: {
Soweit wir sehen können, wird KEYCODE_VOLUME_UP (+) nicht verarbeitet und geht in die Verarbeitung von KEYCODE_VOLUME_DOWN (-) über. In beiden Fällen, sowohl onKeyDown als auch onKeyUp, wird die
dispatchVolumeButtonEventAsSystemService- Methode aufgerufen.
public void dispatchVolumeButtonEventAsSystemService(@NonNull KeyEvent keyEvent) { switch (keyEvent.getAction()) { case KeyEvent.ACTION_DOWN: { int direction = 0; switch (keyEvent.getKeyCode()) { case KeyEvent.KEYCODE_VOLUME_UP: direction = AudioManager.ADJUST_RAISE; break; ... mSessionBinder.adjustVolume(mContext.getPackageName(), mCbStub, true, direction, ... }
Hier rufen wir also die adjustVolume-Methode auf, damit wir unsere Richtung überprüfen können, der der Ereignisparameter zugewiesen wird.
Wenn wir zum
AudioService gelangen, wird
sendVolumeUpdate aufgerufen, und zusätzlich zum Aufrufen der postVolumeChanged-Methode wird die HDMI-Schnittstelle installiert.
Klingelton-Player
RingtonePlayer in Android fungiert als Player. Es erbt auch von SystemUI und in der
start () -Methode sehen wir:
@Override public void start() { ... mAudioService.setRingtonePlayer(mCallback); ... }
Hier installieren wir mCallback, eine Instanz von
IRingtonePlayer .
private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() { @Override public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping) throws RemoteException { ... } @Override public void stop(IBinder token) { ... } @Override public boolean isPlaying(IBinder token) { ... } @Override public void setPlaybackProperties(IBinder token, float volume, boolean looping) { ... } @Override public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) { ... } @Override public void stopAsync() { ... } @Override public String getTitle(Uri uri) { ... } @Override public ParcelFileDescriptor openRingtone(Uri uri) { ... } };
Am Ende können Sie RingtonePlayerService mithilfe des Binders zum Abspielen von Audiodateien steuern.
Powerui
PowerUI ist für die Energieverwaltung und Benachrichtigungen verantwortlich. Es wird ebenfalls von SystemUI geerbt und verfügt über eine start () -Methode.
public void start() { mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mHardwarePropertiesManager = (HardwarePropertiesManager) mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime(); mWarnings = Dependency.get(WarningsUI.class); mEnhancedEstimates = Dependency.get(EnhancedEstimates.class); mLastConfiguration.setTo(mContext.getResources().getConfiguration()); ContentObserver obs = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { updateBatteryWarningLevels(); } }; final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), false, obs, UserHandle.USER_ALL); updateBatteryWarningLevels(); mReceiver.init(); showThermalShutdownDialog(); initTemperatureWarning(); }
Wie aus dem obigen Code
ersichtlich ist, werden die Änderungen von Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL abonniert, und danach wird der Aufruf von
mReceiver.init () aufgerufen.
public void init() {
Hier wird der Rundfunkempfänger registriert, mit dessen Hilfe die Änderungsverfolgung durchgeführt wird.
Die Aufgaben
Recents ist eine primäre und häufig verwendete Funktion in Android-Mobilgeräten.
Die Hauptfunktionen:
- Alle Aufgaben anzeigen
- Zwischen Aufgaben wechseln
- Aufgaben löschen
Darüber hinaus wird Recents auch von SystemUI geerbt. RecentsActivity erstellt und aktualisiert die neuesten Aufgaben, damit wir sie auf unserem Bildschirm sehen können.

Und mit
RecentTaskInfo können wir Informationen über eine bestimmte Aufgabe erhalten.
public static class RecentTaskInfo implements Parcelable { public int id; public int persistentId; public Intent baseIntent; public ComponentName origActivity; public ComponentName realActivity; public CharSequence description; public int stackId; ...
Im Allgemeinen können laufende Aufgaben in einem separaten Thema platziert werden. Ich habe es von allen Seiten studiert, weil ich den Anwendungsbildschirm verwischen wollte, bevor ich die Anwendung in den Hintergrund schaltete, damit in RecentsTask eine unlesbare Version des Snapshots angezeigt wurde. Das Problem ist jedoch, dass der Anwendungs-Snapshot erstellt wird, bevor onPause () aufgerufen wird. Dieses Problem kann auf verschiedene Arten gelöst werden. Oder setzen Sie das Flag so, dass das System einfach den Inhalt des Bildschirms mit verbirgt
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
Worüber ich in einem
früheren Artikel nur über Schnappschüsse gesprochen habe.
Sie können im Allgemeinen sicherstellen, dass die spezifische Aktivität der Anwendung nicht in Aufgaben angezeigt wird, indem Sie sie im Manifest ablegen
android:excludeFromRecents = "true"
Oder Sie können den Trick mit verwenden
Intent.FLAG_ACTIVITY_MULTIPLE_TASK
Sie können die Hauptaktivität über dem Flag
excludeFromRecents = true setzen , damit sich der Bildschirm nicht in den ausgeführten Aufgaben befindet. Wenn die Anwendung geladen wird, führen Sie eine separate Aufgabe aus, die entweder einen verschwommenen Screenshot der Hauptaktivität oder ein anderes Bild anzeigt. Wie dies getan werden kann, wird in der
offiziellen Dokumentation am Beispiel von Google Drive beschrieben.
Bildschirm sperren
Keyguard ist bereits komplizierter als alle oben genannten Module. Es ist ein Dienst, der in SystemUI ausgeführt und mit KeyguardViewMediator verwaltet wird.
private void setupLocked() { ...
In der Realität arbeitet KeyguardService jedoch nicht unabhängig mit der Sperrbildschirmschnittstelle, sondern überträgt nur Informationen an das StatusBar-Modul, in dem bereits Maßnahmen hinsichtlich des visuellen Erscheinungsbilds des Bildschirms und der Anzeige von Informationen ergriffen wurden.
Benachrichtigungsleiste
SystemBars hat ein ziemlich komplexes Gerät und eine ziemlich komplexe Struktur. Seine Arbeit gliedert sich in zwei Phasen:
- SystemBars initialisieren
- Benachrichtigungen anzeigen
Wenn Sie sich den Start von SystemBars ansehen
private void createStatusBarFromConfig() { ... final String clsName = mContext.getString(R.string.config_statusBarComponent); ... cls = mContext.getClassLoader().loadClass(clsName); ... mStatusBar = (SystemUI) cls.newInstance(); ... }
Dann sehen wir einen Link zu der Ressource, aus der
der Klassenname gelesen und eine Instanz erstellt wird.
<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
Wir sehen also, dass hier eine Statusleiste aufgerufen wird, die mit der Benachrichtigungs- und UI-Ausgabe funktioniert.
Ich denke, niemand hat daran gezweifelt, dass Android sehr kompliziert ist und viele Tricks enthält, die in einer Vielzahl von Codezeilen beschrieben werden. SystemUI ist einer der wichtigsten Teile dieses Systems und ich habe es genossen, darüber zu lernen. Aufgrund der Tatsache, dass es zu diesem Thema nur sehr wenig Material gibt, korrigieren Sie mich bitte, wenn Sie Fehler bemerken.
PS Ich
stelle immer eine Auswahl an Material und kürzeren Artikeln auf
@paradisecurity in Telegrammen zur Verfügung.