更新记录

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\"/>"
]

注意事项

  1. 通知监听权限需要用户在系统设置中手动授权,无法通过代码自动开启
  2. 悬浮窗权限同样需要用户手动授权
  3. 悬浮窗使用 TYPE_APPLICATION_OVERLAY,显示在状态栏下方
  4. 监听仅获取新到达的通知,不会推送历史通知
  5. 部分厂商 ROM 可能会限制后台监听服务的存活,建议配合保活插件使用
  6. setFloatingToggleCallback 是全局回调,同一时间只有一个生效,后设置的覆盖之前的
  7. updateFloatingWindow 不会影响已设置的回调,无需重新绑定

隐私、权限声明

1. 本插件需要申请的系统权限列表:

<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"/>

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。