Android 4.0 SystemUI淺析——SystemUI啓動流程

  閱讀Android 4.0源碼也有一段時間了,這次是針對SystemUI的一個學習過程。本文只是對SystemUI分析的一個開始——啓動流程的分析,網上有很多關於2.3的SystemUI的分析,可4.0與2.3的差別還是很大的,爲了給自己留下筆記同時也方便大家學習和探討,遂寫此文,後續將有更多關於SystemUI的分析,敬請關注。

       轉載請註明出處:http://blog.csdn.net/yihongyuelan

       1.初始SystemUI

       什麼是SystemUI?你或許會覺得這個問題很幼稚,界面上的佈局UI顯示?系統的UI?如果你是這麼想的,那麼就大錯特錯了。我們知道Android 4.0 ICS同時適用於Phone和Tablet(TV),因此,對於Phone來說SystemUI指的是:StatusBar(狀態欄)、NavigationBar(導航欄)。而對於Tablet或者是TV來說SystemUI指的是:CombinedBar(包括了StatusBar和NavigationBar)。注:關於Android 4.0的UI介紹請參考這篇文章

       根據上面的介紹,我想大家應該知道SystemUI的具體作用了吧!也就是說我們的Phone的信號,藍牙標誌,Wifi標誌等等這些狀態顯示標誌都會在StatusBar上顯示。當我們的設備開機後,首先需要給用戶呈現的就是各種界面同時也包括了我們的SystemUI,因此對於整個Android系統來說,SystemUI都有舉足輕重的作用,那接下來就來看看它的啓動流程吧!

       2.啓動流程

       這裏只是單單的分析啓動流程,實際上SystemUI啓動過程中涉及到很多東西的調用,這裏暫時不分支去介紹,後續會有相關文章的詳細分析。那麼對於這種分析我還是將自己的分析思路寫出來,而不是直接展現已經分析好的結果,當然結果會在最後展示出來。這樣做一方面有利於鍛鍊自己的分析能力,另一方面各位看官也可以找出分析中的利與弊從而更好的取捨。

       首先來看看SystemUI的代碼位置,路徑:SourceCode/frameworks/base/packages/SystemUI;其次看看它的代碼梗概:

圖 2.1

      在Android 4.0中,Google整合了Phone和Tablet(TV)的SystemUI,也就說可以根據設備的類型自動匹配相應的SystemUI。這一點是在Android 2.3中是沒有的。那麼接下來怎麼分析呢?打開AndroidManifest.xml可以看到:

[html] view plaincopy
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.         package="com.android.systemui"  
  3.         coreApp="true"  
  4.         android:sharedUserId="android.uid.system"  
  5.         android:process="system"  
  6.         >  
  7.   
  8.     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />  
  9.     <uses-permission android:name="android.permission.BLUETOOTH" />  
  10.     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />  
  11.     <uses-permission android:name="android.permission.GET_TASKS" />  
  12.     <uses-permission android:name="android.permission.MANAGE_USB" />  
  13.   
  14.     <application  
  15.         android:persistent="true"  
  16.         android:allowClearUserData="false"  
  17.         android:allowBackup="false"  
  18.         android:hardwareAccelerated="true"  
  19.         android:label="@string/app_label"  
  20.         android:icon="@drawable/ic_launcher_settings">  
  21.   
  22.         <!-- Broadcast receiver that gets the broadcast at boot time and starts  
  23.              up everything else.  
  24.              TODO: Should have an android:permission attribute  
  25.              -->  
  26.         <service android:name="SystemUIService"  
  27.             android:exported="true"  
  28.             />  
  29.   
  30.         <!-- started from PhoneWindowManager  
  31.              TODO: Should have an android:permission attribute -->  
  32.         <service android:name=".screenshot.TakeScreenshotService"  
  33.             android:process=":screenshot"  
  34.             android:exported="false" />  
  35.   
  36.         <service android:name=".LoadAverageService"  
  37.                 android:exported="true" />  
  38.   
  39.         <service android:name=".ImageWallpaper"  
  40.                 android:permission="android.permission.BIND_WALLPAPER"  
  41.                 android:exported="true" />  
  42.   
  43.         <receiver android:name=".BootReceiver" >  
  44.             <intent-filter>  
  45.                 <action android:name="android.intent.action.BOOT_COMPLETED" />  
  46.             </intent-filter>  
  47.         </receiver>  
  48.         ... ...  
  49.     </application>  
  50. </manifest>  


 

       根據以上代碼我們可以發現這其中註冊了很多Service,同時也包括了廣播。但這裏我們只關注SystemUIService,這纔是本文的主旨啊。那麼首先要找到SystemUIService是如何啓動的。對於Service的啓動,在我以前的博文中已有提到,這裏就不多說了,不外乎startService(intent)和bindService(intent),它們都是以intent爲對象,那intent的聲明也需要SystemUIService啊,因此我們可以據此搜索關鍵詞"SystemUIService"。

       經過漫長的搜索和比對之後發現,原來,SystemUIService是在SystemServer.java中被啓動的,如下所示:

[java] view plaincopy
  1. static final void startSystemUi(Context context) {  
  2.     Intent intent = new Intent();  
  3.     intent.setComponent(new ComponentName("com.android.systemui",  
  4.                 "com.android.systemui.SystemUIService"));  
  5.     Slog.d(TAG, "Starting service: " + intent);  
  6.     context.startService(intent);  
  7. }  

這裏的startSystemUi()方法則在ServerThread的run()方法中被調用。這裏提到SystemServer就不得不提及Android的啓動流程,這裏不會展開詳細討論具體的流程,只是簡單的介紹一下大概流程,用以表明SystemServer所處的位置。

        Android的啓動分爲內核啓動、Android啓動、launcher啓動,我們的SystemServer就處於Android啓動中,以下是大致流程圖:

                                                            init->ServiceManager->Zygote->SystemServer->... ...

在SystemServer中,初始化了Android系統中的Java層服務,如PowerManagerService、WindowManagerService等等,當然也包括了SystemUIService,它們通過ServiceManager的addService()方法,添加到ServiceManager的管理中。實際上,根據後面的分析這裏add了一個很重要的StatusBarManagerService。這個Service在後面會用到的。

       既然到這裏SystemUIService已經啓動,那麼我們就繼續跟蹤該Service吧。

       1).首先查看其onCreate()方法,如下:

[java] view plaincopy
  1. public void onCreate() {  
  2.     // Pick status bar or system bar.  
  3.     IWindowManager wm = IWindowManager.Stub.asInterface(  
  4.             ServiceManager.getService(Context.WINDOW_SERVICE));  
  5.     try {  
  6.         SERVICES[0] = wm.canStatusBarHide()//根據wm.canStatusBarHide()判斷設備類型  
  7.                 ? R.string.config_statusBarComponent  
  8.                 : R.string.config_systemBarComponent;  
  9.     } catch (RemoteException e) {  
  10.         Slog.w(TAG, "Failing checking whether status bar can hide", e);  
  11.     }  
  12.   
  13.     final int N = SERVICES.length;  
  14.     mServices = new SystemUI[N];  
  15.     for (int i=0; i<N; i++) {  
  16.         Class cl = chooseClass(SERVICES[i]);  
  17.         Slog.d(TAG, "loading: " + cl);  
  18.         try {  
  19.             mServices[i] = (SystemUI)cl.newInstance();  
  20.         } catch (IllegalAccessException ex) {  
  21.             throw new RuntimeException(ex);  
  22.         } catch (InstantiationException ex) {  
  23.             throw new RuntimeException(ex);  
  24.         }  
  25.         mServices[i].mContext = this;  
  26.         Slog.d(TAG, "running: " + mServices[i]);  
  27.         mServices[i].start();  
  28.     }  
  29. }  

在這段代碼中,通過AIDL的方式獲取了WindowManager的對象wm,並調用其方法canStatusBarHide()來判斷當前設備的類型,也就是說如果我們使用的Phone那麼後續就會加載StatusBar和NivagationBar;而如果我們設備類型是Tablet(TV)之類的(可以在配置文檔裏面配置),
就會加載CombiedBar。

        這裏的canStatusBarHide()方法的具體實現是在:frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java。爲什麼會是這裏呢?我們在Eclipse中導入源碼之後,找到SystemUIService.java中的wm.canStatusBarHide()方法,通過open Implementation直接跳轉到WindowsManagerService中:

[java] view plaincopy
  1. public boolean canStatusBarHide() {  
  2.     return mPolicy.canStatusBarHide();  
  3. }  

但這裏我們發現canStatusBarHide()實際上是WindowManagerPolicy的對象調用的方法,而WindowManagerPolicy只是一個接口類,根據以往分析的經驗可以知道,這裏的WindowManagerPolicy對象所調用的canStatusBartHide()方法一定是其實現類中的
方法。因此,繼續通過open Implementation跳轉,來到了PhoneWindownManager中:

[java] view plaincopy
  1. public boolean canStatusBarHide() {  
  2.     return mStatusBarCanHide;  
  3. }  

繼續查看mSatuBarCanHide的實現,如下所示:

[java] view plaincopy
  1. // Determine whether the status bar can hide based on the size  
  2. // of the screen.  We assume sizes > 600dp are tablets where we  
  3. // will use the system bar.  
  4. int shortSizeDp = shortSize  
  5.         * DisplayMetrics.DENSITY_DEFAULT  
  6.         / DisplayMetrics.DENSITY_DEVICE;  
  7. mStatusBarCanHide = shortSizeDp < 600;  
  8. mStatusBarHeight = mContext.getResources().getDimensionPixelSize(  
  9.         mStatusBarCanHide  
  10.         ? com.android.internal.R.dimen.status_bar_height  
  11.         : com.android.internal.R.dimen.system_bar_height);  
  12.   
  13. mHasNavigationBar = mContext.getResources().getBoolean(  
  14.         com.android.internal.R.bool.config_showNavigationBar);  

這裏通過shortSizeDp來判斷當前設備的類型,如果當前屏幕的shortSize
Dp<600dp,則系統會認爲該設備是Phone反之則認爲是Tablet。根據mStatusBarCanHide的值,設定StatusBar或者SystemBar(CombinedBar)的高度,以及是否顯示NavigationBar。

        繼續回到我們的SystemUIService.java的onCreate()方法中,根據前面對canStatusBarHide()的判斷,SERVICE[0]中將存放R.string.config_statusBarComponent或者R.string.config_systemBarComponent。它們的值具體是:

[html] view plaincopy
  1. <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>  
  2. <string name="config_systemBarComponent" translatable="false">com.android.systemui.statusbar.tablet.TabletStatusBar</string>  

因爲我的測試設備是Phone,那麼現在SERVICE[0]中存放的就是com.android.systemui.statusbart.phone.PhoneStatusBar。查看以下代碼:

[java] view plaincopy
  1. final int N = SERVICES.length;  
  2. mServices = new SystemUI[N];  
  3. for (int i=0; i<N; i++) {  
  4.     Class cl = chooseClass(SERVICES[i]);  
  5.     Slog.d(TAG, "loading: " + cl);  
  6.     try {  
  7.         mServices[i] = (SystemUI)cl.newInstance();  
  8.     } catch (IllegalAccessException ex) {  
  9.         throw new RuntimeException(ex);  
  10.     } catch (InstantiationException ex) {  
  11.         throw new RuntimeException(ex);  
  12.     }  
  13.     mServices[i].mContext = this;  
  14.     Slog.d(TAG, "running: " + mServices[i]);  
  15.     mServices[i].start();  
  16. }  

這些方法會分別啓動兩個方法,這兩個方法可以從log中知道,分別是PhoneStatusBar.start()和PowerUI.start()。而我們的目的是要弄清SystemUI的啓動,因此現關注PhoneStatusBar.start()方法。

log信息:

06-04 13:23:15.379: DEBUG/SystemUIService(396): loading: class com.android.systemui.statusbar.phone.PhoneStatusBar

06-04 13:23:16.739: DEBUG/SystemUIService(396): loading: class com.android.systemui.power.PowerUI

       來到PhoneStatusBar.start()方法中,位於:SourceCode/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java,代碼如下:

[java] view plaincopy
  1. @Override  
  2. public void start() {  
  3.     mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))  
  4.             .getDefaultDisplay();  
  5.   
  6.     mWindowManager = IWindowManager.Stub.asInterface(  
  7.             ServiceManager.getService(Context.WINDOW_SERVICE));  
  8.   
  9.     super.start(); // calls makeStatusBarView()  
  10.   
  11.     addNavigationBar();  
  12.   
  13.     //addIntruderView();  
  14.   
  15.     // Lastly, call to the icon policy to install/update all the icons.  
  16.     mIconPolicy = new PhoneStatusBarPolicy(mContext);  
  17. }  

這裏的重心主要是在super.start()和addNavigationBar()
上。目前市面上很多手機已經刷入了ICS,但是大多數是沒有NavigationBar的,也就是說自己修改了源碼,屏蔽了NavigationBar。繼續跟蹤super.start()方法,來到/SourceCode/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java的start()方法中,代碼如下:

[java] view plaincopy
  1. public void start() {  
  2.     // First set up our views and stuff.  
  3.     View sb = makeStatusBarView();  
  4.   
  5.     // Connect in to the status bar manager service  
  6.     StatusBarIconList iconList = new StatusBarIconList();  
  7.     ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();  
  8.     ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();  
  9.     mCommandQueue = new CommandQueue(this, iconList);  
  10.     mBarService = IStatusBarService.Stub.asInterface(  
  11.             ServiceManager.getService(Context.STATUS_BAR_SERVICE));  
  12.     int[] switches = new int[7];  
  13.     ArrayList<IBinder> binders = new ArrayList<IBinder>();  
  14.     try {  
  15.         mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,  
  16.                 switches, binders);  
  17.     } catch (RemoteException ex) {  
  18.         // If the system process isn't there we're doomed anyway.  
  19.     }  
  20.   
  21.     disable(switches[0]);  
  22.     setSystemUiVisibility(switches[1]);  
  23.     topAppWindowChanged(switches[2] != 0);  
  24.     // StatusBarManagerService has a back up of IME token and it's restored here.  
  25.     setImeWindowStatus(binders.get(0), switches[3], switches[4]);  
  26.     setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);  
  27.   
  28.     // Set up the initial icon state  
  29.     int N = iconList.size();  
  30.     int viewIndex = 0;  
  31.     for (int i=0; i<N; i++) {  
  32.         StatusBarIcon icon = iconList.getIcon(i);  
  33.         if (icon != null) {  
  34.             addIcon(iconList.getSlot(i), i, viewIndex, icon);  
  35.             viewIndex++;  
  36.         }  
  37.     }  
  38.   
  39.     // Set up the initial notification state  
  40.     N = notificationKeys.size();  
  41.     if (N == notifications.size()) {  
  42.         for (int i=0; i<N; i++) {  
  43.             addNotification(notificationKeys.get(i), notifications.get(i));  
  44.         }  
  45.     } else {  
  46.         Log.wtf(TAG, "Notification list length mismatch: keys=" + N  
  47.                 + " notifications=" + notifications.size());  
  48.     }  
  49.   
  50.     // Put up the view  
  51.     final int height = getStatusBarHeight();  
  52.   
  53.     final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(  
  54.             ViewGroup.LayoutParams.MATCH_PARENT,  
  55.             height,  
  56.             WindowManager.LayoutParams.TYPE_STATUS_BAR,  
  57.             WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
  58.                 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING  
  59.                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,  
  60.             // We use a pixel format of RGB565 for the status bar to save memory bandwidth and  
  61.             // to ensure that the layer can be handled by HWComposer.  On some devices the  
  62.             // HWComposer is unable to handle SW-rendered RGBX_8888 layers.  
  63.             PixelFormat.RGB_565);  
  64.       
  65.     // the status bar should be in an overlay if possible  
  66.     final Display defaultDisplay   
  67.         = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))  
  68.             .getDefaultDisplay();  
  69.   
  70.     // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags.  The status bar occupies  
  71.     // very little screen real-estate and is updated fairly frequently.  By using CPU rendering  
  72.     // for the status bar, we prevent the GPU from having to wake up just to do these small  
  73.     // updates, which should help keep power consumption down.  
  74.   
  75.     lp.gravity = getStatusBarGravity();  
  76.     lp.setTitle("StatusBar");  
  77.     lp.packageName = mContext.getPackageName();  
  78.     lp.windowAnimations = R.style.Animation_StatusBar;  
  79.     WindowManagerImpl.getDefault().addView(sb, lp);  
  80.   
  81.     if (SPEW) {  
  82.         Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)   
  83.                + " icons=" + iconList.size()  
  84.                + " disabled=0x" + Integer.toHexString(switches[0])  
  85.                + " lights=" + switches[1]  
  86.                + " menu=" + switches[2]  
  87.                + " imeButton=" + switches[3]  
  88.                );  
  89.     }  
  90.   
  91.     mDoNotDisturb = new DoNotDisturb(mContext);  
  92. }  

在這裏,完成了SystemUI的整個初始化以及設置過程,並最終呈現到界面上。在StatusBar中的start()方法主要完成了以下幾個工作:首先獲取需要在StatusBar上顯示的各種icons。然後初始化一些屬性。最後通過WindowManager的addView方法將StatusBar顯示出來。分析到這裏可能有人會問了,明明說分析的是SystemUI的嘛,怎麼最後變成StatusBar了呢?如果你硬要說我跑題那我也沒有辦法,回過頭去看看addNavigationBar(),你會發現和StatusBar的加載幾乎一致,因此沒必要再詳述了。
如果細心閱讀了的朋友肯定會發現這句代碼:

mBarService = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));

這不正是我們前面add的StatusBarManagerSerivce嗎?這裏通過AIDL的方式來獲取它的對象。

        整個代碼執行的時序圖如圖2.2所示:

圖 2.2

        3.總結

        Android 4.0的SystemUI加載啓動的過程大致就是這樣,雖然看似簡單,但這僅僅是個開始,master還是後面呢!!各家廠商根據自家的需求,需要定製SystemUI或者美化SystemUI,不同的平臺(QCOM、MTK等等)也會有不同的修改,但大體框架是沒有變的,無非是在原有基礎上的修修改改或者增加一些自己的類等等。通過對Android源碼框架性的理解,可以學習到很多設計上的知識(雖然自己還很欠缺)。通過這次分析,開始逐漸用StarUML來畫時序圖,這也是一個學習的過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章