@@ -3,7 +3,7 @@ package cc.winboll.studio.positions.utils;
/**
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/09/30 16:09
* @Date 2025/09/30 16:09
* @Describe NotificationUtils
* @Describe NotificationUtils( 适配API 30, 修复系统默认铃声获取, 任务通知循环响铃)
*/
*/
import android.app.Notification ;
import android.app.Notification ;
import android.app.NotificationChannel ;
import android.app.NotificationChannel ;
@@ -11,175 +11,183 @@ import android.app.NotificationManager;
import android.app.PendingIntent ;
import android.app.PendingIntent ;
import android.content.Context ;
import android.content.Context ;
import android.content.Intent ;
import android.content.Intent ;
import android.media.RingtoneManager ; // 导入RingtoneManager( 关键: 用于获取系统默认铃声)
import android.net.Uri ; // 导入Uri( 存储铃声路径)
import android.os.Build ;
import android.os.Build ;
import androidx.core.app.NotificationCompat ;
import androidx.core.app.NotificationCompat ;
import cc.winboll.studio.positions.R ;
import cc.winboll.studio.positions.R ;
import cc.winboll.studio.positions.activities.LocationActivity ; // 引入你的前台服务类
import cc.winboll.studio.positions.activities.LocationActivity ;
/**
/**
* 通知栏工具类:专注于任务相关通知的显示与点击跳转 + 前台服务通知管理
* 通知栏工具类:
* 核心功能:
* 1. 任务通知: 铃声循环播放( 适配API 30) , 修复系统默认铃声获取方式
* 1 . 显示任务描述通知, 点击后携带positionId/taskId跳转到LocationActivity
* 2 . 前台服务通知: 低打扰( 无声无震动) , 符合API 30规范
* 2. 创建前台服务通知( 用于DistanceRefreshService保活, 符合系统前台服务规范)
*/
*/
public class NotificationUtil {
public class NotificationUtil {
public static final String TAG = " NotificationUtils " ;
public static final String TAG = " NotificationUtils " ;
// 1. 任务通知相关 常量(原有 )
// 任务通知常量(独立渠道,确保循环铃声配置不冲突 )
private static final String TASK_NOTIFICATION_CHANNEL_ID = " task_notification_channel_01 " ;
private static final String TASK_NOTIFICATION_CHANNEL_ID = " task_notification_channel_01 " ;
private static final String TASK_NOTIFICATION_CHANNEL_NAME = " 任务通知 " ;
private static final String TASK_NOTIFICATION_CHANNEL_NAME = " 任务通知(循环铃声) " ;
// 2. 前台服务通知新增 常量(独立渠道,避免与普通任务通知混淆 )
// 前台服务通知常量(独立渠道,低打扰 )
private static final String FOREGROUND_SERVICE_CHANNEL_ID = " foreground_location_service_channel_02 " ;
private static final String FOREGROUND_SERVICE_CHANNEL_ID = " foreground_location_service_channel_02 " ;
private static final String FOREGROUND_SERVICE_CHANNEL_NAME = " 位置服务 " ;
private static final String FOREGROUND_SERVICE_CHANNEL_NAME = " 位置服务 " ;
public static final int FOREGROUND_SERVICE_NOTIFICATION_ID = 10086 ; // 固定ID( 前台服务通知无需动态生成)
public static final int FOREGROUND_SERVICE_NOTIFICATION_ID = 10086 ; // 固定前台服务通知ID
// ---------------------- 原有功能 :任务通知(不变 ) ----------------------
// ---------------------- 核心 :任务通知(循环响铃+修复系统默认铃声 ) ----------------------
private static int getNotificationId ( String taskId ) {
private static int getNotificationId ( String taskId ) {
return taskId . hashCode ( ) & 0xFFFFFF ;
return taskId . hashCode ( ) & 0xFFFFFF ; // 确保通知ID唯一且非负
}
}
public static void show ( Context context , String taskId , String positionId , String taskDescription ) {
public static void show ( Context context , String taskId , String positionId , String taskDescription ) {
if ( context = = null | | taskId = = null | | positionId = = null ) {
if ( context = = null | | taskId = = null | | positionId = = null ) {
return ;
return ;
}
}
NotificationManager notificationManager =
NotificationManager notificationManager =
( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
if ( notificationManager = = null ) {
if ( notificationManager = = null ) {
return ;
return ;
}
}
createNotificationChannel ( notificationManager ) ;
// 1. 初始化通知渠道(配置循环铃声参数,使用修复后的铃声获取方式)
createNotificationChannel ( notificationManager ) ;
Intent jumpIntent = new Intent ( context , LocationActivity . class ) ;
// 2. 点击跳转Intent( 携带任务/位置参数, 适配API 30页面栈)
jumpIntent . putExtra ( " EXTRA_POSITION_ID " , positionId ) ;
Intent jumpIntent = new Intent ( context , LocationActivity . class ) ;
jumpIntent . putExtra ( " EXTRA_TASK _ID " , task Id) ;
jumpIntent . putExtra ( " EXTRA_POSITION _ID " , position Id) ;
jumpIntent . setFlags ( Intent . FLAG_ACTIVITY_CLEAR_TOP | Intent . FLAG_ACTIVITY_SINGLE_TOP ) ;
jumpIntent . putExtra ( " EXTRA_TASK_ID " , taskId ) ;
jumpIntent . setFlags ( Intent . FLAG_ACTIVITY_CLEAR_TOP | Intent . FLAG_ACTIVITY_SINGLE_TOP ) ;
PendingIntent pendingIntent = PendingIntent . getActivity (
// 3. PendingIntent( API 30强制加IMMUTABLE, 避免安全异常)
context ,
PendingIntent pendingIntent = PendingIntent . getActivity (
getNotificationId ( taskId ) ,
context ,
jumpIntent ,
getNotificationId ( taskId ) ,
Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ?
jumpIntent ,
PendingIntent . FLAG_IMMUTABLE | PendingIntent . FLAG_UPDATE_CURRENT :
PendingIntent . FLAG_IMMUTABLE | PendingIntent . FLAG_UPDATE_CURRENT
PendingIntent . FLAG_UPDATE_CURRENT
) ;
) ;
Notification notification = new NotificationCompat . Builder ( context , TASK_NOTIFICATION_CHANNEL_ID )
// 4. 构建通知(核心:循环响铃+修复的系统默认铃声)
. setSmallIcon ( R . mipmap . ic_launcher )
NotificationCompat . Builder builder = new NotificationCompat . Builder ( context , TASK_NOTIFICATION_CHANNEL_ID )
. setContentTitle ( " 任务提醒 " )
. setSmallIcon ( R . mipmap . ic_launcher ) // API 30强制要求, 否则通知不显示
. setContentText ( taskDescription )
. setContentTitle ( " 任务提醒 " )
. setContentInten t ( pendingIntent )
. setContentTex t ( taskDescription )
. setAutoCancel ( true )
. setContentIntent ( pendingIntent )
. setPriority ( NotificationCompat . PRIORITY_DEFAULT )
. setAutoCancel ( true ) // 点击后取消通知,停止循环响铃
. setDefaults ( NotificationCompat . DEFAULT_SOUND )
. setPriority ( NotificationCompat . PRIORITY_DEFAULT ) // 确保铃声能正常播放( API 30规则)
. build ( ) ;
//.setVibrationPattern(new long[]{0, 300, 200, 300}) // 震动与铃声同步循环
. setOnlyAlertOnce ( false ) ; // 重复通知也触发循环提醒
notificati onManager. notify ( getNotificationId ( taskId ) , notification ) ;
// 关键修复: 用Ringt one Manager获取系统默认通知铃声( 替代废弃的NotificationManager.getDefaultUri)
}
Uri defaultNotificationRingtone = RingtoneManager . getDefaultUri ( RingtoneManager . TYPE_NOTIFICATION ) ;
if ( defaultNotificationRingtone ! = null ) {
builder . setSound ( defaultNotificationRingtone ) ; // 设置系统默认铃声
} else {
builder . setDefaults ( NotificationCompat . DEFAULT_SOUND ) ; // 极端情况: 铃声Uri为空时, 用默认提醒音兜底
}
private static void createNotificationChannel ( NotificationManager notificationManager ) {
// 5. 循环响铃核心: 设置FLAG_INSISTENT( 通知未取消则持续循环)
if ( Build . VERSION . SDK_INT > = B uild. VERSION_CODES . O ) {
Notification notification = b uilder . build ( ) ;
// 原有:任务通知渠道
notification . flags | = Notification . FLAG_INSISTENT ;
NotificationChannel taskChannel = new NotificationChannel (
TASK_NOTIFICATION_CHANNEL_ID ,
TASK_NOTIFICATION_CHANNEL_NAME ,
NotificationManager . IMPORTANCE_DEFAULT
) ;
taskChannel . setDescription ( " 接收任务相关提醒,点击可查看任务详情 " ) ;
taskChannel . enableVibration ( true ) ;
taskChannel . setVibrationPattern ( new long [ ] { 0 , 300 } ) ;
// 新增: 前台服务通知渠道( 重要性设为LOW, 避免频繁打扰用户 )
// 6. 显示通知(触发循环响铃 )
N otificationChannel foregroundChannel = new N otificationChannel (
n otificationManager . notify ( getNotificationId ( taskId ) , n otification) ;
FOREGROUND_SERVICE_CHANNEL_ID ,
}
FOREGROUND_SERVICE_CHANNEL_NAME ,
NotificationManager . IMPORTANCE_LOW // 仅通知栏显示,无提示音/震动,符合后台服务低打扰需求
) ;
foregroundChannel . setDescription ( " 位置服务运行中, 用于后台持续获取GPS数据, 关闭会影响定位功能 " ) ; // 明确告知用户服务作用
foregroundChannel . enableVibration ( false ) ; // 前台服务通知不震动(避免打扰)
foregroundChannel . setSound ( null , null ) ; // 关闭提示音(低打扰)
// 注册两个渠道(任务+前台服务)
// ---------------------- 核心: 创建通知渠道( 修复铃声配置, 适配API 30) ----------------------
notificationManager . createNotificationChannel( taskChannel ) ;
private static void createNotificationChannel( NotificationManager notificationManager ) {
notificationManager . createNotificationChannel ( foregroundChannel ) ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . O ) {
}
// 1. 任务通知渠道(用修复后的方式配置系统默认铃声)
}
NotificationChannel taskChannel = new NotificationChannel (
TASK_NOTIFICATION_CHANNEL_ID ,
TASK_NOTIFICATION_CHANNEL_NAME ,
NotificationManager . IMPORTANCE_DEFAULT // 重要性≥DEFAULT, 否则铃声不响( API 30规则)
) ;
taskChannel . setDescription ( " 任务提醒通知,铃声循环播放至点击取消 " ) ;
taskChannel . enableVibration ( true ) ;
taskChannel . setVibrationPattern ( new long [ ] { 0 , 300 , 200 , 300 } ) ;
taskChannel . setAllowBubbles ( false ) ; // 避免气泡打断循环铃声
public static void cancel ( Context context , String taskId ) {
// 关键修复: 渠道铃声也用RingtoneManager获取( 与通知Builder保持一致, 确保铃声统一)
if ( context = = null | | taskId = = null ) {
Uri channelDefaultRingtone = RingtoneManager . getDefaultUri ( RingtoneManager . TYPE_NOTIFICATION ) ;
return ;
taskChannel . setSound ( channelDefaultRingtone , null ) ; // 绑定系统默认铃声到渠道
}
NotificationManager notificationManager =
( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
if ( notificationManager ! = null ) {
notificationManager . cancel ( getNotificationId ( taskId ) ) ;
}
}
// ---------------------- 新增核心功能: 创建前台服务通知( 供DistanceRefreshService调用) ----------------------
// 2. 前台服务渠道(低打扰,无声无震动)
/**
NotificationChannel foregroundChannel = new NotificationChannel (
* 创建前台服务通知( 符合Android前台服务规范, 用于启动/保活DistanceRefreshService)
FOREGROUND_SERVICE_CHANNEL_ID ,
* @param context 服务上下文( 直接传入DistanceRefreshService的this即可)
FOREGROUND_SERVICE_CHANNEL_NAME ,
* @param serviceStatus 服务状态文本( 如“正在后台获取GPS位置...”,动态展示服务状态)
NotificationManager . IMPORTANCE_LOW
* @return 可直接用于startForeground()的Notification对象
) ;
*/
foregroundChannel . setDescription ( " 位置服务运行中,无声音/震动提醒 " ) ;
public static Notification createForegroundServiceNotification ( Context context , String serviceStatus ) {
foregroundChannel . enableVibration ( false ) ;
// 安全校验:上下文非空(避免服务中调用时的空指针)
foregroundChannel . setSound ( null , null ) ; // 明确关闭铃声
if ( context = = null ) {
foregroundChannel . setShowBadge ( false ) ;
throw new IllegalArgumentException ( " Context cannot be null for foreground service notification " ) ;
}
// 步骤1: 初始化通知管理器( 复用已有逻辑, 确保渠道已创建 )
// 注册渠道( API 30覆盖旧配置, 确保修复后的铃声生效 )
N otificationManager notificationManager =
n otificationManager. createNotificationChannel ( taskChannel ) ;
( N otificationManager) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
n otificationManager. createNotificationChannel ( foregroundChannel ) ;
if ( notificationManager ! = null ) {
}
createNotificationChannel ( notificationManager ) ; // 确保前台服务渠道已注册
}
}
// 步骤2: 构建“点击通知跳转至位置管理页”的Intent( 用户点击通知可进入功能页)
// ---------------------- 原有功能:取消任务通知(停止循环响铃) ----------------------
Intent jumpIntent = new I nten t( context , LocationActivity . class ) ;
public static void cancel ( Co ntex t context , String taskId ) {
jumpIntent . setFlags ( I nten t. FLAG_ACTIVITY_CLEAR_TOP | Intent . FLAG_ACTIVITY_SINGLE_TOP ) ; // 避免创建重复页面
if ( co ntex t = = null | | taskId = = null ) {
return ;
}
NotificationManager notificationManager =
( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
if ( notificationManager ! = null ) {
notificationManager . cancel ( getNotificationId ( taskId ) ) ; // 取消后循环铃声自动停止
}
}
// 步骤3: 创建PendingIntent( 授权系统在用户点击时执行跳转)
// ---------------------- 前台服务通知( 适配API 30, 无声无震动) ----------------------
PendingI nten t pendingI nten t = PendingIntent . getActivity (
public static Notification createForegroundServiceNotification ( Co ntex t co ntex t, String serviceStatus ) {
context ,
if ( context = = null ) {
FOREGROUND_SERVICE_NOTIFICATION_ID , // 请求码与通知ID一致, 确保唯一
throw new IllegalArgumentException ( " Context cannot be null for foreground service notification " ) ;
jumpIntent ,
}
// 适配Android 6.0+: IMMUTABLE确保安全性, UPDATE_CURRENT确保Intent参数更新
Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ?
PendingIntent . FLAG_IMMUTABLE | PendingIntent . FLAG_UPDATE_CURRENT :
PendingIntent . FLAG_UPDATE_CURRENT
) ;
// 步骤4: 构建前台服务通知( 低打扰、强关联服务状态 )
// 确保前台服务渠道已创建(低打扰配置 )
return new NotificationCompat . Builder ( context , FOREGROUND_SERVICE_CHANNEL_ID )
NotificationManager notificationManager =
. setSmallIcon ( R . mipmap . ic_launcher ) // 必须设置(系统强制要求,建议用应用图标)
( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
. setContentTitle ( " 位置服务运行中 " ) // 固定标题,用户快速识别服务类型
if ( notificationManager ! = null ) {
. setContentText ( serviceStatus ) // 动态内容( 如“正在获取GPS位置”“已连续运行30分钟”)
createNotificationChannel ( notificationManager ) ;
. setContentIntent ( pendingIntent ) // 点击跳转至功能页
}
. setOngoing ( true ) // 关键:设置为“不可手动清除”(仅服务停止时能取消,符合前台服务规范)
. setPriority ( NotificationCompat . PRIORITY_LOW ) // 低优先级:不弹窗、不抢占通知栏焦点
. setDefaults ( NotificationCompat . DEFAULT_SOUND )
. build ( ) ;
}
/**
// 点击跳转Intent
* ( 配套工具方法) 更新前台服务通知的状态文本( 如GPS获取进度、运行时长)
Intent jumpIntent = new Intent ( context , LocationActivity . class ) ;
* @param context 服务上下文
jumpIntent . setFlags ( Intent . FLAG_ACTIVITY_CLEAR_TOP | Intent . FLAG_ACTIVITY_SINGLE_TOP ) ;
* @param newServiceStatus 新的状态文本( 如“已获取最新位置: 北纬30.123°”)
*/
// PendingIntent( API 30必加IMMUTABLE)
public static void updateForegroundServiceStatus ( Co ntex t co ntex t, String newServiceStatus ) {
PendingI nten t pendingI nten t = PendingIntent . getActivity (
if ( context = = null ) {
context ,
return ;
FOREGROUND_SERVICE_NOTIFICATION_ID ,
}
jumpIntent ,
// 重新创建通知( 复用create方法, 传入新状态文本)
PendingIntent . FLAG_IMMUTABLE | PendingIntent . FLAG_UPDATE_CURRENT
Notification updatedNotification = createForegroundServiceNotification ( context , newServiceStatus ) ;
) ;
// 用相同ID更新通知( 覆盖旧通知, 实现状态刷新)
NotificationManager notificationManager =
// 构建前台服务通知(无声无震动,符合低打扰)
( NotificationManager ) context . getSystemService ( C ontext. NOTIFICATION_SERVICE ) ;
return new NotificationCompat . Builder ( c ontext, FOREGROUND_SERVICE_CHANNEL_ID )
if ( notificationManager ! = null ) {
. setSmallIcon ( R . mipmap . ic_launcher )
notificationManager . notify ( FOREGROUND_SERVICE_NOTIFICATION_ID , updatedNotification ) ;
. setContentTitle ( " 位置服务运行中 " )
}
. setContentText ( serviceStatus )
}
. setContentIntent ( pendingIntent )
. setOngoing ( true ) // 不可手动清除(前台服务规范)
. setPriority ( NotificationCompat . PRIORITY_LOW )
. setDefaults ( 0 ) // 禁用所有默认提醒(无声无震动)
. build ( ) ;
}
// ---------------------- 前台服务通知状态更新( 适配API 30) ----------------------
public static void updateForegroundServiceStatus ( Context context , String newServiceStatus ) {
if ( context = = null ) {
return ;
}
Notification updatedNotification = createForegroundServiceNotification ( context , newServiceStatus ) ;
NotificationManager notificationManager =
( NotificationManager ) context . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
if ( notificationManager ! = null ) {
notificationManager . notify ( FOREGROUND_SERVICE_NOTIFICATION_ID , updatedNotification ) ;
}
}
}
}