Android 12 ソースコード解析 - アプリケーション層 2 (SystemUI の一般的な構成と起動プロセス)
前回の記事では、SystemUI が IDE を使用して編集とデバッグを行う方法を紹介しました。これは SystemUI を解析するための最も基本的な基礎ですので、読者の皆様にはできるだけマスターしていただけると幸いです。
この記事では、SystemUI の一般的な組織構造とその起動プロセスを紹介します。この記事を読むと、次のことがわかります。
- SystemUI が Dagger2 を使用することを選択した理由
- SystemUI で新しいモジュールを作成する方法
- SystemUIの起動処理
読む前に、私に従って次の質問について考えてください。
- SystemUI はどのような機能を実行する必要がありますか?
- さまざまな機能が相互に通信する必要がありますか?
- 関数間の通信が必要な場合、関数間の参照関係をどのように整理するか
- 各機能はシステム サービスと通信する必要がありますか?
- 各関数がシステム サービスと通信する必要がある場合、それらの間の参照を効率的に整理する方法
- さまざまな関数間に依存関係はありますか?
- さまざまな機能を開始するための順序要件はありますか?
上記の問題 1、2、3 に対応して、SystemUI にはロック画面、ステータス バー、ナビゲーション バー、トースト表示、音量調整などの機能が含まれていることがインターフェイスからわかります。これらの関数は相互に参照する場合があるため、たとえば、ステータス バー モジュールは、一部のステータス バー アイコンを非表示にするかどうかを決定するために、ロック画面モジュールの状況を知る必要があります。
これらの機能は多かれ少なかれシステムと対話します。たとえば、ロック画面モジュールは、ロック画面を表示するか非表示にするかを決定するために電源ボタンの状態を知る必要があります。別の例としては、各機能モジュールがナイト モードを表示するかどうか、等
ここから、各モジュールが相互に参照し、システムと通信する必要があることがわかりました。そのためには、各モジュールが目的のオブジェクトを簡単に取得できるように、適切なアーキテクチャを設計する必要があり、これらのオブジェクトの作成も可能になります。 require 他のオブジェクトの作成に依存します。実際、SystemUI のさまざまなオブジェクト間の依存関係は比較的複雑なので、各オブジェクトを手動で作成する場合は、大量のコードを記述する必要があります。これを行うために、新しいコンポーネント管理方法である DI (依存性注入) を使用します。
依存関係注入の中心的な考え方は、コードを記述するときに、開発者が各オブジェクトの作成方法を明示的に編集することはなくなりましたが、依存関係注入ライブラリがオブジェクトの作成方法を決定するということです。SystemUI は、依存関係注入ライブラリとして Dagger2 を選択しました。
注: ここで依存関係について何を説明する必要がありますか? 依存関係とは、コンポーネント A がその機能を完了する過程で別のコンポーネント B を使用する必要があることを意味します。コンポーネント B は、B に応じて A と呼ばれます。A と B には、クラス、オブジェクト、モジュールなどを指定できます。では、注射とは何でしょうか?インジェクションは、コンポーネントに依存関係を提供するプロセスです。
これで、各モジュール間で目的のオブジェクトを取得する方法 (Dagger 依存関係注入) が設計されました。
次に、SystemUI とシステムの間で通信する方法について考えてみましょう。SystemUIをどのように分割するかということですが、SystemUIはシステム全体の基本的なUIコンポーネントとして、Androidシステム全体の中で非常に重要な位置を占めています。Android TV や Android Car などのより多様なシナリオに適応するために、モジュール設計を満たすと同時に、異なる SystemUI を持つ場合があります。したがって、SystemUI はメモリ内に常駐する apk として設計されています。
SystemUI は apk として設計されており、別のプロセスで個別に実行されるため、システムと通信するには、Android の 4 つの主要コンポーネントを通じてのみ対話でき、Android の Binder を使用して対話できます。すると、SystemUI はさまざまな Services、Activity、BroadcastReceiver、ContentProvider のコレクションになります。次に、必要に応じてこれらのコンポーネントを有効にします。
これまでの考え方に基づいて、ロック画面、ステータス バー、ナビゲーション バーなどの各機能モジュールは、適切なコンポーネントに配置されていれば、正しい場所に表示できます。同時に、他のモジュールへのアクセスを容易にするために、補助として Dagger も使用します。
すべてが良さそうに見えますが、本当に良いのでしょうか?重要な質問を見逃していませんか? これらのモジュール間に順序はありますか? システムに素早くアクセスするために、SystemUI は現在この要件を設けていませんが、特定のモジュールが正常に動作する前に別のモジュールの準備が整うまで待機する必要がある場合は、設計ロジックを調整する必要があります。
SystemUI のコンポーネントを確認する
上記のことから、SystemUI は全体として 4 つの主要なコンポーネントに配置されていることがわかります。そのため、AndroidManifest.xml をチェックして、どのコンポーネントが定義されているかを確認してみましょう。
android-12.0.0_r34 ブランチの AndroidManifest.xml には、38 個のアクティビティ、11 個のサービス、4 個のプロバイダー、および 11 個のレシーバーがあります。
次に、各コンポーネントについて簡単に説明し、後続の章で、これらのコンポーネントがどのように起動し、どのような機能を実行するかを詳しく紹介します。
活動
- LongScreenShotActivity: 長いスクリーンショットに使用されるビュー。このアクティビティは、ユーザーが長いスクリーンショットを撮ることを決定したときに呼び出されます。
- ScreenRecordDialog: 画面を記録するときにポップアップするオプション ボックス ビュー。
- TunerActivity: これは開発者向けの微調整インターフェイスです。次のコマンドを使用してインターフェイス エントリを開くことができます。
adb shell pm enable com.android.systemui/com.android.systemui.tuner.TunerActivity
次に、[設定] -> [システム] -> [システム UI チューナー] のインターフェイスに入ります。
- DemoMode: SystemUI のデモ モードは開発者によっても使用され、TunerActivity の機能を補足するものであり、開発者向けオプションでオンにすることができます。
- ForceReSizableInfoActivity: ポップアップ アプリケーションは分割画面モードまたはセカンダリ画面では実行できません
- UsbPermissionActivity: USB 許可の確認ポップアップ ボックス
- UsbResolverActivity: USB デバイスのアプリケーション ポップアップ ボックスを選択します。
- UsbconfirmActivity: アプリを使用するかどうかを決定するビューをポップアップします。これは、UsbResolverActivity の後続のビューです。
- SensorUseStartedActivity: センサーがプライバシー モードの場合、センサーを使用するときにポップアップ ボックスが表示されます。
- TvUnblockSensorActivity: これがテレビで使用されるビューであることを除いて、SensorUseStartedActivity と同じです。
- Usb AccessoriesUriActivity: ボックスがポップアップ表示され、この USB デバイスに対応するアプリケーションをダウンロードできます。
- UsbContaminantActivity: USB が非アクティブ化されたことを示すボックスが表示されます。非アクティブ化の理由としては、USB ポートに盗難品があることが考えられます。
- UsbDebuggingActivity: USB デバッグを許可するかどうかをポップアップします。
- UsbDebuggingActivityAlias: これは UsbDebuggingActivity のエイリアスです
- WifiDebuggingActivity: ネットワーク上でワイヤレス デバッグを許可するかどうかをポップアップします。
- WifiDebuggingActivityAlias: WifiDebuggingActivity のエイリアスです
- WifiDebuggingSecondaryUserActivity: ポップアップ。現在ログインしているユーザーはワイヤレス デバッグを有効にできないため、メイン ユーザーに切り替える必要があります。
- NetworkOverLimitActivity: ポップアップ データ トラフィックが上限に達しました
- MediaProjectionPermissionActivity: マルチメディア投影許可の確認
- TvNotificationPanelActivity: TV 専用、メッセージ ボックスをポップアップします。
- SlicePermissionActivity:スライス許可ポップアップ ボックス
- デザートケース: イースターエッグの 1 つ
- MLandActivity: イースターエッグ ミニゲーム
- PeopleSpaceActivity: Pepole Space UI の場所をプロンプトする、Android 11 の新機能
- LaunchConversationActivity: セッションをクリックするとビューが展開され、Android 11 の新機能
- WorkLockActivity: 仕事用プロファイル インターフェイスのロックを解除します
- CreateUserActivity: ユーザー ビューの作成
- ソムナンビュレーター: スクリーンセーバー
- BrightnessDialog: 明るさポップアップボックス
- ForegroundServicesDialog: フォアグラウンド サービスを表示するポップアップ ボックス
- ChooserActivity: 現在のインテントを処理するために開くアプリケーションをユーザーが選択できるようにするボックスをポップアップします。
- ControlsProviderSelectorActivity ポップアップ「コントローラーを追加するアプリケーションを選択してください」
- ControlsEditingActivity: コントローラーを編集し、ドラッグ アンド ドロップして編集します
- コントロールお気に入りアクティビティ:コントローラー、設定
- ControlsActivity: デバイス コントローラーの一覧表示
- WalletActivity: 電子ウォレット
- ControlsRequestDialog:デバイス コントローラ ポップアップ ボックスを追加するためのコントロール リクエスト
注: ここでのコントロールは、家全体のインテリジェンスのコントローラーなど、外部デバイスのコントローラーです。
上記は非常に簡単な概要にすぎません。いくつかの一般的なコンポーネント ロジックと UI の詳細については、後続の記事で説明します。
これを見て、読者の中には「上記のアクティビティにはステータス バーやロック画面がないように見えますが、これらのアクティビティは彼らの見解ではないのですか?」と疑問に思う人もいるかもしれません。
この問題を調査するには、最初に残りのコンポーネントを確認する必要があります。
サービス
- SystemUIService: なんとも爽やかな名前ですね。このサービスには SystemUI の内部機能のほとんどが含まれており、SystemUI ソース コード分析の最優先事項でもあります。
- SystemUISecondaryUserService: 複数のユーザーの場合、このサービスは複数のユーザーの SystemUI 機能が正常であることを保証します。
- SystemUIAuxiliaryDumpService: 開発および使用のために、必要な各コンポーネントの情報をダンプして表示します
- TakeScreenshotService: スクリーンショット関連のサービス
- RecordingService: 画面録画に関するサービス
- 画像壁紙:壁紙関連サービス
- PeopleBackupFollowUpJob:People サービス UI 関連サービス
- デザートケース夢:小さなイースターエッグ
- KeyguardService: ロック画面関連のサービス
- AuxiliaryPersistenceWrapper$DeletionJobService: 外部デバイス コントローラーに関連するサービス
- DozeService: Doze に関連するサービス
コンテンツプロバイダー篇
- FileProvider: ファイルを提供します
- KeyguardSliceProvider: ロック画面スライスを提供します
- ClockOptionsProvider: セレクター プログラムのクロック プレビューを提供します。
- PeopleProvider: 指定されたショートカットの人物タイルのプレビューを返します。
ブロードキャストレシーバー篇
- ScreenshotServiceErrorReceiver: スクリーンショットが失敗したブロードキャスト レシーバー
- SysuiRestartReceiver: SystemUI ブロードキャスト レシーバーを再起動します。
- ActionProxyReceiver: 共有および編集インテントをインターセプトして、事前にいくつかの処理を容易にするブロードキャスト レシーバー。
- DeleteScreenshotReceiver: スクリーンショット ブロードキャスト レシーバーを削除します。
- SmartActionsReceiver: ユーザーが通知内のスマート アクションをクリックした後、対応するブロードキャストを受信し、スマート アクションを実行するために使用されます。
- ControlsRequestReciver: コントローラーを追加するリクエストを受信するブロードキャスト レシーバー
- TunerService$ClearReciver: TunerService を呼び出すために使用されるクリア ブロードキャスト レシーバー
- KeyboardShortcutsReceiver: キーボード ショートカットを表示または非表示にするブロードキャスト レシーバー
- MediaOutputDialogReceiver: メディア出力インテントを受信するブロードキャスト レシーバー
- PeopleSpaceWidgetPinnedReceiver: このレシーバーは、連絡先タイル ウィジェットが追加されるときに呼び出されます。
- PeopleSpaceWidgetProvider: People Space ウィジェットの実装
ここまで読んだ後でも、読者はまだ疑問を抱いています - SystemUI のロック画面とステータス バーはどこに表示されますか? ServiceでUIを表示できますか? 明らかに不合理です。では、Androidのロック画面やステータスバーはどのように表示されるのでしょうか?
ヒント: ビューを表示する上記のコンポーネントに加えて、SystemUI は WindowManager と直接対話してビューも表示します。なんだ〜〜ため息をつきますか、Androidの設計アーキテクチャは本当に少し混乱しています。
ここでは図示しませんが、後で詳しく説明します。次に、前述の Dagger2 と、それが SystemUI 内のさまざまなコンポーネントの参照関係をどのように処理するかを扱う必要があります。
SystemUI内部コンポーネント設計
Dagger2 に各コンポーネントの依存関係を管理してもらいたいので、Dagger2 にどのような依存関係があるのか、どのような方法を使用する必要があるのかを伝える必要があります。XMLファイルを使って記述しますか?それとも他の方法を使用しますか?
Dagger2 は Java アノテーションを使用して、それらの間の依存関係を記述します。同時に、パフォーマンスを向上させるために、Dagger2 はコンパイル中のアノテーションに従ってさまざまな Java オブジェクトを生成し、生成された Java オブジェクト内のすべての依存関係とライフサイクルを調整します。
さまざまな依存関係を表すために使用されるアノテーションは、Dagger2 のグラフの描画と呼ばれます。次に、SystemUI の例を組み合わせて、SystemUI が Dagger2 のグラフをどのように描画するかを確認します。
SystemUI への Dagger2 の適用
私たちのアイデアでは、RootManager などの最上位のオブジェクトが必要で、この RootManager に基づいて必要な各オブジェクトを取得できます。
SystemUI には、まだそのような RootManager が存在します。それは GlobalRootComponent です。SystemUI の各モジュールについて、必要なオブジェクトを取得したい場合は、GlobalRootComponent を通じて取得できます。
注: これを見た読者は、なぜマネージャーではなくコンポーネントと呼ばれるのか、結局 Android ではマネージャーがどれほど一般的なのか、非常に混乱しているはずです。これは、SystemUI が Dagger2 の抽象化を使用しているためです。Dagger2 では、Component はコンポーネントを表し、実際には、提供できるすべての依存関係を含むコンテナーです。したがって、GlobalRootComponent はすべての依存関係に提供できるコンポーネントです。
それでは、GlobalRootComponent の絵を描く方法を見てみましょう。
//@Singeton:告诉Dagger2所有带有@singtone注解的对象,生命周期一致。此处表示全局唯一
//@Component(xxx):告诉Dagger2,定义了一个Component组件
//modules={xxx}:告诉Dagger2,这个组件依赖这些模块。关于模块的概念,见后文
@Singleton
@Component(modules = {
GlobalModule.class,
SysUISubcomponentModule.class,
WMModule.class})
//此处是interface接口定义,Dagger2会生成对应的实现类,并按照我们给Dagger2的注解图,管理好
//各个对象的依赖和创建
public interface GlobalRootComponent {
//@Component.Builder:
//告诉Dagger2,这个是创建GlobalRootComponent的Builder类
//请务必要思考:为什么此处的对象创建要用Builder模式,而不是工厂模式?
@Component.Builder
interface Builder {
@BindsInstance
Builder context(Context context);
GlobalRootComponent build();
}
//提供一个方法,这个方法的返回类型,就是这个组件可以提供的依赖,此处表示可以提供
//WMComponent.Builder对象
WMComponent.Builder getWMComponentBuilder();
//表示此组件可以提供,SysUIComponent.Builder对象
SysUIComponent.Builder getSysUIComponent();
//注:上面两个方法提供的返回类型可以看到,他们依然是一个Component
//表示可以提供ThreadFactory对象
ThreadFactory createThreadFactory();
}
上の例ではモジュールの概念について説明しましたが、この概念を紹介する前に、「GlobalRootComponent に多数の依存関係がある場合はどうなるでしょうか?」という質問について考えてみましょう。それぞれを図に描くとごちゃごちゃしてしまうため、dagger2ではモジュールを使ってこれらの異なる依存関係を論理的に分割する機能を提供しています。その後、コンポーネントを描画するときに以下を指定するだけで済みます。
@Component(modules={
xxx.class})
表示する SysUISubComponentModule を選択します。ソース コードは次のとおりです。
//@Module:告诉Dagger2,这个module内的所有依赖,逻辑划分为:SysUISubcomponentModule
//subcomponents = {SysUIComponent.class}:告诉Dagger2,这个模块含有SysUIComponent子组件
//关于子组件的概念,我们下文介绍
@Module(subcomponents = {
SysUIComponent.class})
public abstract class SysUISubcomponentModule {
}
上の例ではサブコンポーネントについて触れましたが、この概念を紹介する前に、コンポーネントが他のユーザーにオブジェクトを提供する場合、提供されたオブジェクトは毎回作成されるべきですか、それとも一度だけ作成されるべきですか? 毛織物? これにはオブジェクトのライフ サイクルが関係します。ライフ サイクルをより適切に管理するには、同じライフ サイクルに属するオブジェクトをサブコンポーネントに配置することをお勧めします。したがって、上記の SysUIComponent サブコンポーネント内のすべてのオブジェクトは同じライフサイクルに属します。もちろん、サブコンポーネントはライフサイクルを分離するだけでなく、モジュールを分離してコードを明確にすることもできます。
それでは、このサブコンポーネントがどのように Dagger2 に通知されるかを見てみましょう。
//@SysUISingleton:告诉Dagger2所有SysUISingleton注解的对象生命周期相同
//@Subcomponent:告诉Dagger2,这是一个子组件
//modules={xxx}:告诉dagger2,这个子组件有这么些module的需要依赖
@SysUISingleton
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUIDefaultModule.class})
public interface SysUIComponent {
//告诉Dagger2生命周期
@SysUISingleton
//告诉Dagger2这个子组件的Builder接口定义
@Subcomponent.Builder
interface Builder {
//省略若干相同部分
//@BindsInstance:告诉Dagger2,将t绑定到这个Builder对象中
//在Dagger2根据我们画的图,会根据这个Builder接口,生成一个SysUIComponentBuilder对象
//在这个对象中,会有一个成员,类型为Optional<TaskSurfaceHelper>名字为setTaskSurfaceHelper.
//然后这个setTaskSurfaceHelper()接口函数的实现,就会将参数传入的t保存在setTaskSurfaceHelper成员中。
//这个过程就叫做:绑定实例,也即@BindsInstance的语义
@BindsInstance
Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
//任何一个Builder接口,都必须有一个build()函数,且返回类型为需要构建的对象类型,此处即为SysUIComponent
SysUIComponent build();
}
//定义了一个默认方法,这个方法什么也没有做
default void init() {
// Do nothing
}
//告诉Dagger2它的生命周期
@SysUISingleton
//subcomponent和component一样,如果想要对外提供依赖,就可以定义任何一个函数,函数的返回类型就是
//被提供对象的类型。
BootCompleteCacheImpl provideBootCacheImpl();
//省略若干相同部分
//当返回类型为空,而传入类型不为空的时候,表示需要向传入类型对象(SystemUIAppComponentFactory)
//中被@inject标记的成员赋值,叫做注入
//理论上,函数名为任意值,但是此种函数,几乎只会完成注入的功能,因此此函数最后都叫做inject
void inject(SystemUIAppComponentFactory factory);
//省略若干相同部分
}
上記の inject 関数では、SystemUIAppComponentFactory がどのように見えるかを確認できます。ソース コードは次のとおりです。
public class SystemUIAppComponentFactory extends AppComponentFactory {
//@Inject:告诉Dagger2,这个成员,需要Dagger2的注入。
//可是Dagger2又是如何知道,该怎么创建ContextComponentHelper的呢?
//这就是我们给Dagger2画图的作用,我们已经提前画好图给Dagger2,告诉它应该
//怎么创建这个ContextComponentHelper
@Inject
public ContextComponentHelper mComponentHelper;
}
次に、ContextComponentHelper の画像を描画する方法を見てみましょう。ソースコードは次のとおりです。
public interface ContextComponentHelper {
//省略若干无用部分
}
上記のソースコードを見ると、アノテーションがない、つまりDagger2図には描画されていないことがわかります。Dagger2図には他のクラスも描画されています。ContextComponentHelperの実装クラスはContextComponentResolverです。コードは次のとおりです。
//@SysUISingleton:告诉Dagger2它的生命周期
@SysUISingleton
public class ContextComponentResolver implements ContextComponentHelper {
private final Map<Class<?>, Provider<Activity>> mActivityCreators;
private final Map<Class<?>, Provider<Service>> mServiceCreators;
private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators;
private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators;
private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators;
//@Inject:此处就是告诉Dagger图,注入Dagger2的各种辅助功能,帮助创建这个对象
//在创建对象的时候,需要它的各种参数,而这些参数又应该怎么被Dagger2提供呢?
//只要我们把需要的参数,画好图给Dagger2即可,过程就和这个ContextComponentResolver一样啦,
//在构造器上面标注一下@Inject就可以了
@Inject
ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators,
Map<Class<?>, Provider<Service>> serviceCreators,
Map<Class<?>, Provider<SystemUI>> systemUICreators,
Map<Class<?>, Provider<RecentsImplementation>> recentsCreators,
Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) {
mActivityCreators = activityCreators;
mServiceCreators = serviceCreators;
mSystemUICreators = systemUICreators;
mRecentsCreators = recentsCreators;
mBroadcastReceiverCreators = broadcastReceiverCreators;
}
//省略若干无用部分
}
これを見ると、Dagger2 の使用方法 (画像を提供する方法) についての一般的なアイデアがすでに得られます。しかし、よく考えてみると、まだ問題があります。一部のクラスが私たちによって作成されていない場合、@Inject をコンストラクターに追加することができません。そのため、Dagger2 に、このオブジェクトがいつ必要か、どのようにすべきかをどのように知らせることができるでしょうか。作られるのか?この時点で、Dagger2 は別のアノテーション @Provides を提供します。
GlobalModule を例として説明します。ソース コードは次のとおりです。
//@Module:告诉Dagger2,定义一个逻辑模块,这个模块包含FrameworkServicesModule,GlobalConcurrencyModule
@Module(includes = {
FrameworkServicesModule.class,
GlobalConcurrencyModule.class})
public class GlobalModule {
//@Provides:告诉Dagger2,如果需要DisplayMetrics对象,就调用provideDisplayMetrics()函数即可
//至于这个函数需要的参数Context,该怎么创建,Dagger2已经能够从我们给它的图中自动找到了
@Provides
public DisplayMetrics provideDisplayMetrics(Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
context.getDisplay().getMetrics(displayMetrics);
return displayMetrics;
}
}
ここまで、@Component、@SubComponent、@Inject、@Provides、@Module などの使い方など、Dagger2 での絵の描き方(つまりアノテーションの使い方)を大まかに紹介してきました。 Dagger2 では紹介されていませんが、この記事の残りの部分ではこれで十分です。その他のアノテーションの内容については、Dagger2 のドキュメントを直接参照してください。この記事では SystemUI の分析のみに焦点を当てます。
ただし、上記は単なる図であり、使い方については触れていないので、次に SystemUI の起動処理を組み合わせて、上で描いた Dagger2 の図の使い方を見ていきます。
SystemUIの起動処理
APK の起動は 4 つの主要コンポーネントから開始されます。4 つの主要コンポーネントを起動する前に、カスタマイズされたアプリケーションがあるかどうかが確認されます。存在する場合は、アプリケーションが最初に作成されます。
Android 9 以降では、4 つの主要コンポーネントを作成する前に対応する操作を実行する AppComponentFactory が追加されました。Application と同様に、AndroidManifest.xml で次のように構成されます。
<application
android:name=".SystemUIApplication"
.
.
.
android:appComponentFactory=".SystemUIAppComponentFactory">
<!--省略若干不相干话题-->
</application>
この構成から、次の起動プロセスがわかります。
SystemUIAppComponentFactory -> SystemUIApplication -> 起動するコンポーネント (4 つの主要な Android コンポーネント)。
このプロセスをさらに分析する前に、まず SystemUI を始めたのが誰かを見てみましょう。
system_server の開始点
しかし、読者の中には「SystemUI を始めたのは誰ですか?」と尋ねる人もいるでしょう。アイコンをクリックすると他のアプリが起動するようですが、SystemUI を起動したのは誰ですか?
正解: SystemUI は、Android システムの system_server と呼ばれるプロセスによって起動されます。コンピューターの電源がオンになると system_server が起動され、その後、system_server は SystemUI の起動を含むさまざまな主要なサービスを開始します。これについては、system_server を分析するときに後で詳しく説明します。
ここでは、SystemUI を起動する system_server について簡単に説明します。
- システムが system_server プロセスを開始した後、実行されます。
new SystemServer().run();
- run() メソッドでは、次のようなさまざまなサービスが開始されます。
- ブートサービスを開始する
- コアサービス
- その他のサービス
-
他のサービスを開始すると、SystemUI が (Intent を通じて) 開始されます。SystemUI を起動する特定のコンポーネントを取得するには、
Android の PackageManager を通じて取得できます。 -
PackgeManager は、構成 config_systemUIServiceComponent を読み取ることによって、特定のコンポーネント名を取得します。Android システムのこの構成は次
のとおりです。
<string name="config_systemUIServiceComponent" translatable="false"
>com.android.systemui/com.android.systemui.SystemUIService</string>
これは、まさに SystemUI で定義したコンポーネントであることがわかります。
次に、SystemUI の起動プロセスを要約します。
- Android システムの起動が完了しました。system_server を起動します。
- system_server は、構成に従って、Intent を通じて SystemUI コンポーネントを開始します。
- SystemUI はコンポーネントを開始する前に、まず SystemUIAppComponentFactory オブジェクトを作成し、次に対応するメソッドを呼び出します。
- 次に、SystemUI は SystemUIApplication を作成し、対応するメソッドを呼び出します。
- 最後に、SystemUI は SystemUIService を作成し、対応するメソッドを呼び出します。SystemUIService を作成する前に、手順 3 で作成した SystemUIAppComponentFactory オブジェクトの対応するメソッドを呼び出します。
SystemUIAppComponentFactory を使用する理由
SystemUIAppComponentFactory のソース コードは次のとおりです。
public class SystemUIAppComponentFactory extends AppComponentFactory {
private static final String TAG = "AppComponentFactory";
@Inject
public ContextComponentHelper mComponentHelper;
public SystemUIAppComponentFactory() {
super();
}
@NonNull
@Override
//在创建Application之前,这个函数被调用
public Application instantiateApplicationCompat(
@NonNull ClassLoader cl, @NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//调用父类方法,创建Application,此处会创建AndroidManifest.xml中配置的类
//也即SystemUIApplication
Application app = super.instantiateApplicationCompat(cl, className);
//倘若创建的组件是ContextInitializer,则注册一个回调
//请一定注意:虽然此处创建了Application,但是它还不能当做Context来使用
if (app instanceof ContextInitializer) {
((ContextInitializer) app).setContextAvailableCallback(
context -> {
//1.在回调中,首先创建SystemUIFactory对象
SystemUIFactory.createFromConfig(context);
//2.通过这个SystemUIFactory得到SysUIComponent
//3.注入SystemUIAppComponentFactory中的成员,见上一小节
SystemUIFactory.getInstance().getSysUIComponent().inject(
SystemUIAppComponentFactory.this);
}
);
}
return app;
}
@NonNull
@Override
//ContentProvider被创建之前,该函数被回调
public ContentProvider instantiateProviderCompat(
@NonNull ClassLoader cl, @NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//省略若干
//此处没有列出内容,原因是:它的逻辑和上一个函数一样
}
@NonNull
@Override
public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//省略若干
//此处没有列出内容,原因是:它的逻辑和上一个函数一样
}
@NonNull
@Override
public Service instantiateServiceCompat(
@NonNull ClassLoader cl, @NonNull String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//判断是否为空,如果是,则再次注入
//第一次注入,在instantiateApplicationCompat()函数设置的回调中,
//这个回调由SystemUIApplication的onCreate()触发
if (mComponentHelper == null) {
// This shouldn't happen, but does when a device is freshly formatted.
// Bug filed against framework to take a look: http://b/141008541
SystemUIFactory.getInstance().getSysUIComponent().inject(
SystemUIAppComponentFactory.this);
}
//注意:这里的Service的创建
//1. 先查询mComponentHelper中是否有对应的Service
//2. 如果有则直接用,如果没有则调用父类方法创建
//对于SystemUIService而言,它的构造函数有@Inject注解,因此当调用mComponentHelper.resolveService时,能够正确返回SystemUIService
//请思考:为什么这个不要系统自己创建?
//答案:因为SystemUIService,需要有其他依赖对象,若是由系统创建,那么必然会有
//像SystemUIService.setXXX()之类的函数,会增加代码和逻辑。如果由Dagger2来创建则不会有
//这些烦恼
Service service = mComponentHelper.resolveService(className);
if (service != null) {
return service;
}
return super.instantiateServiceCompat(cl, className, intent);
}
@NonNull
@Override
public BroadcastReceiver instantiateReceiverCompat(@NonNull ClassLoader cl,
@NonNull String className, @Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//省略若干
//此处没有列出内容,原因是:它的逻辑和上一个函数一样
}
}
このクラスで最も重要な機能は次の 3 点です。
- SystemUIApplication、SystemUIService などの対応するコンポーネントの作成。
- mComponentHelper の初期化、つまりインジェクションによる
- Service コンポーネントが作成される前に、まず mComponentHelper にコンポーネントが既に存在するかどうかをクエリし、存在する場合はそれを直接使用します。
SystemUIApplication と SystemUIService の作成を検討する前に、「なぜ SystemUIAppComponentFactory クラスを使用するのか?」という質問をもう一度考える必要があります。このクラスは本当に合理的ですか?もっと良い代替手段はありますか?
答えは SystemUIService の作成にあると思います。SystemUIAppComponentFactory クラスを使用すると、依存関係をより適切に注入できるようになります。
SystemUIService に入る前に、最初に作成されるのは SystemUIApplication です。それが何をするのか見てみましょう。
システムUIアプリケーション
ソースコードは次のとおりです。
public class SystemUIApplication extends Application implements
SystemUIAppComponentFactory.ContextInitializer {
public SystemUIApplication() {
super();
Log.v(TAG, "SystemUIApplication constructed.");
// SysUI may be building without protolog preprocessing in some cases
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
}
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "SystemUIApplication created.");
//用于跟踪启动和关闭的时序数据
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
Trace.TRACE_TAG_APP);
log.traceBegin("DependencyInjection");
//这就是初始化各种Dagger2依赖的地方,这个回调在SystemUIAppComponentFactory中被设置
mContextAvailableCallback.onContextAvailable(this);
//有了Dagger2,就是直接使用对应的组件
mRootComponent = SystemUIFactory.getInstance().getRootComponent();
mSysUIComponent = SystemUIFactory.getInstance().getSysUIComponent();
mComponentHelper = mSysUIComponent.getContextComponentHelper();
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
log.traceEnd();
//设置主题
setTheme(R.style.Theme_SystemUI);
//判断是否为主进程
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
//监听系统的启动广播
IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
//设置线程渲染优先级
int sfPriority = SurfaceControl.getGPUContextPriority();
Log.i(TAG, "Found SurfaceFlinger's GPU Priority: " + sfPriority);
if (sfPriority == ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_REALTIME_NV) {
Log.i(TAG, "Setting SysUI's GPU Context priority to: "
+ ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
ThreadedRendererCompat.setContextPriority(
ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
//注册广播接收器
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//mBootCompleteCache表示的是:是否SytemUI的各种服务启动完成
//这些服务的启动,可能早于系统启动完成广播,也可能晚于系统启动完成广播
//1. 如果SystemUI的各种服务已经启动完成则直接返回
if (mBootCompleteCache.isBootComplete()) return;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
//2. 如果没有启动完成,则挨个启动
unregisterReceiver(this);
mBootCompleteCache.setBootComplete();
if (mServicesStarted) {
final int N = mServices.length;
for (int i = 0; i < N; i++) {
mServices[i].onBootCompleted();
}
}
}
}, bootCompletedFilter);
//监听是否Local改变
//如果Local改变,则通知中的显示就需要改变,如中英文切换等
IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
if (!mBootCompleteCache.isBootComplete()) return;
// Update names of SystemUi notification channels
NotificationChannels.createAll(context);
}
}
}, localeChangedFilter);
} else {
//如果是子进程则会进入此部分逻辑
//如果是主用户下的子进程,则什么也不做,直接返回
String processName = ActivityThread.currentProcessName();
ApplicationInfo info = getApplicationInfo();
if (processName != null && processName.startsWith(info.processName + ":")) {
return;
}
//如果不是主用户,则需要去启动必要的SystemUI组件
startSecondaryUserServicesIfNeeded();
}
}
//省略若干,简单代码
}
SystemUIApplication のコードは比較的単純であり、コメントでマークされています。次に SystemUIService を見てみましょう
システムUIサービス
ソースコードは次のとおりです。
public class SystemUIService extends Service {
//省略若干,简单代码
//@Inject:嘿嘿,这就是给Dagger画的图,好让Dagger2知道怎么创建SystemUIService
@Inject
public SystemUIService(
@Main Handler mainHandler,
DumpHandler dumpHandler,
BroadcastDispatcher broadcastDispatcher,
LogBufferFreezer logBufferFreezer,
BatteryStateNotifier batteryStateNotifier) {
//省略赋值代码
}
@Override
public void onCreate() {
super.onCreate();
//对没错,startServicesIfNeeded作为整个SystemUI关键服务的启动源头,就在这里了。
//在进入分析之前,先思考:为什么要放在这里执行呢?就不能直接放在SystemUIApplication中吗?
((SystemUIApplication) getApplication()).startServicesIfNeeded();
//LogBufferFreezer接收bugreport开始的广播,然后停止对应的LogBuffer的记录
mLogBufferFreezer.attach(mBroadcastDispatcher);
//是否监听电池的状态,并且会提示在通知栏上
if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
mBatteryStateNotifier.startListening();
}
//调试代码,用debug.crash_sysui触发RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
throw new RuntimeException();
}
//Binder调试相关
//如果太多binder调用就触发onLimitReached回调
if (Build.IS_DEBUGGABLE) {
//设置Binder代理计数开
BinderInternal.nSetBinderProxyCountEnabled(true);
//配置Binder代理触发BinderProxyLimitListener回调的最高和最低阈值,最低表示:只有降到最低以下,才能再次触发
BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
//设置BinderProxyLimitListener监听
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
public void onLimitReached(int uid) {
Slog.w(SystemUIApplication.TAG,
"uid " + uid + " sent too many Binder proxies to uid "
+ Process.myUid());
}
}, mMainHandler);
}
//启动DumpService,如果系统运行bugreport,SystemUIAuxiliaryDumpService会将SystemUI中的一些关键数据dump出来
startServiceAsUser(
new Intent(getApplicationContext(), SystemUIAuxiliaryDumpService.class),
UserHandle.SYSTEM);
}
//省略若干,简单代码
}
次に、startServicesIfNeeded() 関数の具体的な内容を見てみましょう。
startServicesIfNeeded() 関数
この関数は SystemUIApplication クラスにあり、ソース コードは次のとおりです。
public void startServicesIfNeeded() {
//1. 获取需要start的服务列表
//2. 然后调用startServicesIfNeed()继续启动
//注意:看到这里,其实大家应该大胆假设,getSystemUIServiceComponents函数
//是不是通过Dagger2的依赖得到的。如果不是为什么?
String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}
private void startServicesIfNeeded(String metricsPrefix, String[] services) {
//省略判断
mServices = new SystemUI[services.length];
//启动完成缓存对象的修改,简单,略
//首先获取DumpManager
final DumpManager dumpManager = mSysUIComponent.createDumpManager();
//trace跟踪点,略
//挨个启动服务
// 1. 首先查看mComponentHelper是否有缓存,如果有则直接使用
// 2. 如果没有则反射创建
// 3. 创建完成调用start()
// 4. 判断是否系统启动完成,如果完成则调用onBootCompleted()
// 5. 将启动的服务,加入DumpManager中,以便bugreport触发其dump
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
if (DEBUG) Log.d(TAG, "loading: " + clsName);
log.traceBegin(metricsPrefix + clsName);
long ti = System.currentTimeMillis();
try {
SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
if (obj == null) {
Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
obj = (SystemUI) constructor.newInstance(this);
}
mServices[i] = obj;
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
| InstantiationException
| InvocationTargetException ex) {
throw new RuntimeException(ex);
}
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
log.traceEnd();
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
}
if (mBootCompleteCache.isBootComplete()) {
mServices[i].onBootCompleted();
}
dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
}
mSysUIComponent.getInitController().executePostInitTasks();
log.traceEnd();
mServicesStarted = true;
}
上記からわかるように、SystemUIService の主な機能は、各サービスの start() 関数と onBootCompleted() 関数を呼び出すことです。起動を終了します。
ここで、コンテンツのこの部分を SystemUIApplication に配置する必要がある理由、および SystemUIService に直接配置できない理由を考えてください。
この質問に答える前に、まず getSystemUIServiceComponents() 関数が開始する必要のあるサービスを取得する方法を見てみましょう。
getSystemUIServiceComponents 関数は、開始するサービスを取得します。
ソースコードは次のとおりです。
public String[] getSystemUIServiceComponents(Resources resources) {
return resources.getStringArray(R.array.config_systemUIServiceComponents);
}
上記の関数は、設定された文字列配列を通じて取得されます。このような便利な機能のために文字列配列を構成する理由は何でしょうか?
なぜこのように構成されているのでしょうか?主な理由はいくつかあると思います。
- AOSP コードをコンパイルするときに、オーバーライド関数を使用して、変更目的でこれらのリソース ファイルをカスタム ディレクトリに配置できます。
- 歴史的な理由から、以前はこのように構成されていました(笑)
ここで、サービスを開始する機能ロジックが SystemUIservice に配置できず、SystemUIApplication に配置される理由を考えてみましょう。
SystemUIApplication に入れたい場合は、SystemUIApplication を介して起動するのではなく、SystemUIService を使用して起動してみてはいかがでしょうか。
-
SystemUIService によるサービスの起動に加えて、複数ユーザーの場合はいくつかのサービスも起動する必要があります。このとき、SystemUI アプリケーションは
SystemUIService を呼び出さずに、SystemUIApplication が最初に呼び出されます。SystemUIService のトリガーは system_server によって開始されるためです。
また、監視システムの起動ロジックは統一的に処理する必要があり、SystemUIApplicationに起動ロジックを組み込むのが自然になります。
注: これは明らかにバグにつながりますが、SystemUI が途中でエラーを報告して実行を停止した場合、再度実行したときに、
SystemUIService によって開始されたさまざまなサービスが正しく初期化されるでしょうか。もちろんそうではありませんが、この記事を書いているときにこのようなことがよく起こります。
-
起動ロジックが SystemUIApplication に組み込まれているため、SystemUIApplication はサービスのこの部分を起動できないのでしょうか?
開始するエントリ ポイントとして別の SystemUIService を使用する必要があるのはなぜですか? この質問に答えるには、実行するコンポーネントを開始することによって Android アプリケーションが開始されることを知っておく必要があります。つまり、system_server が SystemUI を実行したい場合は、アプリケーションだけでなく特定のコンポーネントを起動する必要があります。
したがって、特定の各サービスの開始を担当する、起動用のコンポーネント (SystemUI) が必要です。ブート ブロードキャストの監視と複数のユーザーとの連携に加えて、コンテンツのこの部分は SystemUIApplication で完了します。
また、SystemUIService には依存関係の注入が必要であるため、対応する依存関係の注入を実装するために SystemUIAppComponentFactory が作成されました。
これまでのところ、SystemUIService、SystemUIApplication、SystemUIAppComponentFactory の起動プロセスと、これらがこの方法で機能を割り当てる理由を完全に理解しました。それは次のように要約される。
- system_server が起動すると、さまざまなサービスが開始されます。
- 他のサービスを起動する場合は、まず PackageManager を通じて systemui を起動するコンポーネントの名前を取得し、その名前に従って systemui コンポーネントを起動します。
- 上記の手順で取得した名前は SystemUIService です。
- SystemUIService が開始されると、SystemUIApplication が最初に作成され、SystemUIAppComponentFactory が呼び出されて、作成前に対応する依存関係の注入が追加されます。
- SystemUIApplication が作成されると、システムの起動ブロードキャストをリッスンします。
- 次に、SystemUIService を作成します。作成する前に、SystemUIAppComponentFactory の対応するメソッドが呼び出され、依存関係の注入が追加されます。
- SystemUIService作成後、SystemUIApplication経由で各種サービスを起動します
この時点で、SystemUI の起動全体が完了します。
SystemUI にカスタム サービスを追加する
前の分析を使用して、今度はそれをテストする必要があります。カスタム サービス モジュールを作成します。このモジュールでは、その起動プロセスを出力するだけです。
- PrintLogService という名前の新しいクラスを作成します。このクラスは SystemUI を継承できます。次のように
public class PrintLogService extends SystemUI{
private String TAG = "PrintLogService";
//使用@Inject标记,让Dagger2自动管理依赖
@Inject
public PrintLogService(Context context) {
super(context);
}
//简单打印log
@Override
public void start() {
Slog.d(TAG, "Start PrintLogService");
}
//简单打印log
@Override
protected void onBootCompleted() {
Slog.d(TAG,"PrintLogService boot completed");
}
}
- 次のように、このクラスの名前を構成配列 config_systemUIServiceComponents に入力します。
<!-- 最后一行加入我们自定义的服务 -->
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.util.NotificationChannels</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.statusbar.phone.StatusBar</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.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.biometrics.AuthController</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
<item>com.android.systemui.theme.ThemeOverlayController</item>
<item>com.android.systemui.accessibility.WindowMagnification</item>
<item>com.android.systemui.accessibility.SystemActions</item>
<item>com.android.systemui.toast.ToastUI</item>
<item>com.android.systemui.wmshell.WMShell</item>
<item>com.android.systemui.PrintLogService</item>
</string-array>
- 次のコマンドを使用してコンパイルし、携帯電話にプッシュします。
mmm frameworks/base/packages/SystemUI
adb root
adb remount
adb shell rm -rf system_ext/priv-app/SystemUI
adb push out/**/system_ext/priv-app/SystemUI /system_ext/priv-app/
次に、kill を使用して既存の SystemUI プロセスを強制終了します。
- ログから次の出力が確認できます。
これは、カスタム サービスが正常に開始されたことを意味します。!!
この記事は終わりです!!
この記事では、Dagger2 のさまざまなコンポーネントの初期化に関する mContextAvailableCallback.onContextAvailable( this); について簡単に説明しています。次の記事では、この関数から開始して、SystemUI のさまざまなコンポーネントの初期化を調査し、SystemUI を理解します。使用すべきです。