PS:已同步至我的部落格 點選開啟
一、SystemUI 是什麼
首先SystemUI 是一個系統應用,apk路徑位於/system/priv-app
原始碼路徑位於:/framework/base/packages/SystemUI
它負責的功能如下:
- 狀態列資訊的展示:比如電量資訊,時間,wifi狀態等
- 通知欄訊息
- 桌布管理
- 截圖功能
- 近期工作列顯示,比如長按home鍵顯示最近使用的app
- 錄製螢幕功能
- 截圖服務
以下是7.0 SystemUI 的程式碼截圖
二、SystemUI 的啟動
SystemUI 是在SystemServer裡的AMS例項的systemReady方法裡呼叫startSystemUi方法啟動
SystemServer路徑:/base/services/java/com/android/server/SystemServer.java
mActivityManagerService.systemReady(new Runnable() {
......
static final void startSystemUi(Context context) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
}
......複製程式碼
在這個方法裡啟動一個SystemUIService服務.
public class SystemUIService extends Service {
@Override
public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
}
......複製程式碼
在onCreate方法中會呼叫SystemUIApplication的startServicesIfNeeded方法,這個方法會呼叫 startServicesIfNeeded(SERVICES)方法啟動一系列服務(並不是真正的服務,都繼承自SystemUI),可以這麼說SystemUI就是一個容器,裡面裝有負責不同功能的模組。
public class SystemUIApplication extends Application {
......
private final Class<?>[] SERVICES = new Class[] {
com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
Divider.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
com.android.systemui.tv.pip.PipUI.class,
com.android.systemui.shortcut.ShortcutKeyDispatcher.class
};
public void startServicesIfNeeded() {
startServicesIfNeeded(SERVICES);
}
......複製程式碼
startServicesIfNeeded方法會遍歷services這個陣列,依次呼叫service的start方法啟動服務
private void startServicesIfNeeded(Class<?>[] services) {
//如果已經啟動了就返回
if (mServicesStarted) {
return;
}
//如果沒啟動完成完成
if (!mBootCompleted) {
if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
mBootCompleted = true;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
}
}
final int N = services.length;
for (int i=0; i<N; i++) {
Class<?> cl = services[i];
if (DEBUG) Log.d(TAG, "loading: " + cl);
try {
Object newService = SystemUIFactory.getInstance().createInstance(cl);
mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
//啟動服務
mServices[i].start();
//如果啟動完成了
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
mServicesStarted = true;
}複製程式碼
這裡以com.android.systemui.statusbar.SystemBars.class為例,講解一下
三、狀態列和導航欄 的啟動
SystemBars的start方法會建立一個ServiceMonitor(服務監聽者),會進入到ServiceMonitor的start方法
public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
......
public void start() {
// ServiceMonitor是服務監聽者
mServiceMonitor = new ServiceMonitor(TAG, DEBUG, mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
mServiceMonitor.start(); // will call onNoService if no remote service is found
}
......
}複製程式碼
在ServiceMonitor的start方法啟動
public class ServiceMonitor {
......
public void start() {
......
mHandler.sendEmptyMessage(MSG_START_SERVICE);
}
......
}複製程式碼
在Handler裡處理這個MSG_START_SERVICE
public class ServiceMonitor {
......
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_START_SERVICE:
//啟動服務
startService();
break;
case MSG_CONTINUE_START_SERVICE:
continueStartService();
break;
case MSG_STOP_SERVICE:
stopService();
break;
case MSG_PACKAGE_INTENT:
packageIntent((Intent)msg.obj);
break;
case MSG_CHECK_BOUND:
checkBound();
break;
case MSG_SERVICE_DISCONNECTED:
serviceDisconnected((ComponentName)msg.obj);
break;
}
}
};
......
}複製程式碼
startService方法如下
public class ServiceMonitor {
......
private void startService() {
//獲取服務元件名稱
mServiceName = getComponentNameFromSetting();
//如果為空,回撥服務的onNoService方法
if (mServiceName == null) {
mBound = false;
mCallbacks.onNoService();
} else {
//不為空,回撥服務的的onServiceStartAttempt方法
long delay = mCallbacks.onServiceStartAttempt();
mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
}
}
......
}複製程式碼
這裡對mServiceName是否為空進行判斷,總之無論如何它最終都會啟動這個服務。
回撥SystemBars的onNoService裡建立StatusBar
public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
......
@Override
public void onNoService() {
if (DEBUG) Log.d(TAG, "onNoService");
//建立StatusBar
createStatusBarFromConfig(); // fallback to using an in-process implementation
}
......複製程式碼
private void createStatusBarFromConfig() {
//config_statusBarComponent就是PhoneStatusBar
final String clsName = mContext.getString(R.string.config_statusBarComponent);
Class<?> cls = null;
try {
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
throw andLog("Error loading status bar component: " + clsName, t);
}
try {
//建立BaseStatusBar例項
mStatusBar = (BaseStatusBar) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
//啟動
mStatusBar.start();
}複製程式碼
在createStatusBarFromConfig方法裡會獲取一個config_statusBarComponent的字串值,這個值就是PhoneStatusBar的clasName
所以這裡的mStatusBar是PhoneStatusBar例項,啟動了PhoneStatusBar
PhoneStatusBar的start方法
public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
......
public void start() {
......
//呼叫父類的start方法,在父類BaseStatusBar裡呼叫createAndAddWindows方法
// 3.1
super.start(); // calls createAndAddWindows()
......
//新增導航欄
// 3.2
addNavigationBar();
......
}複製程式碼
它會回撥父類BaseStatusBar 的start方法
3.1、 super.start()
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
ExpandableNotificationRow.OnExpandClickListener,
OnGutsClosedListener {
......
public void start() {
......
/*例項化IStatusBarService,
隨後BaseStatusBar將自己註冊到IStatusBarService之中。以此宣告本例項才是狀態列的真正
實現者,IStatusBarService會將其所接受到的請求轉發給本例項。
IStatusBarService會儲存SystemUi的狀態資訊,避免SystemUi崩潰而造成資訊的丟失
*/
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
......
//IStatusBarService與BaseStatusBar進行通訊的橋樑。
mCommandQueue = new CommandQueue(this);
/*switches則儲存了一些雜項:禁用功能列表,SystemUIVisiblity,是否在導航欄中顯示虛擬的
選單鍵,輸入法視窗是否可見、輸入法視窗是否消費BACK鍵、是否接入了實體鍵盤、實體鍵盤是否被啟用。
*/
int[] switches = new int[9];
ArrayList<IBinder> binders = new ArrayList<IBinder>();
/*它儲存了用於顯示在狀態列的系統狀態
區中的狀態圖示列表。在完成註冊之後,IStatusBarService將會在其中填充兩個陣列,一個字串
陣列用於表示狀態的名稱,一個StatusBarIcon型別的陣列用於儲存需要顯示的圖示資源。
*/
ArrayList<String> iconSlots = new ArrayList<>();
ArrayList<StatusBarIcon> icons = new ArrayList<>();
Rect fullscreenStackBounds = new Rect();
Rect dockedStackBounds = new Rect();
//IStatusBarService註冊一些資訊
try {
mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
fullscreenStackBounds, dockedStackBounds);
} catch (RemoteException ex) {
}
//建立狀態列視窗
createAndAddWindows();
......
}
......
}複製程式碼
BaseStatusBar進行一些設定,獲取了IStatusBarService例項並註冊一些資訊到IStatusBarService中,IStatusBarService是一個系統服務,BaseStatusBar將自己註冊到IStatusBarService之中,IStatusBarService會把操作狀態列和導航欄的請求轉發給BaseStatusBar
為了保證SystemUI意外退出後不會發生資訊丟失,IStatusBarService儲存了所有需要狀態列與導航欄進行顯示或處理的資訊副本。 在註冊時將一個繼承自IStatusBar.Stub的CommandQueue的例項註冊到IStatusBarService以建立通訊,並將資訊副本取回。
public class CommandQueue extends IStatusBar.Stub {複製程式碼
IStatusBarService的真身是StatusBarManagerService
路徑:./services/core/java/com/android/server/statusbar/StatusBarManagerService.java
它的註冊方法做一些資料的初始化
public class StatusBarManagerService extends IStatusBarService.Stub {
......
public void registerStatusBar(IStatusBar bar, List<String> iconSlots,
List<StatusBarIcon> iconList, int switches[], List<IBinder> binders,
Rect fullscreenStackBounds, Rect dockedStackBounds) {
//檢查許可權
enforceStatusBarService();
mBar = bar;
synchronized (mIcons) {
for (String slot : mIcons.keySet()) {
iconSlots.add(slot);
iconList.add(mIcons.get(slot));
}
}
synchronized (mLock) {
switches[0] = gatherDisableActionsLocked(mCurrentUserId, 1);
switches[1] = mSystemUiVisibility;
switches[2] = mMenuVisible ? 1 : 0;
switches[3] = mImeWindowVis;
switches[4] = mImeBackDisposition;
switches[5] = mShowImeSwitcher ? 1 : 0;
switches[6] = gatherDisableActionsLocked(mCurrentUserId, 2);
switches[7] = mFullscreenStackSysUiVisibility;
switches[8] = mDockedStackSysUiVisibility;
binders.add(mImeToken);
fullscreenStackBounds.set(mFullscreenStackBounds);
dockedStackBounds.set(mDockedStackBounds);
}
}
......
}複製程式碼
這幾者的關係如下
回到PhoneStatusBar中, 父類BaseStatusBar中的createAndAddWindows為抽象方法,由子類實現,看下PhoneStatusBar的
createAndAddWindows
@Override
public void createAndAddWindows() {
//新增狀態列視窗
addStatusBarWindow();
}複製程式碼
方法實現如下
private void addStatusBarWindow() {
//建立控制元件
makeStatusBarView();
//建立StatusBarWindowManager例項
mStatusBarWindowManager = new StatusBarWindowManager(mContext);
//建立遠端輸入控制例項
mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
mHeadsUpManager);
//新增狀態列視窗
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}複製程式碼
看下makeStatusBarView方法
makeStatusBarView的方法裡呼叫 inflateStatusBarWindow(context)載入佈局
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null);
}複製程式碼
這裡介紹下佈局
狀態列佈局介紹
整個狀態列的父佈局是R.layout.super_status_bar,對應的是StatusBarWindowView這個自定義佈局.
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
......
<!--正常狀態列下的佈局 -->
<include layout="@layout/status_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height" />
<!--狀態列圖示下的SeekBar佈局 -->
<include layout="@layout/brightness_mirror" />
<!--車載模式的佈局 -->
<ViewStub android:id="@+id/fullscreen_user_switcher_stub"
android:layout="@layout/car_fullscreen_user_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!--狀態列下拉的佈局 -->
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</com.android.systemui.statusbar.phone.StatusBarWindowView>複製程式碼
我這裡以主要的佈局層次做介紹,結合圖片分析會更加清楚
StatusBarWindowView裡有幾個主要的佈局
- layout/status_bar
- layout/brightness_mirror
- layout/status_bar_expanded
如下圖
1.layout/status_bar
這個是正常狀態下(未下拉的狀態列圖示區域)
這個佈局對應的是PhoneStatusBarView
<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:id="@+id/status_bar"
android:background="@drawable/system_bar_background"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
>
......
<!--狀態列 -->
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="6dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
>
<!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
<!-- 通知圖示區域-->
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal" />
<!-- 系統圖示區域 -->
<com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<!-- 系統圖示 -->
<include layout="@layout/system_icons" />
<!-- 時鐘資訊 -->
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_vertical|start"
/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>複製程式碼
以下是細節圖,連線表示層次結構
其中,狀態列的區域分為以下幾種
- 通知欄圖示,在狀態列的最左側顯示通知資訊,比如來了一個簡訊,那麼就會彈出一個簡訊圖示
- 時間資訊,顯示一個時間,比如上午9:58
- 訊號圖示,顯示手機訊號,wifi訊號等
- 電量圖示,顯示當前電量狀態
- 狀態圖示,wifi,藍芽等開關狀態
2.@layout/brightness_mirror
這個佈局就是中間那個調整亮度的seekBar.沒啥好介紹的.
3.@layout/status_bar_expanded
這個佈局是下拉時的狀態列的佈局
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:id="@+id/notification_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
>
<!--鎖屏時的時鐘佈局 -->
<include
layout="@layout/keyguard_status_view"
android:layout_height="wrap_content"
android:visibility="gone" />
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:id="@+id/notification_container_parent"
android:clipToPadding="false"
android:clipChildren="false">
<!--quciksetting區域 -->
<com.android.systemui.AutoReinflateContainer
android:id="@+id/qs_auto_reinflate_container"
android:layout="@layout/qs_panel"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:clipToPadding="false"
android:clipChildren="false" />
<!-- 通知欄區域 -->
<com.android.systemui.statusbar.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap" />
<!--鎖屏切換 -->
<ViewStub
android:id="@+id/keyguard_user_switcher"
android:layout="@layout/keyguard_user_switcher"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<!--鎖屏下的狀態列 -->
<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />
</com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
<!--鎖屏介面底部的圖示 -->
<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />
</com.android.systemui.statusbar.phone.NotificationPanelView><!-- end of sliding panel -->複製程式碼
細節圖
建立完佈局後,就會新增視窗到WindowManager裡,這樣狀態列就建立完成了.接下來會回到3.2 addNavigationBar()的步驟中.
3.2、addNavigationBar
這個方法是新增底部的導航欄的,就是那些home鍵,back鍵所在的區域.
public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
......
protected void addNavigationBar() {
if (mNavigationBarView == null) return;
//初始化導航欄
prepareNavigationBarView();
//新增到WindowManager中
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
}
......
}複製程式碼
在這個方法裡先初始化導航欄,然後把導航欄新增到視窗中.
prepareNavigationBarView()
public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
......
private void prepareNavigationBarView() {
//重新初始化
mNavigationBarView.reorient();
//最近應用鍵
ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
recentsButton.setOnClickListener(mRecentsClickListener);
recentsButton.setOnTouchListener(mRecentsPreloadOnTouchListener);
recentsButton.setLongClickable(true);
recentsButton.setOnLongClickListener(mRecentsLongClickListener);
//後退鍵
ButtonDispatcher backButton = mNavigationBarView.getBackButton();
backButton.setLongClickable(true);
backButton.setOnLongClickListener(mLongPressBackListener);
//home鍵
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(mHomeActionListener);
homeButton.setOnLongClickListener(mLongPressHomeListener);
//監聽配置改變
mAssistManager.onConfigurationChanged();
}
......
}複製程式碼
四、結束
關於SystemUI的狀態列和導航欄就介紹完了,講的很淺顯,只是從整體上梳理了下流程.