状态栏图标的加载流程,本质上是数据与视图的分离与同步过程 。StatusBarIconController 作为数据中心,接收来自系统各处的状态变化,并将变化推送给所有已注册的 IconManager,由后者负责实际的视图更新。
核心角色与职责
-
StatusBarIconController:中央控制器 。它是控制状态图标显示的核心接口,负责接收所有状态栏图标的更新请求,并管理所有IconManager。 -
StatusBarIconControllerImpl:具体实现类 。维护一个IconManager列表 (mIconGroups),接收系统回调并分发状态。其构造函数会根据config_statusBarIcons数组预定义好所有图标槽位(Slots)。
csharp
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java)
public StatusBarIconControllerImpl(
Context context,
CommandQueue commandQueue,
DemoModeController demoModeController) {
//调用StatusBarIconList构造函数
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
...
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
//StatusBarIconList构造函数
public StatusBarIconList(String[] slots) {
final int N = slots.length;
for (int i=0; i < N; i++) {
mSlots.add(new Slot(slots[i], null));
}
}
-
IconManager:视图管理者 。负责实际创建、添加和更新StatusBarIconView。主要有两种:DarkIconManager:用于主状态栏 (CollapsedStatusBarFragment)。TintedIconManager:用于锁屏状态栏或快捷设置面板。
-
PhoneStatusBarPolicy:策略决策者 。负责"决策"哪些图标该显示,根据系统状态(如蓝牙开关)调用StatusBarIconController的setIconVisibility等方法。 -
StatusBarIconView:实际视图 。状态栏中每个图标的具体View实例。 -
PhoneStatusBarView:整个状态栏的根布局容器 ,而StatusBarIconView则是状态栏里每一个具体图标的视图单元 。 它承载了状态栏上所有的可见元素。所有你看到的图标、文字,最终都是被添加到这个容器里的某个子区域中。它是整个状态栏"下拉"手势的起点。当你触摸并向下滑动屏幕顶部时,PhoneStatusBarView会捕获这个触摸事件,并协同StatusBar服务,触发通知面板和快捷设置面板的展开动画。
加载与显示流程
- 系统启动与注册 :
SystemUI启动时,StatusBar服务初始化,通过Dependency机制创建StatusBarIconControllerImpl单例。随后,CollapsedStatusBarFragment等视图会创建对应的IconManager(如DarkIconManager),并通过addIconGroup()将其注册到StatusBarIconControllerImpl中。 - 图标初始化 :注册后,
StatusBarIconControllerImpl会立即遍历所有预定义的图标槽位,通知IconManager创建对应的StatusBarIconView并添加到布局中。PhoneStatusBarPolicy也会在构造时完成对所有系统状态图标的初始化和状态监听。 - 图标数据更新 :当系统状态变化(如连接Wi-Fi)时,
PhoneStatusBarPolicy监听到事件,调用setIcon()等方法。StatusBarIconControllerImpl更新内部数据,并遍历mIconGroups,调用每个IconManager的相应方法。 - 图标视图更新 :
IconManager收到通知后,负责更新对应的StatusBarIconView,比如更新图标资源或可见性。
ini
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
private final StatusBarIconController mStatusBarIconController;
public StatusBar(
Context context,
...
PhoneStatusBarPolicy phoneStatusBarPolicy,
...
NotificationIconAreaController notificationIconAreaController,
...
StatusBarIconController statusBarIconController,
...
Optional<StartingSurface> startingSurfaceOptional) {
super(context);
mNotificationsController = notificationsController;
...
mIconPolicy = phoneStatusBarPolicy;
...
mNotificationIconAreaController = notificationIconAreaController;
...
mStatusBarIconController = statusBarIconController;
...
DateTimeView.setReceiverHandler(timeTickHandler);
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java)
public void start() {
...
//1.创建状态栏和下拉栏窗口和视图
createAndAddWindows(result);
...
//2.图标策略初始化
mIconPolicy.init();
...
}
第1步:createAndAddWindows()
这一步是视图层 的初始化, PhoneStatusBarView 与 StatusBarIconView 直接相关。
-
它会调用
makeStatusBarView(),最终完成以下工作:- 加载
status_bar.xml布局,实例化出PhoneStatusBarView。 - 通过
CollapsedStatusBarFragment将PhoneStatusBarView添加到窗口中。 - 创建
IconManager(如DarkIconManager),并通过StatusBarIconController.addIconGroup()将其注册为观察者。 - 注意:此时
IconManager已注册,但还没有收到任何图标数据,所以状态栏此时是空的或只有占位符。
- 加载
scss
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
//创建状态栏视图
makeStatusBarView(result);
...
}
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
final Context context = mContext;
...
//监听 `CollapsedStatusBarFragment` 的创建完成事件
FragmentHostManager.get(mPhoneStatusBarWindow)
//添加标签监听器,当带有 `TAG` 的 Fragment 被创建或重新创建时触发回调
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
PhoneStatusBarView oldStatusBarView = mStatusBarView;
//从 Fragment 中获取新创建的 `PhoneStatusBarView` 视图
mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView();
//将 `StatusBar` 实例注入到视图,用于回调状态栏事件
mStatusBarView.setBar(this);
//注入面板控制器,用于面板展开状态查询
mStatusBarView.setPanel(mNotificationPanelViewController);
//注入遮罩控制器,用于控制遮罩层
mStatusBarView.setScrimController(mScrimController);
//设置展开状态变化监听器列表
mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
for (ExpansionChangedListener listener : mExpansionChangedListeners) {
//向监听器发送当前展开状态(初始值)
sendInitialExpansionAmount(listener);
}
mNotificationPanel.notifyBarPanelExpansionChanged().
//检查是否有固定的 HeadsUp 通知(即正在显示的 HeadsUp)
if (mHeadsUpManager.hasPinnedHeadsUp()) {
//通知面板展开状态已变化,更新视图 mNotificationPanelViewController.notifyBarPanelExpansionChanged();
}
mStatusBarView.setBouncerShowing(mBouncerShowing);
...
//设置状态栏视图到面板窗口控制器
mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
checkBarModes();
}).getFragmentManager()
.beginTransaction()
.replace(R.id.status_bar_container,
//创建折叠状态栏fragment
new CollapsedStatusBarFragment(
mOngoingCallController,//正在进行的通话控制器
mAnimationScheduler,//动画调度器
mStatusBarLocationPublisher,
mNotificationIconAreaController,//通知图标区域控制器
mFeatureFlags,
mStatusBarIconController,//状态栏图标控制器
mKeyguardStateController,//锁屏状态控制器
mNetworkController,//网络控制器(信号/WiFi)
mStatusBarStateController,//状态栏状态控制器
this,
mCommandQueue
),
CollapsedStatusBarFragment.TAG)
.commit();
...
}
...
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java)
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//是状态栏(非展开状态,即 `Collapsed` 状态)的根视图。在这个容器里,摆放着
//状态栏左侧的通知图标、右侧的系统状态图标等所有可见元素
//处理下拉手势,协调面板状态,响应深色/浅色主题
mStatusBar = (PhoneStatusBarView) view;
...
//初始化 DarkIconManager,管理状态栏右侧的图标
mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons), mFeatureFlags);
...
//状态栏图标控制器
mStatusBarIconController.addIconGroup(mDarkIconManager);
//系统图标区域(右侧,包含信号、WiFi、电量等)
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
...
//初始隐藏系统图标区域
//这些组件会在系统状态更新时(如锁屏状态变化、网络连接)才显示。
//这避免了在 Fragment 初始化时出现闪烁。
showSystemIconArea(false);
...
//初始化通知图标区域(左侧,显示应用通知图标)
initNotificationIconArea();
mAnimationScheduler.addCallback(this);
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
public void addIconGroup(IconManager group) {
mIconGroups.add(group);
List<Slot> allSlots = getSlots();
for (int i = 0; i < allSlots.size(); i++) {
Slot slot = allSlots.get(i);
...
for (StatusBarIconHolder holder : holders) {
...
//调用StatusBarIconController的DarkIconManager的onIconAdded方法
group.onIconAdded(viewIndex, slot.getName(), hidden, holder);
}
}
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
protected void onIconAdded(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
//步骤 1: 创建并添加图标视图
StatusIconDisplayable view = addHolder(index, slot, blocked, holder);
//步骤 2: 注册主题监听
mDarkIconDispatcher.addDarkReceiver((DarkReceiver) view);
}
protected StatusIconDisplayable addHolder(int index, String slot, boolean blocked,
StatusBarIconHolder holder) {
//是否包含被阻止显示的图标
if (mBlockList.contains(slot)) {
blocked = true;
}
switch (holder.getType()) {
//处理普通图标,如电池、蓝牙、闹钟等。
case TYPE_ICON:
return addIcon(index, slot, blocked, holder.getIcon());
//处理Wi-Fi 信号图标,需要显示信号强度(如 0~4 格)
case TYPE_WIFI:
return addSignalIcon(index, slot, holder.getWifiState());
//处理移动网络信号图标,同样需要显示信号强度,还可能包含运营商类型(如 4G、5G)
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
}
return null;
}
//创建普通图标,如电池、蓝牙、闹钟等。
protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
StatusBarIcon icon) {
//1. 创建图标视图实例
StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
//2. 绑定图标数据
view.set(icon);
//3. 添加到父容器
mGroup.addView(view, index, onCreateLayoutParams());
return view;
}
第2步:mIconPolicy.init()
这一步是数据层 的初始化,PhoneStatusBarPolicy.init()。
-
它完成的工作包括:
- 注册各种系统广播接收器和服务回调,开始监听系统状态。
- 主动查询所有系统服务的当前状态 (如 Wi-Fi 是否开启、电量多少等),并调用
StatusBarIconController.setIcon()将初始数据推送过去。 - 一旦
setIcon()被调用,StatusBarIconController就会通知所有已注册的IconManager,触发onIconAdded→addHolder→addIcon调用链,最终创建出StatusBarIconView并添加到PhoneStatusBarView中。
scss
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
public void init() {
//广播接收器注册,监听的事件包括:耳机插拔、SIM卡状态变化、TTY模式变化、
//托管配置文件变化等。
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
...
mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
//监听铃声模式的变化
Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);
//更新音量/静音/振动图标
mRingerModeTracker.getRingerMode().observeForever(observer);
mRingerModeTracker.getRingerModeInternal().observeForever(observer);
...
//更新蓝牙图标
updateBluetooth();
//闹钟图标等一系列图标初始化
//调用StatusBarIconControllerImpl.setIcon()
mIconController.setIcon(mSlotAlarmClock, R.drawable.stat_sys_alarm, null);
mIconController.setIconVisibility(mSlotAlarmClock, false);
...
//各种系统服务注册回调监听器
mRotationLockController.addCallback(this);
//蓝牙图标
mBluetooth.addCallback(this);
...
mCommandQueue.addCallback(this);
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
//1. 根据 slot 查找索引
int index = getSlotIndex(slot);
// 2. 尝试获取已存在的图标数据
StatusBarIconHolder holder = getIcon(index, 0);
if (holder == null) {
StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
Icon.createWithResource(
mContext, resourceId), 0, 0, contentDescription);
holder = StatusBarIconHolder.fromIcon(icon);
//图标不存在,执行"新增"逻辑
setIcon(index, holder);
} else {
holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
holder.getIcon().contentDescription = contentDescription;
//图标已存在,执行"更新"逻辑
handleSet(index, holder);
}
}
public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
// 1. 判断是新增还是更新
boolean isNew = getIcon(index, holder.getTag()) == null;
super.setIcon(index, holder);
if (isNew) {
//新增:通知创建视图
addSystemIcon(index, holder);
} else {
//更新:通知更新视图
handleSet(index, holder);
}
}
private void addSystemIcon(int index, StatusBarIconHolder holder) {
//1. 获取图标的槽位名称
String slot = getSlotName(index);
// 2. 计算图标在视图中的位置索引
int viewIndex = getViewIndex(index, holder.getTag());
// 3. 检查该槽位是否被配置为隐藏
boolean hidden = mIconHideList.contains(slot);
// 4. 遍历并通知所有 IconManager
//这个回调在mStatusBarIconController.addIconGroup(mDarkIconManager)中已分析
//调用StatusBarIconController的DarkIconManager的onIconAdded方法更新图标
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
}
private void handleSet(int index, StatusBarIconHolder holder) {
// 1. 计算图标在视图中的位置索引
int viewIndex = getViewIndex(index, holder.getTag());
// 2. 遍历并通知所有 IconManager 执行更新
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
//frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
switch (holder.getType()) {
//更新普通图标(如电池、蓝牙)的图片资源和描述
case TYPE_ICON:
onSetIcon(viewIndex, holder.getIcon());
return;
//更新 Wi-Fi 信号强度格数
case TYPE_WIFI:
onSetSignalIcon(viewIndex, holder.getWifiState());
return;
//更新移动信号强度格数和网络类型(如 4G/5G)
case TYPE_MOBILE:
onSetMobileIcon(viewIndex, holder.getMobileState());
default:
break;
}
}
从 Android U(14)开始,Wi-Fi 和移动数据图标的逻辑已迁移到全新的"数据管道"架构,而其他大部分系统图标仍沿用上述传统机制。