在 Android 12+ 设备上,在屏幕底部绘制一条遮罩,与顶部系统状态栏(挖孔/刘海隐藏后)形成视觉对称。专为无法接受挖孔屏、开启"隐藏刘海"后觉得头重脚低不对称不舒服的用户设计。
- 视觉对称:底部遮罩与状态栏等高,上下呼应
- 手势导航友好:默认仅覆盖手势条高度,避免遮挡底部导航按钮
- R角匹配:遮罩顶部两端弧形延伸,与手机物理圆角视觉呼应
- 可自定义颜色:预设(纯黑) + 十六进制 + RGB 滑块
- 信息显示模块(可选):黑条化身"第二状态栏",与顶部状态栏形成功能对称
- 时间组:时钟(跟随系统 12/24 制)、日期/星期
- 电池组:电量%(充电时附闪电图标)、电池温度、电池电压、低电量变色提醒(可选呼吸动画)
- 系统指标组:CPU 温度、CPU 负载(频率法估算)、内存使用率、可用存储(每 3 秒刷新)
- 自定义文字(独占模式):独占整行显示用户文字,与系统信息互斥
- 白名单:指定 App 在前台时自动隐藏遮罩
- 无障碍服务保活:系统级高优先级,不易被后台清理杀死
- 开机自启:开启无障碍后,无需额外配置
- 常驻通知(可选):默认关闭,用户可选择开启
直接在 releases 下载安装即可。
我的开发机是小米12S的MIUI14.0.3.0版本,其他品牌型号不一定通用,请自测和反馈,谢谢。
MIUI默认有两种系统导航方式:
- 经典导航栏
- 全面屏手势
- APP截图和双刘海屏设定
- 2026-07-05 新增了底部黑条可显示一些内容,查看 信息显示模块
MIUI下如果开启电池信息后底部黑条没有显示,可以先熄屏后再点亮屏幕后再看。
| 项次 | 版本 |
|---|---|
| Android SDK | 31 (Android 12) 及以上 |
| JDK | 17+(推荐 21) |
| Gradle Wrapper | 项目自带 |
| 设备 | Android 12+ 真机 |
.
├── app/
│ ├── build.gradle.kts # 编译配置(签名 / R8 / Compose)
│ ├── proguard-rules.pro # 混淆规则
│ └── src/main/
│ ├── AndroidManifest.xml # 权限 + 服务声明
│ ├── kotlin/com/swm/navi_overlay/
│ │ ├── MainActivity.kt # Compose UI(设置主页 + 颜色选择器 + 信息显示卡片 + 弹窗)
│ │ ├── WhitelistScreen.kt # 白名单管理页
│ │ ├── OverlayA11yService.kt # 核心:无障碍服务(遮罩绘制 + 前台检测 + 横竖屏 + 电量/时钟/指标采集)
│ │ ├── BarView.kt # 自定义 View(4 层渲染 pipeline + 信息显示双模式渲染)
│ │ ├── SystemMetrics.kt # 系统性能指标采集(CPU 温度/使用率、内存、存储,零权限)
│ │ ├── OverlayPrefs.kt # SharedPreferences 持久化
│ │ ├── NavUtils.kt # 导航栏 / 状态栏 / 屏幕 R 角检测
│ │ ├── A11yUtils.kt # 无障碍开启检测 / 设置跳转
│ │ ├── ManufacturerUtils.kt # 厂商识别 + 后台保活引导文案
│ │ ├── NoticeHelper.kt # 可选常驻通知
│ │ ├── NoticeActionReceiver.kt # 通知按钮(暂停 / 恢复 / 停止)
│ │ └── BootReceiver.kt # 开机自启兜底
│ └── res/
│ ├── xml/accessibility_service_config.xml
│ └── values/strings.xml
├── gradlew / gradlew.bat # Gradle Wrapper
├── local.properties # SDK 路径 + 签名配置(不提交 git)
├── navi-overlay.jks # Release 签名密钥(不提交 git)
├── settings.gradle.kts
└── build.gradle.kts
# 安装到 USB 连接的真机
gradlew.bat installDebug
# 启动
adb shell am start -n com.swm.navi_overlay/.MainActivity
# 查看日志
adb logcat | findstr "com.swm.navi_overlay"- 打开 App → 点击顶栏刷新按钮
- 开启无障碍服务(系统会自动跳转设置页)
- 这是核心,提供保活 + 开机自启 + 前台 App 检测
- (可选)授予"显示在其他应用上层" ← 使用 TYPE_ACCESSIBILITY_OVERLAY 后不再强制要求
- 参考状态栏高度设置黑条高度(卡片底部显示"状态栏高度:37 dp",点击「设为同步」)
- 调整牛角弧半径使顶部弧度与手机物理 R 角协调
- 选择遮罩颜色(默认纯黑)
- 点 「启动黑条」
- (推荐) 按引导锁定后台(防止厂商激进杀进程)
Windows(PowerShell)
# 找到 keytool(JDK 自带,通常在 JAVA_HOME\bin 下)
& "$env:JAVA_HOME\bin\keytool.exe" -genkey -v `
-keystore navi-overlay.jks `
-keyalg RSA -keysize 2048 -validity 10000 `
-alias navi-overlay `
-storepass 你的密钥库密码 `
-keypass 你的密钥密码 `
-dname "CN=你的名字, OU=Dev, O=个人, L=城市, ST=省份, C=CN"Windows(CMD)
"%JAVA_HOME%\bin\keytool.exe" -genkey -v ^
-keystore navi-overlay.jks ^
-keyalg RSA -keysize 2048 -validity 10000 ^
-alias navi-overlay ^
-storepass 你的密钥库密码 ^
-keypass 你的密钥密码 ^
-dname "CN=你的名字, OU=Dev, O=个人, L=城市, ST=省份, C=CN"Linux / macOS(Bash)
keytool -genkey -v \
-keystore navi-overlay.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias navi-overlay \
-storepass 你的密钥库密码 \
-keypass 你的密钥密码 \
-dname "CN=你的名字, OU=Dev, O=个人, L=城市, ST=省份, C=CN"说明:
keytool是 JDK 自带工具,无需额外安装。如果找不到,先确认JAVA_HOME环境变量已配置。
-validity 10000表示证书有效期约 27 年。
生成 .jks 文件后,在项目根目录的 local.properties 中填入签名信息:
sdk.dir=D\:\\DevEnv\\Android\\Sdk
# Release 签名配置
release.keyAlias=navi-overlay
release.keyPassword=你的密钥密码
release.storePassword=你的密钥库密码
release.storeFile=navi-overlay.jks安全提示:
local.properties和navi-overlay.jks已在.gitignore中,不会被提交到 Git- 如果换设备开发,需同时备份
.jks文件和密码。密钥丢失后将无法对同一包名签发更新
# Windows(PowerShell / CMD)
gradlew.bat assembleRelease
# Linux / macOS
./gradlew assembleRelease
### 如果没有gradle-wrapper.jar,先下载
# 1. 确保目录存在
mkdir -Force gradle\wrapper
# 2. 使用 PowerShell 下载 gradle-wrapper.jar
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/gradle/gradle/v8.14.0/gradle/wrapper/gradle-wrapper.jar" -OutFile "gradle\wrapper\gradle-wrapper.jar"
# 如果上面不行,试试这个镜像:
# Invoke-WebRequest -Uri "https://repo1.maven.org/maven2/org/gradle/gradle-wrapper/8.14/gradle-wrapper-8.14.jar" -OutFile "gradle\wrapper\gradle-wrapper.jar"| 平台 | 路径 |
|---|---|
| Windows / Linux | app/build/outputs/apk/release/app-release.apk |
APK 体积约 2.5MB(R8 混淆 + 资源压缩 + Compose)。
# ADB 安装(所有平台通用)
adb install -r app/build/outputs/apk/release/app-release.apk发布新版本前,修改 app/build.gradle.kts 中的版本号:
defaultConfig {
versionCode = 2 // 整数,每次发布 +1
versionName = "0.1.0-beta.1" // 展示给用户的版本号
}| 阶段 | 体积 | 说明 |
|---|---|---|
| Debug(未混淆) | ~55MB | 含调试符号、完整 Compose 运行时 |
| Release(R8 未开) | ~40MB | 仅去除调试信息 |
| Release(R8 + 资源压缩) | ~2.5MB | 当前方案 |
- JDK 版本:项目
compileOptions指定 Java 17,推荐使用 JDK 17 或 21 编译。如果 keytool 报版本不兼容,检查gradle.properties中的org.gradle.jvmargs - Gradle 版本:需使用项目自带的 Gradle Wrapper(
gradlew/gradlew.bat),不要用系统全局 Gradle - 签名警告:Release 构建必须成功签名。如果
local.properties中的密钥路径或密码有误,构建会失败 - 首次构建耗时:首次
assembleRelease会下载 Compose BOM 依赖(约 5~10 分钟),后续构建利用缓存只需数秒
遮罩的颜色由 4 层叠加决定,排查透明度问题需逐层检查:
┌────────────────────────────────────────────────────┐
│ 1. Surface 像素格式 (PixelFormat) │
│ 牛角关闭 → RGBX_8888(无 alpha 通道,不可混合) │
│ 牛角开启 → RGBA_8888(有 alpha,允许透明) │
│ ↑ SurfaceFlinger 合成器根据这层决定是否做 α 混合 │
├────────────────────────────────────────────────────┤
│ 2. View 背景 (setBackgroundColor) │
│ 牛角关闭 → 遮罩颜色(不透明) │
│ 牛角开启 → TRANSPARENT(Path 决定填充区域) │
│ ↑ 系统在 onDraw 之前先画这层 │
├────────────────────────────────────────────────────┤
│ 3. Canvas 绘制 (onDraw → drawColor) │
│ canvas.drawColor(color) 填充整个 canvas / clip 区域│
│ ↑ 最终像素色值 │
├────────────────────────────────────────────────────┤
│ 4. SurfaceFlinger 合成 (α / dimAmount) │
│ params.alpha / params.dimAmount │
│ ↑ 系统层级的透明度控制 │
└────────────────────────────────────────────────────┘
| 类型 | 值 | 需要权限 | α 控制 | 适用场景 |
|---|---|---|---|---|
TYPE_APPLICATION_OVERLAY |
2038 | SYSTEM_ALERT_WINDOW |
系统可能强制降 α | 普通悬浮窗 |
TYPE_ACCESSIBILITY_OVERLAY |
2032 | BIND_ACCESSIBILITY_SERVICE(无障碍已自带) |
系统不干预 | 当前使用 |
在 MIUI/澎湃 OS 上实测,TYPE_APPLICATION_OVERLAY 被系统强制设 alpha≈0.8,导致遮罩半透明。改用 TYPE_ACCESSIBILITY_OVERLAY 后恢复正常 alpha=1.0。
●(0,0) ●(w,0) ← 牛角尖
╲ ╱
╲ ← 圆弧(圆心在 (r,0),屏幕内侧) ╱
●(r,r) ──── 直线 ──── ●(w-r,r) ← 与矩形主体连接
████████████████████████████████████████
█████████████ 矩形主体 █████████████
████████████████████████████████████████
- 圆心
(r, 0)和(w-r, 0)—— 均在屏幕内侧(x > 0) - 半径
r= 牛角弧半径(0~60dp 可调),建议等于手机物理 R 角 - 弧从尖角
(0, 0)逆时针扫 90° 到(r, r) - 圆角开启时 View 总高 = 黑条高度 + 牛角半径
优先级 1: Display.getRoundedCorner(POSITION_BOTTOM_LEFT) ← API 33+,最准确
优先级 2: 系统资源 rounded_corner_radius / _bottom / config_roundedCornerRadius
优先级 3: 返回 0(用户手动调整)
默认纯黑 #000000。用户可通过以下方式自定义:
- 预设色块:纯黑 / 炭灰 / 靛蓝 / 墨绿 / 深咖 / 深紫
- 十六进制输入:
#RRGGBB - RGB 滑块弹窗:点击预览色块或「更多...」按钮
颜色值持久化在 SharedPreferences 中,重启后恢复。
黑条除了纯遮罩,还可作为"第二状态栏"显示系统信息,与顶部状态栏形成功能对称。
BarView 渲染时分两种独立模式,由 UI 层强制互斥(开启任一会自动关闭另一):
| 模式 | 触发条件 | 布局 | 字号 |
|---|---|---|---|
| 自定义文字模式 | showCustomText && customText 非空 |
整行居中独占 | bodyH × 52%(较大) |
| 系统信息模式 | 任一信息开关打开 | 左区(时钟+日期)+ 右区(指标组) | bodyH × 42%(紧凑) |
| 两者均关闭 | — | 退化为按键定位点或纯遮罩 | — |
若因旧数据导致两者同时为真,
BarView的when块让自定义文字模式优先(防御性处理)。
[HH:mm 12/5 周日] [32G 62% 23% 32° 41° 4.2V⚡45%]
└── 左区 ──────────┘ └──────────── 右区 ────────────┘
时间(白) 日期(浅灰) 系统组 CPU组 电量组
右区指标从右到左排列(与系统状态栏习惯一致,最常看的电量贴近右边缘)。
| 指标 | 颜色 | 色值 |
|---|---|---|
| 时钟 / 电量 / 自定义文字 | 白 | rgba(225,255,255) |
| 日期 | 浅灰 | rgba(195,200,200) |
| 电池温度 | 暖橙 | rgba(225,255,167,38) |
| 电池电压 | 青 | rgba(225,128,222,234) |
| CPU 温度 | 蓝 | rgba(225,100,181,246) |
| CPU 负载 | 紫 | rgba(225,149,117,205) |
| 内存使用率 | 绿 | rgba(225,129,199,132) |
| 可用存储 | 黄 | rgba(225,255,213,79) |
| 低电量警告 | 红 | rgba(245,255,90,90) + 整体呼吸叠加 |
| 指标 | 数据源 | 采集方式 | 权限 |
|---|---|---|---|
| 时钟 / 日期 | SimpleDateFormat |
主线程 Handler,对齐到下一分钟边界(~60s) | 无 |
| 电量 / 充电 / 电池温度 / 电池电压 | ACTION_BATTERY_CHANGED 粘性广播 |
BroadcastReceiver |
无 |
| CPU 温度 | /sys/class/thermal/thermal_zoneN/temp |
主线程 Handler,每 3 秒轮询 | 无 |
| CPU 负载 | /sys/devices/system/cpu/cpuN/cpufreq/ |
频率加权比(Σcur / Σmax),每 3 秒轮询 | 无 |
| 内存使用率 | ActivityManager.MemoryInfo |
每 3 秒轮询 | 无 |
| 可用存储 | StatFs 主存储 |
每 3 秒轮询 | 无 |
⚠ 关于"CPU 负载":Android 10+ SELinux 禁止应用读取
/proc/stat和/proc/loadavg,无法计算真实的 CPU 时间占比。本项目退而求其次,用各核心scaling_cur_freq / cpuinfo_max_freq的频率加权比作为负载代理指标。频率不完全等价于使用率(部分调度策略会在低负载时 boost 到最高频),仅作粗略参考。非 root 设备无法获取真实使用率。轮询 Handler 仅在用户启用了任一系统指标且黑条显示时才启动(
needsMetricsPolling()检查),避免空转 I/O 浪费电量。
- 黑条身体高度(= 总高 − 牛角高度)< 22dp 时,信息模块自动隐藏,仅保留遮罩功能;设置页会显示红色提示
/sys/class/thermal不可读的设备(部分模拟器),CPU 温度静默跳过/sys/devices/system/cpu/cpuN/cpufreq/不可读时,CPU 负载静默跳过
当 低电量提醒 开启且电量 ≤ 阈值(默认 20%)且未充电时:
- 电量百分比文字变红
- 若开启
呼吸动画:ValueAnimator(2.5s 周期、AccelerateDecelerateInterpolator)驱动红色叠加层 α 在 60~140 间脉动 View.onDetachedFromWindow自动取消动画,避免泄漏
显示:running=true ∧ 未暂停 ∧ 竖屏 ∧ 前台 App 不在白名单
隐藏:以上任一条件不满足
- 暂停:通知栏或 App 内点击暂停,遮罩立即隐藏
- 横屏:自动隐藏(横屏下刘海在侧边,底部遮罩不对称)
- 白名单:用户在 App 内勾选"不显示"的应用,进入前台时自动隐藏
- 自身 App 前台:不特殊处理,遮罩照常显示(用户可直观看到效果)
基于无障碍服务的 onAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED),实时获取当前前台包名,无额外权限要求。比 UsageStatsManager 轮询更实时、更省电。
无障碍服务在用户系统设置中开启后,系统会在每次开机时自动启动该服务,无需额外 BootReceiver。BootReceiver 仅作为兜底:若用户开启了常驻通知,开机后重新显示通知。
adb shell "dumpsys SurfaceFlinger --layer-list | grep -i -A 15 navi"关注字段:
| 字段 | 正常值 | 异常表现 |
|---|---|---|
alpha= |
1.000000 |
<1 表示系统降低了透明度 |
defaultPixelFormat= |
RGBX_8888 或 RGBA_8888 |
— |
windowType= |
2032(ACCESSIBILITY_OVERLAY) |
2038 表示还在用旧类型 |
isOpaque= |
— | true 更好 |
adb logcat -v time *:S System.err:W | findstr "navi"- Kotlin(100%)
- Jetpack Compose + Material 3(UI)
- AccessibilityService(核心保活 + 前台检测)
- WindowManager(系统级悬浮窗绘制)
- SharedPreferences(持久化)
- R8 + ProGuard(Release 混淆压缩)
本说明文档由 DeepSeek V4 Pro 协助编写。



