
この記事では、メインのAndroidアプリケーションであるSystemUIのアーキテクチャと動作原理を分析します。 このトピックに興味があったのは、システムがどのように機能するのか疑問だからです。このシステムは膨大な数のユーザーによって使用され、Google Playまたは単にインターネットで毎日何千ものアプリケーションがダウンロードされます。 さらに、Androidの情報セキュリティの問題と、そのために作成されたアプリケーションにも興味があります。
Androidでは、SystemUIはソースコードパスが
platform_frameworks_base / packages / SystemUI /にあるアプリケーションであり、デバイスではsystem / priv-app / -SystemUIにあります。
priv-appは、特権アプリケーションが保存されるディレクトリです。 ところで、システム/アプリのパスに沿ってプリインストールされたアプリケーションがあり、自分でデバイスにインストールした通常のアプリケーションはデータ/アプリに保存されます。
これはすぐに疑問を提起します。すべてのプリインストールされた特権アプリケーションを1つのディレクトリにプッシュできないのはなぜですか、なぜこの分離が必要なのですか?
実際、一部のアプリケーションは他のアプリケーションよりも体系的です:)そして、保護された操作にアクセスするために、システムアプリケーションのエクスプロイトカバレッジを減らすために、この分離が必要です。 特別なApplicationInfo.FLAG_SYSTEMを持つアプリケーションを作成し、システムでより多くの権限を取得できますが、この権限を持つapkファイルはシステムセクションに配置されます。
そのため、SystemUIはapkファイルであり、本質的には通常のアプリケーションです。 ただし、複雑なSystemUIデバイスを見ると、単純なアプリケーションのように見えなくなりますよね?
このアプリケーションは非常に重要な機能を実行します。
- ナビゲーション
- 最近のアプリケーション
- クイック設定
- 通知バー
- ロック画面
- 音量調節
- ホーム画面
- ...
SystemUIを起動する
上記で述べたように、SystemUIは通常のアプリケーションとは異なります。そのため、ほとんどのアプリケーションの場合のように、その起動はアクティビティの起動を伴いません。 SystemUIは、システムブートプロセス中に起動し、完了できない
グローバルユーザーインターフェイスです。
<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">
Androidの世界の2つの柱の1つであるSystemServerに
アクセスすると (2番目はZygoteですが、これについては後で説明します)、システムの
起動時に
SystemUIが開始される場所を見つけることができます。
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);
ここでは、非パブリックAPI startServiceAsUserを使用してSystemUIサービスが開始する方法を確認します。 これを使用したい場合、反射に頼らなければなりません。 しかし、AndroidでリフレクションAPIを使用することに決めた場合、それが価値があるかどうかを何度か考えてください。
百回考えてください:)そのため、ここではアプリケーション用に個別のプロセスを作成します。実際、
SystemUIの各セクションは個別のサービスまたは独立したモジュールです。
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); } }
start()メソッドが呼び出されて、以下にリストされている
各サービスが開始されます。
<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>
音量調節
デバイスのボリュームボタンを定期的に使用しますが、サウンドを追加またはオフにできるように、システムで発生するプロセスについては考えません。 操作は言葉では非常に簡単に思えますが、
SystenUI / volumeのサブフォルダーにある
VolumeUIを見ると、インターフェイスにはさまざまなモードで独自のバリエーションがあります。

SystemUIサービスはstart()メソッドによって開始されると既に述べました。
VolumeUIクラスを見ると、
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(); } …
ここで、mEnabledを使用して、サウンド設定のあるパネルを表示するかどうかを決定することがわかります。 そして、VolumeDialogComponentによって判断すると、VolumeUIはサウンドバーをダイアログとして表示します。 ただし、音量キーの押下に関するすべてのアクションは
PhoneWindowで処理され
ます 。
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: {
ご覧のとおり、KEYCODE_VOLUME_UP(+)は処理されず、KEYCODE_VOLUME_DOWN(-)の処理に入ります。 onKeyDownとonKeyUpの両方のイベントで、
dispatchVolumeButtonEventAsSystemServiceメソッドが呼び出されます。
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, ... }
そのため、ここではadjustVolumeメソッドを呼び出して、イベントパラメータが割り当てられる方向を確認できるようにします。
その結果、
AudioServiceに
到達すると、
sendVolumeUpdateが呼び出され、postVolumeChangedメソッドの呼び出しに加えて、HDMIインターフェイスがインストールされます。
着メロ
AndroidのRingtonePlayerはプレーヤーとして機能します。 また、SystemUIを継承し、
start()メソッドに次のように表示されます。
@Override public void start() { ... mAudioService.setRingtonePlayer(mCallback); ... }
ここでは、本質的に
IRingtonePlayerのインスタンスである
mCallbackをインストールします。
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) { ... } };
最後に、バインダーを使用してRingtonePlayerServiceを制御し、オーディオファイルを再生できます。
パワーイ
PowerUIは、電源管理と通知を担当します。 SystemUIから同様に継承され、start()メソッドがあります。
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(); }
上記のコードからわかるように、Settings.Global.LOW_POWER_MODE_TRIGGER_LEVELの変更がサブスクライブされ、その後、
mReceiver.init()の呼び出しが呼び出されます。
public void init() {
ここで、ブロードキャストレシーバーが登録され、その助けを借りて変更追跡が実行されます。
タスク
Recentsは、Androidモバイルデバイスで主に使用される機能です。
主な機能:
- すべてのタスクを表示する
- タスクを切り替える
- タスクを削除する
さらに、RecentsもSystemUIから継承されます。 RecentsActivityは、最新のタスクを作成および更新して、画面に表示できるようにします。
RecentTaskInfoを使用すると、特定のタスクに関する情報を取得できます。
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; ...
一般に、実行中のタスクは別のトピックに配置できます。 アプリケーションをバックグラウンドに切り替える前にアプリケーション画面をぼかすことで、RecentsTaskに読み取り不能なバージョンのスナップショットが表示されるようにしたので、あらゆる側面から調査しました。 ただし、問題は、onPause()が呼び出される前にアプリケーションスナップショットが取得されることです。 この問題はいくつかの方法で解決できます。 または、システムが単に画面の内容を非表示にするようにフラグを設定します
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
スナップショットに関する
以前の記事で話したこと。
通常、アプリケーションの特定のアクティビティがタスクに表示されないようにするには、マニフェストを挿入します
android:excludeFromRecents = "true"
または、このトリックを使用できます
Intent.FLAG_ACTIVITY_MULTIPLE_TASK
フラグ
excludeFromRecents = trueの上にメインアクティビティを設定して、画面が実行中のタスクにないようにすることができますが、アプリケーションが読み込まれると、メインアクティビティのぼやけたスクリーンショットまたはその他の画像を表示する別のタスクを実行します。 詳細については、Googleドライブの例に関する
公式ドキュメントで説明しています。
ロック画面
キーガードは、すでに上記のすべてのモジュールよりも複雑です。 SystemUIで実行されるサービスであり、KeyguardViewMediatorを使用して管理されます。
private void setupLocked() { ...
ただし、実際には、KeyguardServiceはロック画面インターフェイスとは独立して機能せず、情報をStatusBarモジュールに転送するだけです。StatusBarモジュールでは、画面の視覚的な外観と情報の表示に関するアクションが既に実行されています。
通知バー
SystemBarsには、かなり複雑なデバイスと構造があります。 彼の仕事は2つの段階に分かれています。
- SystemBarsの初期化
- 通知を表示する
SystemBarsの起動を見ると
private void createStatusBarFromConfig() { ... final String clsName = mContext.getString(R.string.config_statusBarComponent); ... cls = mContext.getClassLoader().loadClass(clsName); ... mStatusBar = (SystemUI) cls.newInstance(); ... }
次に
、クラス名が読み取られ、インスタンスが作成されるリソースへのリンクが表示されます。
<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
したがって、ここではStatusBarが呼び出され、通知とUIの出力で機能することがわかります。
Androidは非常に複雑で、膨大な数のコード行に記述されている多くのトリックが含まれていることを疑う人はいないと思います。 SystemUIはこのシステムの最も重要な部分の1つであり、私はそれについて学ぶことを楽しみました。 このトピックに関する資料はほとんどないため、エラーに気づいた場合は修正してください。
PS私は、テレグラムの
@paradisecurityに関する素材の選択と短い記事を常に公開しています。