在上一篇(https://blog.csdn.net/u011164827/article/details/102998091)分析到SystemUI的啓動過程,如今分析StatusBar。java
SystemUI在SystemUIApplication會啓動各個模塊,在這個地方會調用com.android.systemui.SystemBars的start方法。android
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java網絡
@Override 40 public void start() { 41 if (DEBUG) Log.d(TAG, "start"); 42 createStatusBarFromConfig(); 43 } private void createStatusBarFromConfig() { 53 if (DEBUG) Log.d(TAG, "createStatusBarFromConfig"); //取出className 54 final String clsName = mContext.getString(R.string.config_statusBarComponent); 55 if (clsName == null || clsName.length() == 0) { 56 throw andLog("No status bar component configured", null); 57 } // 經過反射獲取該對象 58 Class<?> cls = null; 59 try { 60 cls = mContext.getClassLoader().loadClass(clsName); 61 } catch (Throwable t) { 62 throw andLog("Error loading status bar component: " + clsName, t); 63 } 64 try { 65 mStatusBar = (SystemUI) cls.newInstance(); 66 } catch (Throwable t) { 67 throw andLog("Error creating status bar component: " + clsName, t); 68 } //填充信息並啓動 StatusBar start() 方法 69 mStatusBar.mContext = mContext; 70 mStatusBar.mComponents = mComponents; 71 mStatusBar.start(); 72 if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName()); 73 }
這裏面的代碼很少,主要是經過反射獲取config_statusBarComponent
中定義的對象,並啓動該對象的start方法。config_statusBarComponent
的值有3種,默認是phone佈局,另外兩個是tv和car。ide
44 <!-- Component to be used as the status bar service. Must implement the IStatusBar 45 interface. This name is in the ComponentName flattened format (package/class) --> 46 <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
咱們這裏分析frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java佈局
public void start() { //因爲狀態欄的窗口不屬於任何一個Activity,因此須要使用WindowManager窗口建立 656 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 657 658 mDisplay = mWindowManager.getDefaultDisplay(); 659 updateDisplaySize(); 660 661 Resources res = mContext.getResources(); 662 mVibrateOnOpening = mContext.getResources().getBoolean( 663 R.bool.config_vibrateOnIconAnimation); 664 mVibratorHelper = Dependency.get(VibratorHelper.class); 665 mScrimSrcModeEnabled = res.getBoolean(R.bool.config_status_bar_scrim_behind_use_src); 666 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); 667 668 DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER)); 669 putComponent(StatusBar.class, this); 670 671 // start old BaseStatusBar.start(). //狀態欄的存在對窗口布局有重要的影響,所以狀態欄中所發生的變化有必要通知給WMS 672 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 673 mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( 674 Context.DEVICE_POLICY_SERVICE); 675 676 mAccessibilityManager = (AccessibilityManager) 677 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); 678 679 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 680 681 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); 682 683 mBarService = IStatusBarService.Stub.asInterface( 684 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 685 686 mRecents = getComponent(Recents.class); 687 688 mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 689 mLockPatternUtils = new LockPatternUtils(mContext); 690 691 mMediaManager.setUpWithPresenter(this, mEntryManager); 692 693 // Connect in to the status bar manager service //mCommandQueue是CommandQueue類的一個實例。CommandQueue繼承自IStatusBar.Stub。所以它是IStatusBar的Bn端。在完成註冊後,這一Binder對象的Bp端將會保存在 //IStatusBarService中,所以它是IStatusBarService與BaseStatusBar進行通訊的橋樑。 694 mCommandQueue = getComponent(CommandQueue.class); 695 mCommandQueue.addCallbacks(this); 696 /* *switches存儲了一些雜項:禁用功能列表,SystemUIVisiblity,是否在導航欄中顯示虛擬的菜單鍵,輸入法窗口是否可見,輸入法是否消費BACK鍵,是否接入了實體鍵盤 *實體鍵盤是否被啓用 */ 697 int[] switches = new int[9]; 698 ArrayList<IBinder> binders = new ArrayList<>(); 699 ArrayList<String> iconSlots = new ArrayList<>(); 700 ArrayList<StatusBarIcon> icons = new ArrayList<>(); 701 Rect fullscreenStackBounds = new Rect(); 702 Rect dockedStackBounds = new Rect(); 703 try { //向IStatusBarServie進行註冊,並獲取全部保存在IStatusBarService中的信息副本 704 mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders, 705 fullscreenStackBounds, dockedStackBounds); 706 } catch (RemoteException ex) { 707 // If the system process isn't there we're doomed anyway. 708 } 709 //建立狀態欄與導航欄的窗口 710 createAndAddWindows(); 711 712 // Make sure we always have the most current wallpaper info. 713 IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); 714 mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter); 715 mWallpaperChangedReceiver.onReceive(mContext, null); 716 717 mLockscreenUserManager.setUpWithPresenter(this, mEntryManager); 718 mCommandQueue.disable(switches[0], switches[6], false /* animate */); //禁用某些功能 719 setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, 720 fullscreenStackBounds, dockedStackBounds); //設置SystemUIVisibilty 721 topAppWindowChanged(switches[2] != 0); //設置菜單鍵的可見性 722 // StatusBarManagerService has a back up of IME token and it's restored here. 723 setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0); //根據輸入法窗口的可見性調整導航欄的樣式 724 725 // Set up the initial icon state //依次向系統狀態去添加狀態圖標 726 int N = iconSlots.size(); 727 for (int i=0; i < N; i++) { 728 mCommandQueue.setIcon(iconSlots.get(i), icons.get(i)); 729 } 730 731 // Set up the initial notification state. //初始化通知欄 732 mNotificationListener.setUpWithPresenter(this, mEntryManager); 733 734 if (DEBUG) { 735 Log.d(TAG, String.format( 736 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 737 icons.size(), 738 switches[0], 739 switches[1], 740 switches[2], 741 switches[3] 742 )); 743 } 744 745 setHeadsUpUser(mLockscreenUserManager.getCurrentUserId()); 746 747 IntentFilter internalFilter = new IntentFilter(); 748 internalFilter.addAction(BANNER_ACTION_CANCEL); 749 internalFilter.addAction(BANNER_ACTION_SETUP); 750 mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, 751 null); 752 753 IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( 754 Context.VR_SERVICE)); 755 try { 756 vrManager.registerListener(mVrStateCallbacks); 757 } catch (RemoteException e) { 758 Slog.e(TAG, "Failed to register VR mode state listener: " + e); 759 } 760 761 IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface( 762 ServiceManager.getService(Context.WALLPAPER_SERVICE)); 763 try { 764 wallpaperManager.setInAmbientMode(false /* ambientMode */, false /* animated */); 765 } catch (RemoteException e) { 766 // Just pass, nothing critical. 767 } 768 769 // end old BaseStatusBar.start(). 770 771 // Lastly, call to the icon policy to install/update all the icons. //最後,調用圖標策略以安裝/更新全部圖標 772 mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController); 773 mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController); 774 775 mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); 776 mUnlockMethodCache.addListener(this); 777 startKeyguard(); 778 779 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateCallback); 780 putComponent(DozeHost.class, mDozeServiceHost); 781 782 mScreenPinningRequest = new ScreenPinningRequest(mContext); 783 mFalsingManager = FalsingManager.getInstance(mContext); 784 785 Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this); 786 787 Dependency.get(ConfigurationController.class).addCallback(this); 788 }
StatusBar.java中有大量的代碼。ui
mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE));this
這一行獲取IStatusBarService的實例。IStatusBarService是一個系統服務,由ServerThread啓動並常駐system_seerver進程中。IStatusBarService爲那些對狀態欄感興趣的其它系統服務定義了一系列API,然而對SystemUI而言,它更像一個客戶端。由於IStatusBarService會將操做狀態欄的請求發給SystemUI,並由後者完成請求。spa
這裏主要看一下createAndAddWindows方法.net
狀態欄須要顯示的信息分爲如下5種:3d
系統狀態圖標區:這個區域用來顯示系統當前的狀態,好比能夠展現藍牙狀態、鬧鈴等。StatusBarManagerService經過setIcon()接口爲外界提供了修改系統狀態圖標的途徑,可是它對信息的內容有很強的限制。一、系統狀態圖標沒法顯示圖標之外的信息;二、系統狀態圖標對其顯示的圖標數量以及圖標鎖表示的意圖有嚴格的限制。
咱們繼續分析createAndAddWindows
public void createAndAddWindows() { addStatusBarWindow(); } private void addStatusBarWindow() { //建立狀態欄的控件樹 makeStatusBarView(); mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); mRemoteInputController = new RemoteInputController(mHeadsUpManager); //經過StatusBarWindowManager.add建立狀態欄的窗口 mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } /* *獲取statusbar高度,在framework/base/core/res/res/values/diamens.xml中設置 */ public int getStatusBarHeight() { if (mNaturalBarHeight < 0) { final Resources res = mContext.getResources(); mNaturalBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); } return mNaturalBarHeight; } // ================================================================================ // Constructing the view // ================================================================================ protected void makeStatusBarView() { final Context context = mContext; //獲取屏幕參數 updateDisplaySize(); // populates mDisplayMetrics //更新Panels資源數據,statusbar包含不少panel,在建立PhoneStatusBarView時須要更新panel數據 updateResources(); updateTheme(); inflateStatusBarWindow(context); //加載佈局 mStatusBarWindow.setService(this); mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener()); //mStatusBarWindow的點擊事件 Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController); FragmentHostManager.get(mStatusBarWindow) .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment; statusBarFragment.initNotificationIconArea(mNotificationIconAreaController); mStatusBarView = (PhoneStatusBarView) fragment.getView(); mStatusBarView.setBar(this); mStatusBarView.setPanel(mNotificationPanel); mStatusBarView.setScrimController(mScrimController); mStatusBarView.setBouncerShowing(mBouncerShowing); setAreThereNotifications(); checkBarModes(); }).getFragmentManager() .beginTransaction() .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG) //替換爲CollapsedStatusBarFragment .commit(); mIconController = Dependency.get(StatusBarIconController.class); } /* *加載佈局 */ protected void inflateStatusBarWindow(Context context) { mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null); }
調用流程是createAndAddWindows——>addStatusBarWindow——>makeStatusBarView
/** 87 * Adds the status bar view to the window manager. 88 * 89 * @param statusBarView The view to add. 90 * @param barHeight The height of the status bar in collapsed state. 91 */ 92 public void add(View statusBarView, int barHeight) { 93 94 // Now that the status bar window encompasses the sliding panel and its 95 // translucent backdrop, the entire thing is made TRANSLUCENT and is 96 // hardware-accelerated. 97 mLp = new WindowManager.LayoutParams( 98 ViewGroup.LayoutParams.MATCH_PARENT, 99 barHeight, 100 WindowManager.LayoutParams.TYPE_STATUS_BAR, 101 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 102 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 103 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 104 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 105 | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, 106 PixelFormat.TRANSLUCENT); 107 mLp.token = new Binder(); 108 mLp.gravity = Gravity.TOP; 109 mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 110 mLp.setTitle("StatusBar"); 111 mLp.packageName = mContext.getPackageName(); 112 mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 113 mStatusBarView = statusBarView; 114 mBarHeight = barHeight; 115 mWindowManager.addView(mStatusBarView, mLp); 116 mLpChanged = new WindowManager.LayoutParams(); 117 mLpChanged.copyFrom(mLp); 118 }
狀態欄的高度是從frameworks/base/core/res/res/values/dimens.xml中獲取的,默認爲25dp。TYPE_STATUS_BAR使得PhomeWindowManager
爲狀態欄的窗口分配了較大的layer值,使其能夠顯示在其它應用窗口上。FLAG_NOT_FOCUSABLE、FLAG_TOUCHABLE_WHEN_WAKING、FLAG_SPLIT_TOUCH
定義了輸入事件的響應行爲。另外當窗口建立後LayoutParams是會反生變化的。狀態欄窗口建立時高度爲25dip,flags描述爲其不可接受按鍵事件。不過當用戶
按下狀態欄致使捲簾下拉時,StatusBar會經過WindowManager.updateViewLayout()方法修改窗口的LayoutParams高度爲match_parent,即充滿整個屏幕使得捲簾
能夠滿屏顯示,而且移除FLAG_NOT_FOCUSABLE,使得StatusBar能夠監聽back按鈕
在inflateStatusBarWindow會初始化佈局
<!-- This is the combined status bar / notification panel window. --> <-- StatusBarWindowView 繼承於FrameLayout --> <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"> <com.android.systemui.statusbar.BackDropView android:id="@+id/backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" sysui:ignoreRightInset="true" > <ImageView android:id="@+id/backdrop_back" android:layout_width="match_parent" android:scaleType="centerCrop" android:layout_height="match_parent" /> <ImageView android:id="@+id/backdrop_front" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:visibility="invisible" /> </com.android.systemui.statusbar.BackDropView> <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_behind" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" sysui:ignoreRightInset="true" /> <com.android.systemui.statusbar.AlphaOptimizedView android:id="@+id/heads_up_scrim" android:layout_width="match_parent" android:layout_height="@dimen/heads_up_scrim_height" android:background="@drawable/heads_up_scrim" sysui:ignoreRightInset="true" android:importantForAccessibility="no"/> <FrameLayout android:id="@+id/status_bar_container" //頂部狀態欄 android:layout_width="match_parent" android:layout_height="wrap_content" /> <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" //QuickSettings和Notification欄 android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" /> <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front" android:layout_width="match_parent" android:layout_height="match_parent" android:importantForAccessibility="no" sysui:ignoreRightInset="true" /> </com.android.systemui.statusbar.phone.StatusBarWindowView>
在super_status_bar.xml文件中主要定義了三個佈局頂部狀態欄、亮度調節、QuickSettings和Notification。繼續分析頂部狀態欄,頂部狀態欄是一個Fragment,佈局文件是SystemUI/res/layout/status_bar.xml
<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:layout_width="match_parent" android:layout_height="@dimen/status_bar_height" android:id="@+id/status_bar" android:background="@drawable/system_bar_background" android:orientation="vertical" android:focusable="false" android:descendantFocusability="afterDescendants" > <!--notification_lights_out 通常狀況下是不可見,在SystemUIVisiblity中有一個名爲SYSTEM_UI_FLAG_LOW_PROFLE的標記 當一個應用程序但願讓客戶注意力更對集中在它所顯示的內容時,能夠在其SystemUIVisiblity中添加這一標記,SYSTEM_UI_FLAG_LOW_PROFLE會使 得狀態欄與導航欄進入低識別度模式。低識別度模式下的狀態欄將不會顯示任何信息,只是在黑色背景中顯示一個灰色圓點,這個圓點即這個--> <ImageView android:id="@+id/notification_lights_out" android:layout_width="@dimen/status_bar_icon_size" android:layout_height="match_parent" android:paddingStart="6dip" android:paddingBottom="2dip" android:src="@drawable/ic_sysbar_lights_out_dot_small" android:scaleType="center" android:visibility="gone" /> <!-- status_bar_contents顯示頂部狀態欄的各類信息 --> <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" /> <!--system_icon_area 繼承一個LinearLayout 除消息通知之外的信息 --> <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal" > <!--顯示wifi SIM卡 電量等 --> <include layout="@layout/system_icons" /> !--clock 顯示時間信息 --> <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> <ViewStub android:id="@+id/emergency_cryptkeeper_text" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout="@layout/emergency_cryptkeeper_text" /> </com.android.systemui.statusbar.phone.PhoneStatusBarView>
@+id/system_icon_area的寬度定義爲ware_content,而@+id/notification_icon_area的weight被設置爲1.在這種狀況下system_icon_area將在狀態欄右側根據其所顯示的圖標個數調整其尺寸。而notification_icon_area則會佔用狀態欄左側的剩餘空間。這說明一個問題:系統圖標區將優先佔用狀態欄的控件進行信息的顯示。
面又嵌套了一個SystemUI/res/layout/system_icons.xml佈局,system_icons裏面包含電量、系統狀態、信號等圖標。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/system_icons" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical"> <!--系統狀態圖標 --> <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal"/> <!--信號圖標區域 --> <include layout="@layout/signal_cluster_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/signal_cluster_margin_start"/> <!--顯示電量信息 --> <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="match_parent" android:layout_width="wrap_content" /> </LinearLayout>
信號圖標顯示在signal_cluster_view.xml文件中,這裏就不繼續深刻分析,等到分析信號時再作研究。
如今看一下狀態欄的控件樹結構
一、刪除頂部狀態欄
framework/base/core/res/res/values/dimens.xml中將status_bar_height設置爲0dp
二、刪除下方的導航欄
將qemu.hw.mainkeys屬性設置爲0
在看一下SystemUI的全套佈局
參考資料:
Android 8.0 SystemUI(三):一說頂部 StatusBar
深刻理解Android卷3