1. 程式人生 > >【系統解讀】SystemUI篇(一)SystemUI啟動流程

【系統解讀】SystemUI篇(一)SystemUI啟動流程

前言

       SystemUI是系統啟動中第一個使用者肉眼可見的應用,其功能包羅永珍,比如開機後看到的鎖屏介面,充電時充電介面,狀態列,導航欄,多工欄等,都是與Android手機使用者息息相關的功能。所以不止SystemUI開發者,普通的應用開發者也很有必要去了解一下SystemUI。本系列文章會基於Android P和Android Q來介紹SystemUI的各個方面,本篇作為本系列第一篇,主要介紹了SystemUI的啟動流程,以及主要功能簡介。

 

一、SystemUI簡介

      SystemUI,顧名思義是系統為使用者提供的系統級別的資訊顯示與互動的一套UI元件,所以其功能包羅永珍。比如鎖屏、狀態列、底部導航欄、最近使用App列表等,大部分功能相互獨立,按需啟動,後文會繼續列出更多功能。在系統原始碼中,其位置為:frameworks/base/package/SystemUI。儘管從表現形式上看,SystemUI和普通的Android APP有較大的差別,但其本質和普通APP並沒有什麼差別,也是以apk的形式存在,如下圖所示:

 

 以當前測試機為例,其就預置在系統指定的目錄下。也是通過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(new Runnable(){
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表示式等,在平時系統開發中都會經常碰到,讀者都可以繼續拓展深入學