更新记录
1.0.0(2026-08-02)
● ## sh_NotificationManagement v1.0.0 发布日志
平台:Android(最低 API 21)
权限管理
- 检查/跳转通知监听权限
-
检查/跳转悬浮窗权限
通知监听
- 开启/停止监听 Android 通知栏新消息
- 实时回调:包名、标题、内容
-
查询监听状态
清除通知
- 按包名清除指定应用所有通知
- 按关键词清除匹配通知(忽略大小写)
-
清除通知栏全部通知
悬浮窗胶囊(灵动岛风格)
- 显示/隐藏悬浮窗胶囊,淡入淡出动画
- 点击胶囊展开面板,点击外部或 5 秒自动收起
- 自由拖动胶囊,不会超出屏幕
- 拖到屏幕边缘 3 秒自动缩成圆点
- 短按圆点恢复胶囊,长按圆点展开面板
- 胶囊宽度随内容自适应,始终保持居中
- 面板内置"开启/停止监听"开关按钮
- 面板内置"打开应用"按钮
-
更新胶囊状态文本和通知数量
回调
-
面板开关按钮回调(全局共享,跨页面可用)
自定义
- 自定义面板标题/副标题,支持恢复默认
平台兼容性
uni-app x(5.15)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| × | × | 5.0 | × | × | × |
Sh-NoticeMgmt 插件使用
Android通知管理插件,支持通知栏消息监听、清除通知、悬浮窗胶囊。
平台支持:仅 Android(最低 API 21)
一、功能概述
| 模块 | 说明 |
|---|---|
| 权限管理 | 检查通知监听权限、悬浮窗权限,跳转系统设置页 |
| 通知监听 | 监听 Android 通知栏新消息,实时回调包名、标题、内容 |
| 清除通知 | 按包名、关键词清除指定通知,或一键清除全部通知 |
| 悬浮窗胶囊 | 桌面悬浮窗,支持展开面板、拖动、边缘吸附、监听开关 |
| 自定义面板 | 自定义面板标题/副标题文本 |
二、使用方法
import { NotificationManager } from '@/uni_modules/Sh-NoticeMgmt'
const sh_Not = new NotificationManager()
所有 API 通过 sh_Not 实例调用,多个实例共享同一底层 Service。
三、使用示例
完整示例 uniappx
// #ifdef APP-ANDROID
import { NotificationManager } from '@/uni_modules/Sh-NoticeMgmt'
var sh_Notif = new NotificationManager()
const notifPerm = ref(false)
const overlayPerm = ref(false)
const listening = ref(false)
const notifLogs = ref({} as UTSJSONObject)
const clearPkg = ref('')
const clearKeyword = ref('')
const floatingShowing = ref(false)
const toggleLogs = ref([] as Array<UTSJSONObject>)
const panelTitle = ref('')
const panelSubtitle = ref('')
const click = (id : number) => {
if (id == 0) {
notifPerm.value = sh_Notif.isNotificationPermissionEnabled() as boolean
uni.showToast({ title: notifPerm.value ? '已开启' : '未开启', icon: 'none', position: 'bottom' })
} else if (id == 1) {
sh_Notif.openNotificationPermissionSettings()
} else if (id == 2) {
overlayPerm.value = sh_Notif.isOverlayPermissionEnabled() as boolean
uni.showToast({ title: overlayPerm.value ? '已开启' : '未开启', icon: 'none', position: 'bottom' })
} else if (id == 3) {
sh_Notif.requestOverlayPermission()
} else if (id == 4) {
if (!sh_Notif.isNotificationPermissionEnabled()) {
uni.showToast({ title: '请先开启通知权限', icon: 'none', position: 'bottom' })
return
}
listening.value = true
sh_Notif.startListening((pkg : string, title : string, msg : string) => {
notifLogs.value = { pkg, title, msg }
})
uni.showToast({ title: '监听已开启', position: 'bottom' })
} else if (id == 5) {
listening.value = false
sh_Notif.stopListening()
uni.showToast({ title: '监听已停止', position: 'bottom' })
} else if (id == 6) {
const pkg = clearPkg.value as string
if (pkg == '') {
uni.showToast({ title: '请输入包名', icon: 'none', position: 'bottom' })
return
}
sh_Notif.cancelPackageNotifications(pkg)
uni.showToast({ title: `已清除: ${pkg}`, position: 'bottom' })
} else if (id == 7) {
const kw = clearKeyword.value as string
if (kw == '') {
uni.showToast({ title: '请输入关键词', icon: 'none', position: 'bottom' })
return
}
const pkg = clearPkg.value as string
sh_Notif.cancelByKeyword(kw, pkg != '' ? pkg : null)
uni.showToast({ title: `已清除关键词: ${kw}`, position: 'bottom' })
} else if (id == 8) {
sh_Notif.cancelAllNotifications()
uni.showToast({ title: '已清除所有通知', position: 'bottom' })
} else if (id == 9) {
if (!sh_Notif.isOverlayPermissionEnabled()) {
uni.showToast({ title: '请先开启悬浮窗权限', icon: 'none', position: 'bottom' })
return
}
floatingShowing.value = true
sh_Notif.showFloatingWindow(listening.value as boolean, 0)
sh_Notif.setFloatingToggleCallback((enabled : boolean) => {
const now = new Date()
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
const newLogs = (toggleLogs.value as Array<UTSJSONObject>).slice()
newLogs.unshift({ enabled: enabled, time: time } as UTSJSONObject)
toggleLogs.value = newLogs.slice(0, 20)
if (enabled) {
if (!sh_Notif.isNotificationPermissionEnabled()) {
uni.showToast({ title: '请先开启通知权限', icon: 'none', position: 'bottom' })
return
}
listening.value = true
sh_Notif.startListening((pkg : string, title : string, msg : string) => {
notifLogs.value = { pkg, title, msg }
})
uni.showToast({ title: '监听已开启', position: 'bottom' })
sh_Notif.updateFloatingWindow(listening.value as boolean, 0)
} else if (!enabled && listening.value) {
listening.value = false
sh_Notif.stopListening()
uni.showToast({ title: '监听已停止', position: 'bottom' })
sh_Notif.updateFloatingWindow(listening.value as boolean, 0)
}
})
setTimeout(() => {
floatingShowing.value = sh_Notif.isFloatingWindowShowing() as boolean
uni.showToast({ title: floatingShowing.value ? '已显示' : '未显示', icon: 'none', position: 'bottom' })
}, 500)
} else if (id == 10) {
sh_Notif.hideFloatingWindow()
setTimeout(() => {
floatingShowing.value = sh_Notif.isFloatingWindowShowing() as boolean
uni.showToast({ title: floatingShowing.value ? '已显示' : '已隐藏', icon: 'none', position: 'bottom' })
}, 500)
} else if (id == 11) {
sh_Notif.updateFloatingWindow(listening.value as boolean, 66)
uni.showToast({ title: '胶囊已更新', position: 'bottom' })
} else if (id == 12) {
floatingShowing.value = sh_Notif.isFloatingWindowShowing() as boolean
uni.showToast({ title: floatingShowing.value ? '已显示' : '未显示', icon: 'none', position: 'bottom' })
} else if (id == 13) {
const title = panelTitle.value as string
const subtitle = panelSubtitle.value as string
sh_Notif.setPanelTexts(title != '' ? title : null, subtitle != '' ? subtitle : null)
sh_Notif.updateFloatingWindow(listening.value as boolean, 99)
uni.showToast({ title: '文本已应用', position: 'bottom' })
} else if (id == 14) {
panelTitle.value = ''
panelSubtitle.value = ''
sh_Notif.setPanelTexts(null, null)
sh_Notif.updateFloatingWindow(listening.value as boolean, 99)
uni.showToast({ title: '已恢复默认', position: 'bottom' })
}
}
onShow(() => {
click(0);
click(2);
})
onMounted(() => {
click(0);
click(2);
})
onUnmounted(() => {
if (listening.value) { sh_Notif.stopListening() }
if (floatingShowing.value) { sh_Notif.hideFloatingWindow() }
})
// #endif
清除通知示例
// 清除微信所有通知
sh_Not.cancelPackageNotifications('com.tencent.mm')
// 清除所有含"到账"的通知
sh_Not.cancelByKeyword('到账')
// 只清除微信中含"收款"的通知
sh_Not.cancelByKeyword('收款', 'com.tencent.mm')
// 清除所有通知
sh_Not.cancelAllNotifications()
跨页面回调示例
// ===== app.uvue 中显示悬浮窗 =====
const nmA = new NotificationManager()
nmA.showFloatingWindow(true, 0)
// ===== index.uvue 中设置回调,全局生效 =====
const nmB = new NotificationManager()
nmB.setFloatingToggleCallback((enabled: boolean) => {
if (enabled) {
nmB.startListening((pkg, title, msg) => {
console.log(`[${pkg}] ${title}: ${msg}`)
})
} else {
nmB.stopListening()
}
})
四、API 参考
权限检查与申请
| 方法名 | 说明 | 返回值 | 参数 |
|---|---|---|---|
isNotificationPermissionEnabled() |
检查通知监听权限是否已开启 | boolean |
— |
openNotificationPermissionSettings() |
跳转系统通知使用权限设置页 | void |
— |
isOverlayPermissionEnabled() |
检查悬浮窗权限是否已开启 | boolean |
— |
requestOverlayPermission() |
跳转系统悬浮窗权限设置页 | void |
— |
// 检查通知监听权限
if (!sh_Not.isNotificationPermissionEnabled()) {
sh_Not.openNotificationPermissionSettings()
}
// 检查悬浮窗权限
if (!sh_Not.isOverlayPermissionEnabled()) {
sh_Not.requestOverlayPermission()
}
通知监听
| 方法名 | 说明 | 返回值 | 参数 |
|---|---|---|---|
startListening(callback) |
开启通知监听,新通知到达时回调 | void |
callback: (pkg: string, title: string, msg: string) => void |
stopListening() |
关闭通知监听 | void |
— |
getListeningStatus() |
获取当前监听状态 | boolean |
— |
// 开启监听
sh_Not.startListening((pkg: string, title: string, msg: string) => {
console.log(`包名: ${pkg}`)
console.log(`标题: ${title}`)
console.log(`内容: ${msg}`)
})
// 停止监听
sh_Not.stopListening()
// 查询状态
const status = sh_Not.getListeningStatus()
清除通知
| 方法名 | 说明 | 返回值 | 参数 |
|---|---|---|---|
cancelPackageNotifications(pkg) |
清除指定包名的所有通知 | void |
pkg: string |
cancelByKeyword(keyword, pkg?) |
清除标题或内容含关键词的通知 | void |
keyword: string, pkg?: string \| null |
cancelAllNotifications() |
清除通知栏所有通知 | void |
— |
// 清除指定包名
sh_Not.cancelPackageNotifications('com.tencent.mm')
sh_Not.cancelPackageNotifications('com.eg.android.AlipayGphone')
// 清除关键词匹配(忽略大小写)
sh_Not.cancelByKeyword('到账')
sh_Not.cancelByKeyword('收款', 'com.tencent.mm')
// 清除所有
sh_Not.cancelAllNotifications()
悬浮窗胶囊
| 方法名 | 说明 | 返回值 | 参数 |
|---|---|---|---|
showFloatingWindow(isListening, count) |
显示悬浮窗胶囊 | void |
isListening: boolean(初始监听状态), count: number(初始通知数) |
hideFloatingWindow() |
关闭悬浮窗胶囊 | void |
— |
updateFloatingWindow(isListening, count) |
更新胶囊状态文本和数量 | void |
isListening: boolean, count: number |
isFloatingWindowShowing() |
悬浮窗是否正在显示 | boolean |
— |
// 显示胶囊
sh_Not.showFloatingWindow(true, 0)
// 更新胶囊(收到通知后)
sh_Not.updateFloatingWindow(true, 5)
// 查询状态
const showing = sh_Not.isFloatingWindowShowing()
// 隐藏胶囊
sh_Not.hideFloatingWindow()
回调设置
| 方法名 | 说明 | 返回值 | 参数 |
|---|---|---|---|
setFloatingToggleCallback(callback) |
设置悬浮窗面板开关按钮回调(全局共享) | void |
callback: (enabled: boolean) => void |
sh_Not.setFloatingToggleCallback((enabled: boolean) => {
if (enabled) {
sh_Not.startListening((pkg, title, msg) => {
console.log(`新通知: ${title}`)
})
} else {
sh_Not.stopListening()
}
})
自定义面板文本
| 方法名 | 说明 | 返回值 | 参数 |
|---|---|---|---|
setPanelTexts(title?, subtitle?) |
自定义面板标题/副标题,传 null 恢复默认 |
void |
title?: string \| null, subtitle?: string \| null |
// 自定义
sh_Not.setPanelTexts('我的监听面板', '已监听 3 条消息')
// 只改标题
sh_Not.setPanelTexts('支付通知监听中', null)
// 恢复默认
sh_Not.setPanelTexts(null, null)
五、悬浮窗交互说明
| 操作 | 效果 |
|---|---|
| 点击胶囊 | 顶部展开面板(全屏遮罩,面板居中) |
| 点击面板外空白区域 | 收起面板 |
| 面板展开后 5 秒无操作 | 自动收起 |
| 拖动胶囊 | 自由移动,不会超出屏幕边界 |
| 拖到屏幕边缘停留 3 秒 | 缩成圆点吸附边缘 |
| 短按圆点 | 在当前位置恢复胶囊 |
| 长按圆点(≥ 0.5 秒) | 展开面板 |
| 面板"开启/停止监听"按钮 | 切换监听状态,触发回调 |
| 面板"打开应用"按钮 | 返回 App 主页面 |
| 胶囊内容变化 | 宽度自动适配,向两边均匀扩展保持居中 |
权限声明
[
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />",
"<uses-permission android:name=\"android.permission.BIND_NOTIFICATION_LISTENER_SERVICE\" tools:ignore=\"ProtectedPermissions\" />",
"<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\"/>"
]
注意事项
- 通知监听权限需要用户在系统设置中手动授权,无法通过代码自动开启
- 悬浮窗权限同样需要用户手动授权
- 悬浮窗使用
TYPE_APPLICATION_OVERLAY,显示在状态栏下方 - 监听仅获取新到达的通知,不会推送历史通知
- 部分厂商 ROM 可能会限制后台监听服务的存活,建议配合保活插件使用
setFloatingToggleCallback是全局回调,同一时间只有一个生效,后设置的覆盖之前的updateFloatingWindow不会影响已设置的回调,无需重新绑定

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 0
赞赏 0
下载 12474906
赞赏 1936
赞赏
京公网安备:11010802035340号