作为一个Android系统开发工程师,StatusBar是永远无法绕开的一个部分,今天来好好的分析一下这个模块
一、简介
从结构上来说,下拉菜单和状态栏都属于StatusBar,在Android9.0之后,可以对状态栏进行变色操作或者添加图标,真正实现了自定义!
在最顶部的super_status_bar结构之后下属有两个分支,分别是
status_bar_expanded下拉菜单,分快捷图标和通知栏。
status_bar_container状态栏,分左边通知栏和右边系统功能的状态图标显示。
二、启动
首先我们来看一下start()方法的代码,在代码中去分析
@Override
public void start() {
//在这里注册了很多监听事件并进行了一些控制类和服务的获取
mScreenLifecycle.addObserver(mScreenObserver);
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mBypassHeadsUpNotifier.setUp();
mBubbleController.setExpandListener(mBubbleExpandListener);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mColorExtractor.addOnColorsChangedListener(this);
mStatusBarStateController.addCallback(this,
SysuiStatusBarStateController.RANK_STATUS_BAR);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
//dream是在充电设备空闲或停靠在桌面底座时启动的交互式屏幕保护程序。 Dreams 为应用程序提供了另一种表达自我的方式,专为展示后背体验而量身定制
//这里是引入外部框架并检查服务是否开启
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE));
//进行手机省电模式的监听(如果设备不支持,将不会生效)
/* UNISOC: Bug 1074234, 885650, Super power feature @{ */
mDataSaverController = Dependency.get(DataSaverController.class);
if (UnisocPowerManagerUtil.SUPPORT_SUPER_POWER_SAVE) {
int mode = UnisocPowerManagerUtil.getPowerSaveModeInternal();
mPowerSaveMode = mode;
curMode = mode;
}
/* @} */
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayId = mDisplay.getDisplayId();
//创建StatusBar时和显现的时候调用
updateDisplaySize();
//振动器是否开启
mVibrateOnOpening = mContext.getResources().getBoolean(
R.bool.config_vibrateOnIconAnimation);
// start old BaseStatusBar.start().
//注册功能管理
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mWallpaperSupported =
mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
// Connect in to the status bar manager service
//这里类似消息队列,让线程不会堆积堵塞
mCommandQueue.addCallback(this);
RegisterStatusBarResult result = null;
try {
result = mBarService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
createAndAddWindows(result);
if (mWallpaperSupported) {
// Make sure we always have the most current wallpaper info.
IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
//注册广播接收
mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter,
null /* handler */, UserHandle.ALL);
mWallpaperChangedReceiver.onReceive(mContext, null);
} else if (DEBUG) {
Log.v(TAG, "start(): no wallpaper service ");
}
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
if (containsType(result.mTransientBarTypes, ITYPE_STATUS_BAR)) {
showTransientUnchecked();
}
onSystemBarAppearanceChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions,
result.mNavbarColorManagedByIme);
mAppFullscreen = result.mAppFullscreen;
mAppImmersive = result.mAppImmersive;
// StatusBarManagerService has a back up of IME token and it's restored here.
setImeWindowStatus(mDisplayId, result.mImeToken, result.mImeWindowVis,
result.mImeBackDisposition, result.mShowImeSwitcher);
// Set up the initial icon state
//这里mIcons是一个arrayMap,所以可以根据他的键值去寻找
int numIcons = result.mIcons.size();
for (int i = 0; i < numIcons; i++) {
mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
}
if (DEBUG) {
Log.d(TAG, String.format(
"init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
numIcons,
result.mDisabledFlags1,
result.mAppearance,
result.mImeWindowVis));
}
IntentFilter internalFilter = new IntentFilter();
internalFilter.addAction(BANNER_ACTION_CANCEL);
internalFilter.addAction(BANNER_ACTION_SETUP);
mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
null);
if (mWallpaperSupported) {
IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
ServiceManager.getService(Context.WALLPAPER_SERVICE));
try {
wallpaperManager.setInAmbientMode(false /* ambientMode */, 0L /* duration */);
} catch (RemoteException e) {
// Just pass, nothing critical.
}
}
// end old BaseStatusBar.start().
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy.init();
mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
mKeyguardStateController.addCallback(this);
startKeyguard();
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
mDozeServiceHost.initialize(this, mNotificationIconAreaController,
mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,
mNotificationPanelViewController, mAmbientIndicationContainer);
mConfigurationController.addCallback(this);
// set the initial view visibility
int disabledFlags1 = result.mDisabledFlags1;
int disabledFlags2 = result.mDisabledFlags2;
mInitController.addPostInitTask(
() -> setUpDisableFlags(disabledFlags1, disabledFlags2));
//注册插件监听
mPluginManager.addPluginListener(
new PluginListener<OverlayPlugin>() {
private ArraySet<OverlayPlugin> mOverlays = new ArraySet<>();
@Override
public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
mMainThreadHandler.post(
() -> plugin.setup(getNotificationShadeWindowView(),
getNavigationBarView(),
new Callback(plugin), mDozeParameters));
}
@Override
public void onPluginDisconnected(OverlayPlugin plugin) {
mMainThreadHandler.post(() -> {
mOverlays.remove(plugin);
mNotificationShadeWindowController
.setForcePluginOpen(mOverlays.size() != 0);
});
}
class Callback implements OverlayPlugin.Callback {
private final OverlayPlugin mPlugin;
Callback(OverlayPlugin plugin) {
mPlugin = plugin;
}
@Override
public void onHoldStatusBarOpenChange() {
if (mPlugin.holdStatusBarOpen()) {
mOverlays.add(mPlugin);
} else {
mOverlays.remove(mPlugin);
}
mMainThreadHandler.post(() -> {
mNotificationShadeWindowController
.setStateListener(b -> mOverlays.forEach(
o -> o.setCollapseDesired(b)));
mNotificationShadeWindowController
.setForcePluginOpen(mOverlays.size() != 0);
});
}
}
}, OverlayPlugin.class, true /* Allow multiple plugins */);
// For longscreenshot feature
setRegionOrLongshotMode(mContext, 0);
// UNISOC: BUG 1353950 add screen resolution observer to control NavigationBarView
mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(ACTION_SR_CHANGING),
false, mScreenResolutionObserver);
//UNISOC: Add for bug 1359220
registerNavBarModeSettingChange(mContext);
}
在start()方法中
1.获取到了很多的服务以及一些控制器
2.判断对壁纸的支持
3.初始化图标按键
4.将自身注册到StatusBarService中供其他app使用
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
//消息队列加入回调
mCommandQueue.addCallback(this);
//以上
RegisterStatusBarResult result = null;
try {
//这是一个数据对象,用于服务与客服端之间的通讯
result = mBarService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
三、创建
接下来我们来看createAndAddWindows()方法
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mNotificationShadeWindowController.attach();
mStatusBarWindowController.attach();
}
在这个方法里面,主要是调用了别的方法体,首先是makeStatusBarView(result),我们来看看里面都做了些什么
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
final Context context = mContext;
updateDisplaySize(); // populates mDisplayMetrics//更新显示尺寸
updateResources();//更新变化了的配置内容(进行重新加载)
updateTheme();//切换主题颜色
FragmentHostManager.get(mPhoneStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
// CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
// mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
// PhoneStatusBarView's new instance will set to be gone in
// PanelBar.updateVisibility after calling mStatusBarView.setBouncerShowing
// that will trigger PanelBar.updateVisibility. If there is a heads up showing,
// it needs to notify PhoneStatusBarView's new instance to update the correct
// status by calling mNotificationPanelViewController.notifyBarPanelExpansionChanged().
if (mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationPanelViewController.notifyBarPanelExpansionChanged();
}
mStatusBarView.setBouncerShowing(mBouncerShowing);
if (oldStatusBarView != null) {
float fraction = oldStatusBarView.getExpansionFraction();
boolean expanded = oldStatusBarView.isExpanded();
mStatusBarView.panelExpansionChanged(fraction, expanded);
}
HeadsUpAppearanceController oldController = mHeadsUpAppearanceController;
if (mHeadsUpAppearanceController != null) {
// This view is being recreated, let's destroy the old one
mHeadsUpAppearanceController.destroy();
}
// TODO: this should probably be scoped to the StatusBarComponent
// TODO (b/136993073) Separate notification shade and status bar
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager,
mNotificationShadeWindowView,
mStatusBarStateController, mKeyguardBypassController,
mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
mNotificationPanelViewController, mStatusBarView);
mHeadsUpAppearanceController.readFrom(oldController);
mLightsOutNotifController.setLightsOutNotifView(
mStatusBarView.findViewById(R.id.notification_lights_out));
mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
checkBarModes();
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
CollapsedStatusBarFragment.TAG)
.commit();
.......
//创建导航栏
createNavigationBar(result);
if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
mKeyguardIndicationController.setIndicationArea(
mNotificationShadeWindowView.findViewById(R.id.keyguard_indication_area));
mNotificationPanelViewController.setKeyguardIndicationController(
mKeyguardIndicationController);
......
// TODO: Find better place for this callback.
//电池状态监听
mBatteryController.addCallback(new BatteryStateChangeCallback() {
@Override
public void onPowerSaveChanged(boolean isPowerSave) {
mHandler.post(mCheckBarModes);
if (mDozeServiceHost != null) {
mDozeServiceHost.firePowerSaveChanged(isPowerSave);
}
}
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
// noop
}
});
mAutoHideController.setStatusBar(new AutoHideUiElement() {
@Override
public void synchronizeState() {
checkBarModes();
}
@Override
public boolean shouldHideOnTouch() {
return !mRemoteInputManager.getController().isRemoteInputActive();
}
@Override
public boolean isVisible() {
return isTransientShown();
}
@Override
public void hide() {
clearTransient();
}
});
......
}
代码很长,截取了一部分。那么在这部分代码中干了些什么事情呢?
1.首先,对拥有的资源文件以及系统主题进行了更新同步
2.使用inflateStatusBarWindow()加载视图初始化资源文件,获取状态栏高度
3.我们注意到,其中有这样一段代码
FragmentHostManager.get(mPhoneStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
mStatusBarView.setBar(this);
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
这个CollapsedStatusBarFragment就是作为StatusBar的Fragment去加载的。那么我们都知道,一个fragment的加载首先是从onCreateView方法中去加载layout,所以
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.status_bar, container, false);
}
所以我们找到status_bar.xml进行查看,这里就不贴代码了,我们会发现有以下几点
1.@+id/status_bar_contents 这个LinearLayout,里面包含了@+id/status_bar_left_side ,从名字来看就知道是状态栏左边部分。这个状态栏左边部分包含了时钟@+id/clock和短信通知@+id/notification_icon_area。
2.@+id/system_icon_area这个也是一个LinearLayout包含了@layout/system_icons,这部分就是状态栏右边部分,里面包含了电池图标和系统状态图标
字段 | 位置 |
---|---|
status_bar_contents | 左 |
system_icon_area | 右 |
四、加载图标
接着我们发现不论是什么图标,他们基本都是动态加载的,那么他们是怎么被加载和移除的呢
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);//短信通知icon
mNotificationIconAreaInner =
notificationIconAreaController.getNotificationInnerAreaView();
//移除自己,再添加自己
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
// Default to showing until we know otherwise.
showNotificationIconArea(false);
}
根据id找到,这是短信添加的代码。
在短信第一次被添加的时候,会调用NotificationEntryManager.addNotification方法
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
private void addNotificationInternal(
StatusBarNotification notification,
RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
if (DEBUG) {
Log.d(TAG, "addNotification key=" + key);
}
updateRankingAndSort(rankingMap, "addNotificationInternal");
Ranking ranking = new Ranking();
rankingMap.getRanking(key, ranking);
NotificationEntry entry = mPendingNotifications.get(key);
if (entry != null) {
entry.setSbn(notification);
} else {
entry = new NotificationEntry(
notification,
ranking,
mFgsFeatureController.isForegroundServiceDismissalEnabled(),
SystemClock.uptimeMillis());
mAllNotifications.add(entry);
mLeakDetector.trackInstance(entry);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryInit(entry);
}
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
}
// Construct the expanded view.
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mNotificationRowBinderLazy.get()
.inflateViews(
entry,
() -> performRemoveNotification(notification, REASON_CANCEL),
mInflationCallback);
}
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPendingEntryAdded(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryAdded(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
}
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
在收到系统的通知之后,会在NotificationIconAreaController.updateIconsForLayout方法中更新手机通知栏布局的通知图标,在这里也就是把短信图标更新上去。