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