
Android SystemUI
在 Android 系统中,SystemUI 是承载「系统级交互与状态展示」的核心组件,我们日常看到的状态栏、导航栏(俗称 Dock 栏)、通知中心、锁屏界面等,均属于 SystemUI 的范畴。
它独立运行于 SystemServer 进程(部分组件为独立进程),是用户与系统交互的重要桥梁,也是 Android 系统视觉风格的直接体现。
本文将从 SystemUI 整体架构切入,重点拆解 Dock 栏与状态栏的创建流程,结合框架层源码还原其从初始化到最终显示的完整逻辑。
核心定位与整体架构
SystemUI 全称「System User Interface」,直译是「系统用户界面」,本质是 Android 系统提供的「系统级 UI 集合」,负责展示系统核心状态(如电量、信号、时间)和提供系统级交互入口(如返回、主页、多任务)。
其核心定位是「非应用级、全局生效的 UI 载体」,区别于第三方应用的 UI,SystemUI 拥有更高的系统权限,可直接与 WindowManagerService(WMS)、ActivityManagerService(AMS) 等核心系统服务交互。
1. SystemUI 的核心功能
-
状态展示:通过状态栏展示电量、网络信号、Wi-Fi、蓝牙、时间、通知图标等系统状态;
-
导航交互:通过 Dock 栏(导航栏)提供返回、主页、多任务三大核心导航按键,部分机型支持手势导航;
-
通知管理:通过通知中心展示、管理应用发送的通知,支持下拉展开、点击跳转、清除等操作;
-
系统交互:包含锁屏界面、音量调节弹窗、亮度调节滑块、截图预览等系统级交互 UI;
-
全局适配:适配不同屏幕尺寸、分辨率,处理应用全屏/沉浸式显示时的 UI 适配(如状态栏隐藏/变色)。
2. SystemUI 的整体架构:模块化设计
SystemUI 采用「模块化架构」,每个核心功能对应一个独立的模块(服务/组件),由 SystemUIApplication 统一管理初始化。核心模块与对应职责如下:
| 核心模块 | 对应组件 | 核心职责 |
|---|---|---|
| 状态栏模块 | StatusBar、StatusBarManagerService | 状态栏布局加载、系统状态图标更新、通知图标展示 |
| 导航栏模块 | NavigationBar、NavigationBarManager | Dock 栏布局加载、导航按键事件处理、显示隐藏控制 |
| 通知模块 | NotificationCenter、NotificationManagerService | 通知接收、展示、交互(下拉、点击、清除) |
| 锁屏模块 | KeyguardService、KeyguardViewMediator | 锁屏界面展示、解锁逻辑处理、安全验证 |
架构核心特点:各模块独立解耦,通过「接口回调」和「系统服务通信」实现协同;所有模块由 SystemUIApplication 启动初始化,确保启动顺序和依赖关系正确。
3. SystemUI 的启动时机
SystemUI 随系统启动而启动,具体时机是 SystemServer 进程启动后期。核心启动流程:
-
SystemServer 启动核心系统服务(AMS、WMS、PackageManagerService 等);
-
SystemServer 启动
SystemUIService,该服务是 SystemUI 的启动入口; -
SystemUIService 初始化
SystemUIApplication; -
SystemUIApplication 加载并启动所有核心模块(状态栏、导航栏、通知中心等)。
关键源码(SystemServer.java):
java
// SystemServer 中启动 SystemUI
private void startOtherServices() {
// ... 启动其他系统服务 ...
// 启动 SystemUI
startSystemUi(context, windowManagerF);
// ...
}
private static void startSystemUi(Context context, WindowManagerService windowManager) {
// 启动 SystemUIService
context.startServiceAsUser(
new Intent(context, SystemUIService.class),
UserHandle.SYSTEM
);
// 等待 SystemUI 初始化完成
windowManager.onSystemUiStarted();
}
状态栏(StatusBar)创建流程
状态栏是 SystemUI 最核心的组件之一,位于屏幕顶部,负责展示系统状态和通知图标。其创建流程核心围绕「初始化时机」「布局加载」「子视图初始化」「系统服务绑定」四个关键环节,核心类为 StatusBar,源码路径:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java。
1. 初始化时机:SystemUI 模块加载阶段
状态栏由 SystemUIApplication 加载启动,属于 SystemUI 核心模块,启动顺序在导航栏之前。核心逻辑在 SystemUIApplication.startServicesIfNeeded() 方法中,该方法会读取预设的 SystemUI 模块列表,通过反射创建实例并调用 onCreate() 初始化。
java
// SystemUIApplication 中加载 SystemUI 模块
private void startServicesIfNeeded() {
// 预设的 SystemUI 核心模块列表,包含状态栏
String[] services = getResources().getStringArray(R.array.config_systemUIServiceComponents);
for (String service : services) {
try {
// 反射创建模块实例(如 StatusBar)
Class<?> cl = Class.forName(service);
Object obj = cl.getConstructor(Context.class).newInstance(this);
// 启动初始化
((SystemUI) obj).onCreate();
} catch (Exception e) {
throw new RuntimeException("Failed to create SystemUI service: " + service, e);
}
}
}
注:config_systemUIServiceComponents 是系统资源文件中定义的 SystemUI 模块列表,状态栏对应的类名是 com.android.systemui.statusbar.StatusBar。
2. 核心创建流程:StatusBar.onCreate() 详解
StatusBar.onCreate() 是状态栏创建的核心方法,完成「布局加载」「子视图初始化」「系统服务绑定」「窗口添加」四大核心操作,完整流程拆解如下:
(1)步骤 1:初始化核心成员与系统服务绑定
状态栏需要与多个系统服务交互以获取状态数据(如电量、信号、时间),因此初始化阶段首先绑定核心系统服务:
java
@Override
public void onCreate() {
super.onCreate();
// 1. 获取核心系统服务
mContext = getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mStatusBarManager = mContext.getSystemService(StatusBarManager.class);
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mBatteryManager = mContext.getSystemService(BatteryManager.class);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
// 2. 初始化核心工具类(如通知管理、图标管理)
mNotificationManager = NotificationManagerCompat.from(mContext);
mIconManager = new StatusBarIconManager(mContext);
// 3. 加载状态栏布局
inflateStatusBarLayout();
// 4. 初始化子视图(信号、电池、时间等)
initStatusBarViews();
// 5. 将状态栏添加到 WindowManager
addStatusBarToWindow();
// 6. 注册系统状态监听器(如电量变化、网络变化)
registerSystemStatusListeners();
}
(2)步骤 2:加载状态栏布局(inflateStatusBarLayout)
状态栏的布局文件是 status_bar.xml,位于 frameworks/base/packages/SystemUI/res/layout/status_bar.xml,核心作用是定义状态栏的整体结构(如左侧通知图标区、中间时间区、右侧系统状态图标区)。
xml
// status_bar.xml 核心结构(简化)
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:orientation="horizontal">
<!-- 左侧:通知图标区 -->
<FrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
<!-- 中间:时间显示区 -->
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"/>
<!-- 右侧:系统状态图标区(信号、Wi-Fi、蓝牙、电池等) -->
<FrameLayout
android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"/>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
布局加载核心代码:
java
private void inflateStatusBarLayout() {
// Inflate 布局文件,创建 StatusBarWindowView 实例
mStatusBarWindowView = (StatusBarWindowView) LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, null);
// 初始化状态栏窗口参数(WindowManager.LayoutParams)
mStatusBarLayoutParams = new WindowManager.LayoutParams();
// 设置窗口类型为系统窗口(层级高,确保显示在最上层)
mStatusBarLayoutParams.type = WindowManager.LayoutParams.TYPE_STATUS_BAR;
// 设置宽高:宽度全屏,高度为系统定义的状态栏高度
mStatusBarLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
mStatusBarLayoutParams.height = getResources().getDimensionPixelSize(R.dimen.status_bar_height);
// 设置对齐方式:顶部
mStatusBarLayoutParams.gravity = Gravity.TOP;
// 设置标志位:无焦点、不拦截触摸事件(除非用户下拉通知)
mStatusBarLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
(3)步骤 3:初始化子视图(initStatusBarViews)
布局加载完成后,需要初始化子视图(信号、电池、时间等),并为其绑定数据来源:
java
private void initStatusBarViews() {
// 1. 初始化时间视图
mTimeView = mStatusBarWindowView.findViewById(R.id.time);
// 注册时间更新监听器,每秒更新时间显示
mAlarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 1000,
new PendingIntent(...)); // 省略PendingIntent创建逻辑,核心是每秒触发时间更新
// 2. 初始化系统状态图标区(信号、Wi-Fi、电池等)
mSystemIconArea = mStatusBarWindowView.findViewById(R.id.system_icon_area);
// 创建信号图标视图
mSignalIconView = new ImageView(mContext);
mSystemIconArea.addView(mSignalIconView);
// 创建电池图标视图
mBatteryIconView = new ImageView(mContext);
mSystemIconArea.addView(mBatteryIconView);
// 3. 初始化通知图标区
mNotificationIconArea = mStatusBarWindowView.findViewById(R.id.notification_icon_area);
}
(4)步骤 4:添加状态栏到 WindowManager(addStatusBarToWindow)
状态栏本质是一个系统窗口,最终需要通过 WindowManager.addView() 添加到系统窗口体系中,才能显示在屏幕上:
java
private void addStatusBarToWindow() {
try {
// 将状态栏视图添加到 WindowManager
mWindowManager.addView(mStatusBarWindowView, mStatusBarLayoutParams);
mStatusBarAdded = true;
} catch (WindowManager.BadTokenException e) {
// 异常处理:添加失败(如权限问题)
Log.e(TAG, "Failed to add status bar to window manager", e);
}
}
(5)步骤 5:注册系统状态监听器(registerSystemStatusListeners)
为了实时更新系统状态图标(如信号强度变化、电池电量变化),需要注册对应的系统监听器:
java
private void registerSystemStatusListeners() {
// 1. 注册电池状态监听器
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 获取电池电量
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
// 更新电池图标
updateBatteryIcon(level);
}
}, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
// 2. 注册网络状态监听器
mTelephonyManager.listen(new PhoneStateListener() {
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
// 更新信号图标
updateSignalIcon(signalStrength);
}
}, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
// 3. 注册时间变化监听器(系统时间修改时触发)
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateTimeView();
}
}, new IntentFilter(Intent.ACTION_TIME_TICK));
}
3. 状态栏的核心特性:显示控制与适配
状态栏支持动态显示隐藏和样式适配,核心控制逻辑由 StatusBarManagerService 管理:
-
沉浸式显示 :应用可通过
WindowManager.LayoutParams.FLAG_FULLSCREEN隐藏状态栏,或通过View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN实现沉浸式(状态栏透明,应用内容延伸到状态栏下方); -
样式适配:状态栏背景色可通过系统主题修改,图标颜色支持深色/浅色切换(适配不同应用背景);
-
下拉交互 :状态栏支持下拉展开通知中心,核心逻辑由
NotificationCenter处理。
Dock 栏(导航栏)创建流程
Dock 栏(通常指「导航栏」)位于屏幕底部,是 Android 系统的核心导航入口,包含返回、主页、多任务三个核心按键(部分机型支持手势导航,隐藏实体按键)。
其创建流程与状态栏类似,但核心聚焦「交互事件处理」和「窗口位置适配」,核心类为 NavigationBar,源码路径:frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java。
1. 初始化时机:紧随状态栏之后
导航栏与状态栏同属 SystemUI 核心模块,由 SystemUIApplication 加载,启动顺序紧随状态栏之后。核心原因是导航栏需要依赖状态栏的初始化状态,确保系统 UI 显示顺序正确(顶部状态栏先显示,底部导航栏后显示)。
2. 核心创建流程:NavigationBar.onCreate() 详解
导航栏的创建流程同样包含「布局加载」「子视图初始化」「事件绑定」「窗口添加」四个核心步骤,与状态栏的差异在于「布局结构」和「交互逻辑」:
(1)步骤 1:初始化核心成员与系统服务绑定
java
@Override
public void onCreate() {
super.onCreate();
mContext = getContext();
// 绑定 WindowManager(核心,用于添加导航栏窗口)
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
// 绑定 InputManager(用于处理导航按键的触摸事件)
mInputManager = (InputManager) mContext.getSystemService(Context.INPUT_SERVICE);
// 绑定 AMS(用于处理导航事件,如返回、主页对应的 Activity 操作)
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
// 加载导航栏布局
inflateNavigationBarLayout();
// 初始化子视图(返回、主页、多任务按键)
initNavigationBarViews();
// 绑定导航按键事件
bindNavigationButtonEvents();
// 将导航栏添加到 WindowManager
addNavigationBarToWindow();
}
(2)步骤 2:加载导航栏布局(inflateNavigationBarLayout)
导航栏的布局文件是 navigation_bar.xml,位于 frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml,核心结构是「左侧返回键 + 中间主页键 + 右侧多任务键」:
xml
// navigation_bar.xml 核心结构(简化)
<com.android.systemui.navigationbar.NavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/navigation_bar_height"
android:orientation="horizontal"
android:background="@color/navigation_bar_background">
<!-- 左侧:返回键 -->
<ImageButton
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_back"/>
<!-- 中间:主页键 -->
<ImageButton
android:id="@+id/home"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_home"/>
<!-- 右侧:多任务键 -->
<ImageButton
android:id="@+id/recent_apps"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_recent_apps"/>
</com.android.systemui.navigationbar.NavigationBarView>
布局加载核心代码(重点是窗口参数设置):
java
private void inflateNavigationBarLayout() {
// Inflate 布局文件,创建 NavigationBarView 实例
mNavigationBarView = (NavigationBarView) LayoutInflater.from(mContext)
.inflate(R.layout.navigation_bar, null);
// 初始化导航栏窗口参数
mNavigationBarLayoutParams = new WindowManager.LayoutParams();
// 设置窗口类型为系统窗口(层级高,确保显示在最上层)
mNavigationBarLayoutParams.type = WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
// 宽高:宽度全屏,高度为系统定义的导航栏高度
mNavigationBarLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
mNavigationBarLayoutParams.height = getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
// 对齐方式:底部
mNavigationBarLayoutParams.gravity = Gravity.BOTTOM;
// 标志位:无焦点、拦截自身区域的触摸事件
mNavigationBarLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING;
}
(3)步骤 3:初始化子视图(initNavigationBarViews)
初始化三个核心导航按键,并设置默认状态(如禁用、启用):
java
private void initNavigationBarViews() {
// 初始化返回键
mBackButton = mNavigationBarView.findViewById(R.id.back);
// 初始化主页键
mHomeButton = mNavigationBarView.findViewById(R.id.home);
// 初始化多任务键
mRecentAppsButton = mNavigationBarView.findViewById(R.id.recent_apps);
// 初始状态:返回键默认禁用(无前置 Activity 时)
mBackButton.setEnabled(false);
// 监听 Activity 栈变化,动态启用/禁用返回键
mActivityManager.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityResumed(Activity activity) {
// 有前置 Activity 则启用返回键
mBackButton.setEnabled(mActivityManager.getBackStackEntryCount() > 0);
}
});
}
(4)步骤 4:绑定导航按键事件(bindNavigationButtonEvents)
导航栏的核心是交互,需要为每个按键绑定对应的系统操作(如返回键触发 Activity 回退):
java
private void bindNavigationButtonEvents() {
// 1. 返回键点击事件:触发 Activity 回退
mBackButton.setOnClickListener(v -> {
mActivityManager.moveTaskToBack(true); // 回退到上一个 Activity
});
// 2. 主页键点击事件:返回桌面(Launcher)
mHomeButton.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
});
// 3. 多任务键点击事件:打开最近应用列表
mRecentAppsButton.setOnClickListener(v -> {
mActivityManager.showRecentApps(); // 调用 AMS 显示最近应用
});
// 4. 长按事件(可选):如长按主页键打开语音助手
mHomeButton.setOnLongClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
mContext.startActivity(intent);
return true;
});
}
(5)步骤 5:添加导航栏到 WindowManager(addNavigationBarToWindow)
与状态栏类似,导航栏最终通过 WindowManager.addView() 添加到系统窗口:
java
private void addNavigationBarToWindow() {
try {
mWindowManager.addView(mNavigationBarView, mNavigationBarLayoutParams);
mNavigationBarAdded = true;
} catch (WindowManager.BadTokenException e) {
Log.e(TAG, "Failed to add navigation bar to window manager", e);
}
}
3. 导航栏的核心特性:显示隐藏与手势适配
-
动态显示隐藏 :全屏应用(如视频、游戏)可通过
WindowManager.LayoutParams.FLAG_HIDE_NAVIGATION隐藏导航栏,用户滑动屏幕边缘可重新显示; -
手势导航适配 :Android 10+ 支持手势导航,导航栏会隐藏,通过滑动手势替代按键(如从左边缘滑动=返回,底部上滑=主页),核心逻辑由
GestureNavigationBar处理; -
机型适配:不同机型的导航栏布局可能不同(如部分机型有搜索键),通过系统资源文件的「机型适配目录」(如 res/layout-sw600dp/)实现差异化布局。
状态栏与 Dock 栏的协同机制
状态栏与 Dock 栏并非独立存在,而是通过 SystemUI 的核心模块协同工作,确保系统 UI 的一致性和交互连贯性:
-
显示区域协同:两者均为系统窗口,通过 WindowManager 分配显示区域(顶部状态栏 + 底部导航栏),中间区域为应用窗口,确保无重叠;
-
沉浸式协同 :应用进入沉浸式模式时,状态栏和导航栏会同时隐藏或透明,通过
SystemUI的全局状态管理实现同步; -
事件协同:如用户下拉状态栏展开通知中心时,导航栏的触摸事件会被暂时屏蔽,避免交互冲突;
-
样式协同:状态栏和导航栏的背景色、图标颜色通过系统主题统一控制,确保视觉风格一致。
开发与定制注意事项
SystemUI 属于系统级组件,定制或开发相关功能时需注意以下几点:
-
权限限制:SystemUI 运行在系统进程,需要系统签名(platform signature),普通应用无法直接修改或替换状态栏/导航栏;
-
版本差异:不同 Android 版本的 SystemUI 源码结构可能不同(如 Android 12+ 引入了 Material You 设计,布局文件有变化),定制时需适配对应版本;
-
性能影响:状态栏和导航栏的更新频率较高(如时间每秒更新、信号实时变化),避免在监听器中执行耗时操作;
-
兼容性:修改布局或交互逻辑时,需适配不同屏幕尺寸、分辨率和机型,避免出现显示异常。
总结
SystemUI 是 Android 系统级 UI 的核心载体,状态栏和 Dock 栏(导航栏)作为其最基础的两个组件,承担着「系统状态展示」和「全局导航交互」的核心职责。
两者的创建流程遵循「初始化 → 布局加载 → 子视图初始化 → 系统服务绑定 → 窗口添加」的统一逻辑,核心依赖 WindowManager 实现系统窗口的显示,依赖各类系统服务(AMS、TelephonyManager、BatteryManager 等)获取状态数据和处理交互事件。
