SystemUI是系統啟動中第一個使用者肉眼可見的應用,其功能包羅萬象,比如開機後看到的鎖屏界面,充電時充電界面,狀态欄,導航欄,多工作列等,都是與Android手機使用者息息相關的功能。是以不止SystemUI開發者,普通的應用開發者也很有必要去了解一下SystemUI。本系列文章會基于Android P和Android Q來介紹SystemUI的各個方面,本篇作為本系列第一篇,主要介紹了SystemUI的啟動流程,以及主要功能簡介。
前言
轉載請聲明,轉自【https://www.cnblogs.com/andy-songwei/p/11471355.html】,謝謝!
SystemUI是系統啟動中第一個使用者肉眼可見的應用,其功能包羅萬象,比如開機後看到的鎖屏界面,充電時充電界面,狀态欄,導航欄,多工作列等,都是與Android手機使用者息息相關的功能。是以不止SystemUI開發者,普通的應用開發者也很有必要去了解一下SystemUI。本系列文章會基于Android P和Android Q來介紹SystemUI的各個方面,本篇作為本系列第一篇,主要介紹了SystemUI的啟動流程,以及主要功能簡介。
本文的主要内容如下:
一、SystemUI簡介
SystemUI,顧名思義是系統為使用者提供的系統級别的資訊顯示與互動的一套UI元件,是以其功能包羅萬象。比如鎖屏、狀态欄、底部導航欄、最近使用App清單等,大部分功能互相獨立,按需啟動,後文會繼續列出更多功能。在系統源碼中,其位置為:frameworks/base/package/SystemUI。盡管從表現形式上看,SystemUI和普通的Android APP有較大的差别,但其本質和普通APP并沒有什麼差别,也是以apk的形式存在,如下圖所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5COzEDM0UzMwkTMtIDN5kTN3ATOxMDM5ATOxAjMtIDMwIzN08CX5ATOxAjMvwlMwAjM3QzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
以目前測試機為例,其就預置在系統指定的目錄下。也是通過Android的4大元件中的Activity、Service、BroadcastReceiver來接受外界的請求并執行相關的操作,隻不過它們所接受的請求主要來自各個系統服務而已。
二、Lambda表達式簡介
由于後面有個流程中用到了Lambda表達式,為了後面便于講解,這裡咱們先簡單介紹一下它,并簡單示範其使用方法,這裡不做深入探讨,有興趣的可以自行研究。
Lambda表達式是一個匿名函數,即沒有函數名的函數,是基于數學中的λ演算得名。在java中,從java8開始引入,使用它來設計代碼會更加簡潔。下面在Android項目中舉兩個例子來直覺感受一下Lambda文法的使用。
以下是一個很常見的設定點選事件的例子,先看看不用Lambda表達式時的情況:
1 mTextView.setOnClickListener(new View.OnClickListener() {
2 @Override
3 public void onClick(View v) {
4 Log.i("songzheweiwang", "test lambda");
5 }
6 });
在采用Lambda表達式後,就是下面這種情況:
1 mTextView.setOnClickListener(onClickListener -> {
2 Log.i("songzheweiwang", "test lambda");
3 });
其中“onClickListener”是随意取的一個字元串,我們取名的時候便于識别就可以了。可見整個代碼簡潔了很多,閱讀起來也非常簡單。
另外再看一個更加明顯的例子,不使用Lambda表達式時是這樣:
1 Runnable runnable = new Runnable() {
2 @Override
3 public void run() {
4 Log.i("songzheweiwang", "test lambda");
5 }
6 };
使用Lambda表達式後,就成了這樣:
1 Runnable runnable2 = () -> Log.i("songzheweiwang", "test lambda");
如上的“->”符号可以讀作“go to”。使用Lambda表達式來代替匿名的内部類,确實是非常的友善,但是使用的時候需要注意java的版本号,前面說了,是在java8中才引入的,否則在編譯時會報如下的錯誤:
如上内容參考【Lambda表達式_百度百科】
三、SystemUI的啟動時機
在【【乘風破浪】Android系統啟動篇】中,我介紹過Android系統的大緻流程,在第6步中講到了SystemServer程序的啟動。SystemServer程序啟動時,會執行下面的代碼:
1 //=========SystemServer.java=========
2 public static void main(String[] args) {
3 new SystemServer().run();
4 }
5 private void run() {
6 ......
7 //建立消息Looper
8 Looper.prepareMainLooper();
9 // 加載動态庫libandroid_servers.so,初始化native服務
10 System.loadLibrary("android_servers");
11 ......
12 //初始化系統context
13 createSystemContext();
14 //建立SystemServiceManager
15 mSystemServiceManager = new SystemServiceManager(mSystemContext);
16 ......
17 //啟動引導服務,如AMS等
18 startBootstrapServices();
19 //啟動核心服務
20 startCoreServices();
21 //啟動其它服務,如WMS,SystemUI等
22 startOtherServices();
23 ....
24 }
在執行完第18、20行的代碼後,會啟動引導服務和一些核心服務,如AMS等,然後第22行中就會啟動其他服務,其中SystemUI就在其中。
1 //======SystemServer.java======
2 private void startOtherServices() {
3 ......
4 // We now tell the activity manager it is okay to run third party
5 // code. It will call back into us once it has gotten to the state
6 // where third party code can really run (but before it has actually
7 // started launching the initial applications), for us to complete our
8 // initialization.
9 mActivityManagerService.systemReady(() -> {
10 ......
11 traceBeginAndSlog("StartSystemUI");
12 try {
13 startSystemUi(context, windowManagerF);
14 } catch (Throwable e) {
15 reportWtf("starting System UI", e);
16 }
17 traceEnd();
18 ......
19 }, BOOT_TIMINGS_TRACE_LOG);
20 ......
21 }
在第9行中,前面講過AMS先啟動了,mActivityManagerService調用systemReady方法,這裡就用到了前面介紹過的Lambda表達式,systemReady方法的源碼如下:
1 public void systemReady(final Runnable goingCallback, BootTimingsTraceLog traceLog) {
2 ......
3 if (goingCallback != null) {
4 goingCallback.run();
5 }
6 ......
7 }
這裡參照前面介紹的Lambda表達式的使用方法就容易了解了,實際上就是執行Runnable的回調而已,這裡其實就等同于如下代碼:
1 mActivityManagerService.systemReady(2 new Runnable() {
3 @Override
4 public void run() {
5 //Lambda表達式中的回調代碼
6 }
7 }
8 },BOOT_TIMINGS_TRACE_LOG);
實際上在Lambda表達式還未引入前,即早期的代碼中就是這樣寫法。
當一切就緒後,回調開始執行,就開始執行第13行的startSystemUI方法了。該方法的源碼如下:
1 static final void startSystemUi(Context context, WindowManagerService windowManager) {
2 Intent intent = new Intent();
3 intent.setComponent(new ComponentName("com.android.systemui",
4 "com.android.systemui.SystemUIService"));
5 intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
6 //Slog.d(TAG, "Starting service: " + intent);
7 context.startServiceAsUser(intent, UserHandle.SYSTEM);
8 windowManager.onSystemUiStarted();
9 }
第3、4行中給出了包名和類名,這樣就開始啟動SystemUI了。從這段代碼可以看到,SystemUI是通過Service來啟動的,而且是以系統的身份來啟動它的。
四、Service啟動流程淺析
上節中startSystemUI方法中開始啟動SystemUIService,Service的啟動流程比較複雜,這裡不做詳細分析,僅簡單介紹一下其中和本節息息相關的關鍵流程。
上節代碼第7行startSystemUI方法的調用者看起來是Context類型的context,Context是一個抽象類,實際執行者其實是ContextImpl。調用流程會通過Binder方式從ContextImpl跳轉到AMS中,再通過Binder方式跳轉到ActivityThread中的内部類ApplicationThread中的scheduleCreateService方法。在該方法中會發送給Handler H來處理,Handler H的執行個體化是使用的主線程的Looper,是以其回調方法handleMessage就是在主線程中執行的,此時會在該方法中調用handleCreateService方法,咱們從這個方法開始看。
1 private void handleCreateService(CreateServiceData data) {
2 ......
3 Service service = null;
4 try {
5 ......
6 service = packageInfo.getAppFactory()
7 .instantiateService(cl, data.info.name, data.intent);
8 } catch (Exception e) {
9 ......
10 }
11 try {
12 ......
13 ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
14 context.setOuterContext(service);
15 Application app = packageInfo.makeApplication(false, mInstrumentation);
16 service.attach(context, this, data.info.name, data.token, app,
17 ActivityManager.getService());
18 service.onCreate();
19 ......
20 } catch (Exception e) {
21 ......
22 }
23 }
第6行建立了service的執行個體,第13行建立上下文,第15行建立Application,并在其中執行了Application的onCreate方法,第18行執行了service的onCreate方法。這裡進入到第15行的makeApplication方法。下面截取了關鍵代碼:
1 public Application makeApplication(boolean forceDefaultAppClass,
2 Instrumentation instrumentation) {
3 ......
4 Application app = null;
5 String appClass = mApplicationInfo.className;
6 if (forceDefaultAppClass || (appClass == null)) {
7 appClass = "android.app.Application";
8 }
9 try {
10 ......
11 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
12 app = mActivityThread.mInstrumentation.newApplication(
13 cl, appClass, appContext);
14 appContext.setOuterContext(app);
15 } catch (Exception e) {
16 ......
17 }
18 mActivityThread.mAllApplications.add(app);
19 ......
20 if (instrumentation != null) {
21 try {
22 instrumentation.callApplicationOnCreate(app);
23 } catch (Exception e) {
24 ......
25 }
26 }
27 ......
28 return app;
29 }
因為是初始啟動,是以會走到第7行。第12行的newApplication源碼如下:
1 public Application newApplication(ClassLoader cl, String className, Context context)
2 throws InstantiationException, IllegalAccessException,
3 ClassNotFoundException {
4 Application app = getFactory(context.getPackageName())
5 .instantiateApplication(cl, className);
6 app.attach(context);
7 return app;
8 }
繼續追蹤instantiateApplication方法:
1 public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
2 @NonNull String className)
3 throws InstantiationException, IllegalAccessException, ClassNotFoundException {
4 return (Application) cl.loadClass(className).newInstance();
5 }
這裡就通過類加載器的形式建立了Application的執行個體。可見前面的makeApplication方法第12行的作用就是建立Application執行個體了,然後走到該方法的第22行,進入該方法:
1 public void callApplicationOnCreate(Application app) {
2 app.onCreate();
3 }
該方法中Application執行了onCreate方法。
到這裡service的大緻啟動流程就明了了,這裡咱們需要記住一個執行順序(因為我看過不少資料容易在這裡犯錯,說是Application會比Service先執行個體化,通過這個流程我們可以看到這種說法是錯誤的,是以這裡着重提出來):
(1)執行個體Service;
(2)執行個體Application;
(3)Application執行個體執行onCreate方法;
(4)Service執行個體執行onCrate方法。
五、SystemUIApplication中onCreate方法處理邏輯
上一節我們分析了,會先執行Application的onCreate方法,在執行Service的onCreate方法,這裡先分析SystemUIApplication中onCreate方法的執行邏輯。
1 //============SystemUIApplication.java========
2 private SystemUI[] mServices;
3 @Override
4 public void onCreate() {
5 super.onCreate();
6 ......
7 //設定主題
8 setTheme(R.style.Theme_SystemUI);
9 ......
10 if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
11 IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
12 bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
13 registerReceiver(new BroadcastReceiver() {
14 @Override
15 public void onReceive(Context context, Intent intent) {
16 if (mBootCompleted) return;
17
18 if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
19 unregisterReceiver(this);
20 mBootCompleted = true;
21 if (mServicesStarted) {
22 final int N = mServices.length;
23 for (int i = 0; i < N; i++) {
24 mServices[i].onBootCompleted();
25 }
26 }
27 }
28 }, bootCompletedFilter);
29
30 IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
31 registerReceiver(new BroadcastReceiver() {
32 @Override
33 public void onReceive(Context context, Intent intent) {
34 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
35 if (!mBootCompleted) return;
36 // Update names of SystemUi notification channels
37 NotificationChannels.createAll(context);
38 }
39 }
40 }, localeChangedFilter);
41 } else {
42 ......
43 startSecondaryUserServicesIfNeeded();
44 }
45 }
這裡說一下第9行的if-else邏輯,我們知道Linux是多使用者作業系統,是以這個if-else語句就是判斷是系統使用者,還是切換到了其它使用者。SystemUI大多數功能對所有使用者都是一樣的,隻有少部分功能會因為不同的使用者而表現不一樣,比如通知、多任務功能等,這裡後面會再講到。如果是系統使用者就會走if中的流程,這裡注冊了兩個廣播接收器,用于監聽Intent.ACTION_BOOT_COMPLETED和Intent.ACTION_LOCALE_CHANGED。
Intent.ACTION_BOOT_COMPLETED是監聽開機啟動,這裡分析的Android9.0的系統源碼,目前系統中使用的是FBE加密方式(讀者請自行查閱FBE加密方式,這裡不做詳細介紹),這種方式下,要等到系統啟動并鎖屏界面解鎖後,在進入到桌面過程中,系統才會發送發送該廣播,是以接收該廣播的處理邏輯會比較延後。通過第15行和第18行可以看到,該廣播隻會處理一次,就會反注冊該廣播,以後就不會再接收了。在這個邏輯當中,第20行到第25行,判斷mServicesStarted變量,該變量表示SystemUIService是否已經啟動了,實際上由于該廣播接收的時機比較延後,會在SystemUIService啟動完後才接收到該廣播,是以這裡面的代碼會在此時執行。第23行的mService[]數組存儲的是SystemUI的子服務,當整個系統啟動完成後,這裡面的每個子服務都會執行onBootCompleted()方法,讓各個子服務知道系統啟動完成了,要做自己該做的事情了。mService[]的指派以及它存儲的SytemUI子服務,下一節會詳細講解,這裡我們隻需要知道,這個過程發生在SystemUIService的啟動階段即可。
Intent.ACTION_LOCALE_CHANGED廣播是用于監聽裝置目前區域設定已更改時發出的廣播,簡單來說就是修改語言時發出的廣播(暫時不知道其它動作是否也會發送該廣播)。
第42行就是在目前使用者不是系統使用者時的情況,即切換使用者後的場景,該動作發生時系統是已經啟動了的,不會再觸發Intent.ACTION_BOOT_COMPLETED廣播。這裡看一看它的執行過程:
1 void startSecondaryUserServicesIfNeeded() {
2 String[] names =
3 getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
4 startServicesIfNeeded(names);
5 }
第2行和第4行其實就是啟動資源檔案指定的功能,如下所示:
1 <string-array name="config_systemUIServiceComponentsPerUser" translatable="false">
2 <item>com.android.systemui.Dependency</item>
3 <item>com.android.systemui.util.NotificationChannels</item>
4 <item>com.android.systemui.recents.Recents</item>
5 </string-array>
可以看到包含了通知(第3行)和多任務(第4行),這幾個功能會因使用者不同而異,第2行是什麼功能暫時不清楚,讀者可以自己查閱。另外我們會發現,實際上這幾個子服務,在下一節的 config_systemUIServiceComponents數組資源中也都是包含的,也就正好對應了前面說的,切換到個人使用者後這幾個功能會因使用者不同而表現不同,需要重新加載一次。startSecondaryUserServicesIfNeeded方法的處理邏輯,在下一節會詳細講到,這裡咱們隻需要清楚這一塊的功能即可。
六、SystemUIService中onCreate方法處理邏輯
如前文所述,SystemUI通過“com.android.systemui.SystemUIService”這個服務來啟動,在Application的onCreate方法執行完後,就會執行自己的onCreate方法。下面看看SystemUIService啟動過程中做了哪些工作:
1 public class SystemUIService extends Service {
2 @Override
3 public void onCreate() {
4 super.onCreate();
5 ((SystemUIApplication) getApplication()).startServicesIfNeeded();
6 ......
7 }
8 ......
9 }
該類中關鍵代碼是第5行代碼,其它的沒有什麼重要邏輯,繼續追蹤startServicesIfNeeded()方法:
1 //===========SystemUIApplication==========
2 public void startServicesIfNeeded() {
3 String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
4 startServicesIfNeeded(names);
5 }
第3行在資源檔案中對應的數組如下所示,每一項都對應了一個子服務(這裡并不是表示它們是Service,而是指某項功能子產品),實際上在Android O及以前的版本中,這些類都是以數組的形式儲存在代碼中的。
1 <string-array name="config_systemUIServiceComponents" translatable="false">
2 <item>com.android.systemui.Dependency</item>
3 <item>com.android.systemui.util.NotificationChannels</item>
4 <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
5 <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
6 <item>com.android.systemui.recents.Recents</item>
7 <item>com.android.systemui.volume.VolumeUI</item>
8 <item>com.android.systemui.stackdivider.Divider</item>
9 <item>com.android.systemui.SystemBars</item>
10 <item>com.android.systemui.usb.StorageNotification</item>
11 <item>com.android.systemui.power.PowerUI</item>
12 <item>com.android.systemui.media.RingtonePlayer</item>
13 <item>com.android.systemui.keyboard.KeyboardUI</item>
14 <item>com.android.systemui.pip.PipUI</item>
15 <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
16 <item>@string/config_systemUIVendorServiceComponent</item>
17 <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
18 <item>com.android.systemui.LatencyTester</item>
19 <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
20 <item>com.android.systemui.ScreenDecorations</item>
21 <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
22 <item>com.android.systemui.SliceBroadcastRelayHandler</item>
23 </string-array>
在Android Q上将第21行修改為了
1 <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
就是将指紋識别功能改成了生物識别功能,在Android Q上開始,除了指紋識别外,還增加了人臉識别。在原來的基礎上另外再添加了3條:
1 <item>com.android.systemui.SizeCompatModeActivityController</item>
2 <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
3 <item>com.android.systemui.theme.ThemeOverlayController</item>
打開這每一個類後,會發現它們都繼承自SystemUI類,SystemUI類是一個抽象類,提供了如下接口:
1 public abstract class SystemUI implements SysUiServiceProvider {
2 ......
3 public abstract void start();
4
5 protected void onConfigurationChanged(Configuration newConfig) {
6 }
7
8 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
9 }
10
11 protected void onBootCompleted() {
12 }
13 ......
14 }
15 }
startServicesIfNeeded(names)方法源碼如下:
1 private SystemUI[] mServices;
2 private void startServicesIfNeeded(String[] services) {
3 ......
4 mServices = new SystemUI[services.length];
5 ......
6 final int N = services.length;
7 for (int i = 0; i < N; i++) {
8 String clsName = services[i];
9 Class cls;
10 try {
11 cls = Class.forName(clsName);
12 mServices[i] = (SystemUI) cls.newInstance();
13 } catch (ClassNotFoundException ex) {
14 throw new RuntimeException(ex);
15 } catch (IllegalAccessException ex) {
16 throw new RuntimeException(ex);
17 } catch (InstantiationException ex) {
18 throw new RuntimeException(ex);
19 }
20 ......
21 mServices[i].mContext = this;
22 mServices[i].mComponents = mComponents;
23 ......
24 mServices[i].start();
25 ......
26 if (mBootCompleted) {
27 mServices[i].onBootCompleted();
28 }
29 }
30 }
實際上就是通過反射的方式将前面的各個子服務類執行個體化,并執行這些對象中的start()方法,來啟動這些服務。這樣整個SystemUI就算啟動了,上述邏輯還是比較簡單的。
我們需要注意的是,這裡使用了模闆模式。SystemUI是一個基類,其中定義了4個抽象或空方法,作為模闆指定了子類的行為模式。資源檔案中定義的衆多子服務類都是SystemUI的子類,既然都繼承自SystemUI類,那麼這些子類就有一些共同的行為模式,在某些階段應該有什麼表現,隻是具體如何表現因不同子類而異。比如說,在上述代碼中第24行和27行分别規定了SystemUI子類們在啟動時要執行start()方法,系統啟動後要執行onBootCompleted()方法,是以在這些子類中都重寫了這兩個方法,到一定的階段都會以回調的方式執行,但是具體要在這些方法中幹什麼,子類們自己說了算。這就是典型的模闆模式使用,至于具體介紹和使用模闆模式,這裡不展開講,讀者可以自行查資料,該模式在Android系統中使用還是很常見的,讀者最好能好好掌握。
到這裡為止,SystemUI的啟動流程就介紹完了,這裡歸納起來就是執行了如下幾個階段:
(1)系統啟動就緒後,SystemServer程序下達啟動SystemUIService的指令;
(2)SystemUI的SystemUiApplication中執行onCreate方法,注冊系統啟動廣播和區域設定更改廣播。
(3)SystemUI的SystemUIService中執行onCreate方法,啟動公共使用者的各項服務,各子服務執行onStart()回調方法。
(4)系統啟動後,第二步注冊的廣播會接收到系統啟動廣播,然後各個子服務執行onBootCompleted()回調方法。
(5)在切換都個人使用者時,再次加載因人而異的子服務功能。
七、SystemUI包含的功能子產品
上一節中通過數組的形式列出了SystemUI的子服務類,這些類都分别表示什麼功能呢?下面我簡單介紹其中幾項,讀者可以根據名稱來對号入座。至于更詳細的介紹,有需要的話會專門寫一篇文章來做介紹。
(1)Status bars(狀态欄)
(2)Navigation bars(導航欄)
(3)Notification(通知)
(4)Keyguard(鎖屏)
(5)Quick settings(快速設定)
(6)Recent task panel(最近任務面闆)
(7)VolumeUI(音量UI)
(8)Screenshot(截屏)
(9)PowerUI(電量UI)
(10)RingtonePlayer(鈴聲播放器)
(11)StackDivider(分屏)
(12)PipUI(畫中畫UI)
(13)Biometrics(生物識别解鎖功能,如指紋解鎖、人臉解鎖、虹膜解鎖等)
結語
SystemUI的啟動流程就介紹到這裡,由于講得還算比較詳細,是以涉及的内容及細節不少,一定會有些描述不準确或者不妥的地方,如果發現,請讀者不吝賜教。另外由于篇幅有限,有些地方還是僅提到或者簡單介紹而已,比如FBE加密,模闆模式,Lambda表達式等,在平時系統開發中都會經常碰到,讀者都可以繼續拓展深入學習。