Android 4.0 ICS SystemUI淺析——SystemUI啟動流程

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

       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介紹請參考這篇文章http://developer.android.com/design/get-started/ui-overview.html#home-all-apps-recents。

       根據上面的介紹,我想大傢應該知道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]
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
        package="com.android.systemui" 
        coreApp="true" 
        android:sharedUserId="android.uid.system" 
        android:process="system" 
        > 
 
    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> 
    <uses-permission android:name="android.permission.BLUETOOTH" /> 
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 
    <uses-permission android:name="android.permission.GET_TASKS" /> 
    <uses-permission android:name="android.permission.MANAGE_USB" /> 
 
    <application 
        android:persistent="true" 
        android:allowClearUserData="false" 
        android:allowBackup="false" 
        android:hardwareAccelerated="true" 
        android:label="@string/app_label" 
        android:icon="@drawable/ic_launcher_settings"> 
 
        <!– Broadcast receiver that gets the broadcast at boot time and starts 
             up everything else. 
             TODO: Should have an android:permission attribute 
             –> 
        <service android:name="SystemUIService" 
            android:exported="true" 
            /> 
 
        <!– started from PhoneWindowManager 
             TODO: Should have an android:permission attribute –> 
        <service android:name=".screenshot.TakeScreenshotService" 
            android:process=":screenshot" 
            android:exported="false" /> 
 
        <service android:name=".LoadAverageService" 
                android:exported="true" /> 
 
        <service android:name=".ImageWallpaper" 
                android:permission="android.permission.BIND_WALLPAPER" 
                android:exported="true" /> 
 
        <receiver android:name=".BootReceiver" > 
            <intent-filter> 
                <action android:name="android.intent.action.BOOT_COMPLETED" /> 
            </intent-filter> 
        </receiver> 
        … … 
    </application> 
</manifest> 

 

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

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

[java]
static final void startSystemUi(Context context) { 
    Intent intent = new Intent(); 
    intent.setComponent(new ComponentName("com.android.systemui", 
                "com.android.systemui.SystemUIService")); 
    Slog.d(TAG, "Starting service: " + intent); 
    context.startService(intent); 

這裡的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]
public void onCreate() { 
    // Pick status bar or system bar. 
    IWindowManager wm = IWindowManager.Stub.asInterface( 
            ServiceManager.getService(Context.WINDOW_SERVICE)); 
    try { 
        SERVICES[0] = wm.canStatusBarHide()//根據wm.canStatusBarHide()判斷設備類型 
                ? R.string.config_statusBarComponent 
                : R.string.config_systemBarComponent; 
    } catch (RemoteException e) { 
        Slog.w(TAG, "Failing checking whether status bar can hide", e); 
    } 
 
    final int N = SERVICES.length; 
    mServices = new SystemUI[N]; 
    for (int i=0; i<N; i++) { 
        Class cl = chooseClass(SERVICES[i]); 
        Slog.d(TAG, "loading: " + cl); 
        try { 
            mServices[i] = (SystemUI)cl.newInstance(); 
        } catch (IllegalAccessException ex) { 
            throw new RuntimeException(ex); 
        } catch (InstantiationException ex) { 
            throw new RuntimeException(ex); 
        } 
        mServices[i].mContext = this; 
        Slog.d(TAG, "running: " + mServices[i]); 
        mServices[i].start(); 
    } 

在這段代碼中,通過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]
public boolean canStatusBarHide() { 
    return mPolicy.canStatusBarHide(); 

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

[java]
public boolean canStatusBarHide() { 
    return mStatusBarCanHide; 

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

[java]
// Determine whether the status bar can hide based on the size 
// of the screen.  We assume sizes > 600dp are tablets where we 
// will use the system bar. 
int shortSizeDp = shortSize 
        * DisplayMetrics.DENSITY_DEFAULT 
        / DisplayMetrics.DENSITY_DEVICE; 
mStatusBarCanHide = shortSizeDp < 600; 
mStatusBarHeight = mContext.getResources().getDimensionPixelSize( 
        mStatusBarCanHide 
        ? com.android.internal.R.dimen.status_bar_height 
        : com.android.internal.R.dimen.system_bar_height); 
 
mHasNavigationBar = mContext.getResources().getBoolean( 
        com.android.internal.R.bool.config_showNavigationBar); 

這裡通過shortSizeDp來判斷當前設備的類型,如果當前屏幕的shortSizeDp<600dp,則系統會認為該設備是Phone反之則認為是Tablet。根據mStatusBarCanHide的值,設定StatusBar或者SystemBar(CombinedBar)的高度,以及是否顯示NavigationBar。
        繼續回到我們的SystemUIService.java的onCreate()方法中,根據前面對canStatusBarHide()的判斷,SERVICE[0]中將存放R.string.config_statusBarComponent或者R.string.config_systemBarComponent。它們的值具體是:

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

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

[java]
final int N = SERVICES.length; 
mServices = new SystemUI[N]; 
for (int i=0; i<N; i++) { 
    Class cl = chooseClass(SERVICES[i]); 
    Slog.d(TAG, "loading: " + cl); 
    try { 
        mServices[i] = (SystemUI)cl.newInstance(); 
    } catch (IllegalAccessException ex) { 
        throw new RuntimeException(ex); 
    } catch (InstantiationException ex) { 
        throw new RuntimeException(ex); 
    } 
    mServices[i].mContext = this; 
    Slog.d(TAG, "running: " + mServices[i]); 
    mServices[i].start(); 

這些方法會分別啟動兩個方法,這兩個方法可以從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]
@Override 
public void start() { 
    mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 
            .getDefaultDisplay(); 
 
    mWindowManager = IWindowManager.Stub.asInterface( 
            ServiceManager.getService(Context.WINDOW_SERVICE)); 
 
    super.start(); // calls makeStatusBarView() 
 
    addNavigationBar(); 
 
    //addIntruderView(); 
 
    // Lastly, call to the icon policy to install/update all the icons. 
    mIconPolicy = new PhoneStatusBarPolicy(mContext); 

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

[java]
public void start() { 
    // First set up our views and stuff. 
    View sb = makeStatusBarView(); 
 
    // Connect in to the status bar manager service 
    StatusBarIconList iconList = new StatusBarIconList(); 
    ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 
    ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 
    mCommandQueue = new CommandQueue(this, iconList); 
    mBarService = IStatusBarService.Stub.asInterface( 
            ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 
    int[] switches = new int[7]; 
    ArrayList<IBinder> binders = new ArrayList<IBinder>(); 
    try { 
        mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 
                switches, binders); 
    } catch (RemoteException ex) { 
        // If the system process isn't there we're doomed anyway. 
    } 
 
    disable(switches[0]); 
    setSystemUiVisibility(switches[1]); 
    topAppWindowChanged(switches[2] != 0); 
    // StatusBarManagerService has a back up of IME token and it's restored here. 
    setImeWindowStatus(binders.get(0), switches[3], switches[4]); 
    setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 
 
    // Set up the initial icon state 
    int N = iconList.size(); 
    int viewIndex = 0; 
    for (int i=0; i<N; i++) { 
        StatusBarIcon icon = iconList.getIcon(i); 
        if (icon != null) { 
            addIcon(iconList.getSlot(i), i, viewIndex, icon); 
            viewIndex++; 
        } 
    } 
 
    // Set up the initial notification state 
    N = notificationKeys.size(); 
    if (N == notifications.size()) { 
        for (int i=0; i<N; i++) { 
            addNotification(notificationKeys.get(i), notifications.get(i)); 
        } 
    } else { 
        Log.wtf(TAG, "Notification list length mismatch: keys=" + N 
                + " notifications=" + notifications.size()); 
    } 
 
    // Put up the view 
    final int height = getStatusBarHeight(); 
 
    final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 
            ViewGroup.LayoutParams.MATCH_PARENT, 
            height, 
            WindowManager.LayoutParams.TYPE_STATUS_BAR, 
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 
                | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 
                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 
            // We use a pixel format of RGB565 for the status bar to save memory bandwidth and 
            // to ensure that the layer can be handled by HWComposer.  On some devices the 
            // HWComposer is unable to handle SW-rendered RGBX_8888 layers. 
            PixelFormat.RGB_565); 
     
    // the status bar should be in an overlay if possible 
    final Display defaultDisplay  
        = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 
            .getDefaultDisplay(); 
 
    // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags.  The status bar occupies 
    // very little screen real-estate and is updated fairly frequently.  By using CPU rendering 
    // for the status bar, we prevent the GPU from having to wake up just to do these small 
    // updates, which should help keep power consumption down. 
 
    lp.gravity = getStatusBarGravity(); 
    lp.setTitle("StatusBar"); 
    lp.packageName = mContext.getPackageName(); 
    lp.windowAnimations = R.style.Animation_StatusBar; 
    WindowManagerImpl.getDefault().addView(sb, lp); 
 
    if (SPEW) { 
        Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)  
               + " icons=" + iconList.size() 
               + " disabled=0x" + Integer.toHexString(switches[0]) 
               + " lights=" + switches[1] 
               + " menu=" + switches[2] 
               + " imeButton=" + switches[3] 
               ); 
    } 
 
    mDoNotDisturb = new DoNotDisturb(mContext); 

在這裡,完成瞭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來畫時序圖,這也是一個學習的過程。

摘自 yihongyuelan
 

發佈留言