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介紹請參考這篇文章
根據上面的介紹,我想大家應該知道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來畫時序圖,這也是一個學習的過程。