更新应用通知渠道设置
This commit is contained in:
@@ -91,7 +91,7 @@ dependencies {
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
|
||||
// WinBoLL库 nexus.winboll.cc 地址
|
||||
api 'cc.winboll.studio:libaes:15.12.12'
|
||||
api 'cc.winboll.studio:libaes:15.12.13'
|
||||
api 'cc.winboll.studio:libappbase:15.14.2'
|
||||
|
||||
// WinBoLL备用库 jitpack.io 地址
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Jan 06 20:25:41 HKT 2026
|
||||
#Wed Jan 07 12:45:14 GMT 2026
|
||||
stageCount=3
|
||||
libraryProject=
|
||||
baseVersion=15.14
|
||||
publishVersion=15.14.2
|
||||
buildCount=0
|
||||
buildCount=6
|
||||
baseBetaVersion=15.14.3
|
||||
|
||||
@@ -104,7 +104,6 @@
|
||||
<!-- 辅助服务:dataSync 类型 -->
|
||||
<service
|
||||
android:name=".services.AssistantService"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:exported="false"
|
||||
android:stopWithTask="false"/>
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
package cc.winboll.studio.contacts.services;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.model.MainServiceBean;
|
||||
import cc.winboll.studio.contacts.utils.NotificationManagerUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
@@ -115,38 +111,38 @@ public class AssistantService extends Service {
|
||||
/**
|
||||
* 创建前台服务通知(Android 8.0+ 必须配置渠道)
|
||||
*/
|
||||
private Notification createForegroundNotification() {
|
||||
// 1. 创建通知渠道(API 26+ 必需)
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
FOREGROUND_CHANNEL_ID,
|
||||
"守护服务",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("守护服务后台运行,保障主服务存活");
|
||||
// 空指针防护
|
||||
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 构建通知(Java 7 分步设置,取消链式调用简化)
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
builder = new Notification.Builder(this, FOREGROUND_CHANNEL_ID);
|
||||
} else {
|
||||
builder = new Notification.Builder(this);
|
||||
}
|
||||
builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
builder.setContentTitle("守护服务运行中");
|
||||
builder.setContentText("正在监控主服务状态");
|
||||
builder.setPriority(Notification.PRIORITY_LOW);
|
||||
builder.setOngoing(true); // 不可手动取消
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
// private Notification createForegroundNotification() {
|
||||
// // 1. 创建通知渠道(API 26+ 必需)
|
||||
// if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
// NotificationChannel channel = new NotificationChannel(
|
||||
// FOREGROUND_CHANNEL_ID,
|
||||
// "守护服务",
|
||||
// NotificationManager.IMPORTANCE_LOW
|
||||
// );
|
||||
// channel.setDescription("守护服务后台运行,保障主服务存活");
|
||||
// // 空指针防护
|
||||
// NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
// if (manager != null) {
|
||||
// manager.createNotificationChannel(channel);
|
||||
// LogUtils.d(TAG, "createForegroundNotification: 通知渠道创建成功");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 2. 构建通知(Java 7 分步设置,取消链式调用简化)
|
||||
// Notification.Builder builder;
|
||||
// if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
// builder = new Notification.Builder(this, FOREGROUND_CHANNEL_ID);
|
||||
// } else {
|
||||
// builder = new Notification.Builder(this);
|
||||
// }
|
||||
// builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
// builder.setContentTitle("守护服务运行中");
|
||||
// builder.setContentText("正在监控主服务状态");
|
||||
// builder.setPriority(Notification.PRIORITY_LOW);
|
||||
// builder.setOngoing(true); // 不可手动取消
|
||||
//
|
||||
// return builder.build();
|
||||
// }
|
||||
|
||||
// ====================== Service 生命周期方法区 ======================
|
||||
@Override
|
||||
@@ -154,22 +150,6 @@ public class AssistantService extends Service {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate: 守护服务创建");
|
||||
|
||||
// 适配 Android 12+ 后台启动限制:应用后台时启动为前台服务
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_12_API) {
|
||||
Notification notification = createForegroundNotification();
|
||||
// 修复:使用 dataSync 类型,添加异常捕获防止崩溃
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_10_API) {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
|
||||
} else {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, notification);
|
||||
}
|
||||
LogUtils.d(TAG, "onCreate: 守护服务已启动为前台服务(dataSync 类型)");
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "onCreate: 启动前台服务失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化主服务连接回调
|
||||
if (mMyServiceConnection == null) {
|
||||
mMyServiceConnection = new MyServiceConnection();
|
||||
|
||||
@@ -2,8 +2,6 @@ package cc.winboll.studio.contacts.services;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -11,11 +9,9 @@ import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.bobulltoon.TomCat;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
import cc.winboll.studio.contacts.handlers.MainServiceHandler;
|
||||
@@ -23,6 +19,7 @@ import cc.winboll.studio.contacts.listenphonecall.CallListenerService;
|
||||
import cc.winboll.studio.contacts.model.MainServiceBean;
|
||||
import cc.winboll.studio.contacts.model.RingTongBean;
|
||||
import cc.winboll.studio.contacts.receivers.MainReceiver;
|
||||
import cc.winboll.studio.contacts.utils.NotificationManagerUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
@@ -247,43 +244,43 @@ public class MainService extends Service {
|
||||
* 创建前台服务通知(Android8.0+需渠道,低版本兼容)
|
||||
* @return Notification 前台服务通知实例
|
||||
*/
|
||||
private Notification createForegroundNotification() {
|
||||
// 1. Android8.0+创建通知渠道(必需,否则通知不显示)
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
FOREGROUND_CHANNEL_ID,
|
||||
"拨号主服务",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("主服务后台运行,保障通话监听与号码识别功能正常");
|
||||
channel.setSound(null, null); // 关闭通知声音
|
||||
channel.enableVibration(false); // 关闭振动
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
if (notificationManager != null) {
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
LogUtils.d(TAG, "createForegroundNotification: Android8.0+通知渠道创建成功");
|
||||
} else {
|
||||
LogUtils.e(TAG, "createForegroundNotification: NotificationManager获取失败,渠道创建失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 构建通知实例(分版本兼容Builder)
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
builder = new Notification.Builder(this, FOREGROUND_CHANNEL_ID);
|
||||
} else {
|
||||
builder = new Notification.Builder(this);
|
||||
}
|
||||
builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
builder.setContentTitle("拨号服务运行中");
|
||||
builder.setContentText("后台保障通话监听与号码识别,请勿手动关闭");
|
||||
builder.setPriority(Notification.PRIORITY_LOW); // 低优先级,不打扰用户
|
||||
builder.setOngoing(true); // 不可手动清除,保障服务存活
|
||||
|
||||
LogUtils.d(TAG, "createForegroundNotification: 前台服务通知构建完成");
|
||||
return builder.build();
|
||||
}
|
||||
// private Notification createForegroundNotification() {
|
||||
// // 1. Android8.0+创建通知渠道(必需,否则通知不显示)
|
||||
// if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
// NotificationChannel channel = new NotificationChannel(
|
||||
// FOREGROUND_CHANNEL_ID,
|
||||
// "拨号主服务",
|
||||
// NotificationManager.IMPORTANCE_LOW
|
||||
// );
|
||||
// channel.setDescription("主服务后台运行,保障通话监听与号码识别功能正常");
|
||||
// channel.setSound(null, null); // 关闭通知声音
|
||||
// channel.enableVibration(false); // 关闭振动
|
||||
//
|
||||
// NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
// if (notificationManager != null) {
|
||||
// notificationManager.createNotificationChannel(channel);
|
||||
// LogUtils.d(TAG, "createForegroundNotification: Android8.0+通知渠道创建成功");
|
||||
// } else {
|
||||
// LogUtils.e(TAG, "createForegroundNotification: NotificationManager获取失败,渠道创建失败");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 2. 构建通知实例(分版本兼容Builder)
|
||||
// Notification.Builder builder;
|
||||
// if (Build.VERSION.SDK_INT >= ANDROID_8_API) {
|
||||
// builder = new Notification.Builder(this, FOREGROUND_CHANNEL_ID);
|
||||
// } else {
|
||||
// builder = new Notification.Builder(this);
|
||||
// }
|
||||
// builder.setSmallIcon(R.drawable.ic_launcher);
|
||||
// builder.setContentTitle("拨号服务运行中");
|
||||
// builder.setContentText("后台保障通话监听与号码识别,请勿手动关闭");
|
||||
// builder.setPriority(Notification.PRIORITY_LOW); // 低优先级,不打扰用户
|
||||
// builder.setOngoing(true); // 不可手动清除,保障服务存活
|
||||
//
|
||||
// LogUtils.d(TAG, "createForegroundNotification: 前台服务通知构建完成");
|
||||
// return builder.build();
|
||||
// }
|
||||
|
||||
/**
|
||||
* 检查指定服务是否正在运行(通过ActivityManager查询)
|
||||
@@ -423,8 +420,8 @@ public class MainService extends Service {
|
||||
|
||||
// 1. 优先启动前台服务(避免前台服务启动超时崩溃,核心优先级)
|
||||
try {
|
||||
Notification foregroundNotification = createForegroundNotification();
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, foregroundNotification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
|
||||
NotificationManagerUtils notificationManagerUtils = new NotificationManagerUtils(this);
|
||||
notificationManagerUtils.startForegroundServiceNotify(this, "主要拨号服务已启动。");
|
||||
LogUtils.i(TAG, "startCoreBusiness: 前台服务启动成功,通知ID=" + FOREGROUND_NOTIFICATION_ID);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "startCoreBusiness: 前台服务启动失败(服务类型不匹配)", e);
|
||||
@@ -463,7 +460,7 @@ public class MainService extends Service {
|
||||
Intent assistantIntent = new Intent(this, AssistantService.class);
|
||||
// Android10+应用后台启动服务需用前台服务模式
|
||||
LogUtils.d(TAG, "wakeupAndBindAssistantService: Android10+,前台服务模式启动守护服务");
|
||||
startForegroundService(assistantIntent);
|
||||
startService(assistantIntent);
|
||||
|
||||
// 绑定守护服务(BIND_IMPORTANT:高优先级绑定,断开时回调)
|
||||
bindService(assistantIntent, mServiceConnection, Context.BIND_IMPORTANT);
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
package cc.winboll.studio.contacts.utils;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.RingtoneManager;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
import cc.winboll.studio.contacts.MainActivity;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* 通知工具类:统一管理前台服务/临时通知
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @CreateTime 2025/12/14 21:01:00
|
||||
* @LastEditTime 2026/01/07 22:00:00
|
||||
* @Describe 适配API30,支持拨号服务前台保活通知、临时通知提醒,提供通知的创建、更新、取消功能;支持通知版本管理,版本变更时自动清理旧渠道
|
||||
*/
|
||||
public class NotificationManagerUtils {
|
||||
// ================================== 静态常量(置顶统一管理,杜绝魔法值)=================================
|
||||
public static final String TAG = "NotificationManagerUtils";
|
||||
// ********** 新增:通知版本管理常量 **********
|
||||
/** 通知版本标识(源码标记版本,变更时会清理旧渠道) */
|
||||
public static final String NOTIFICATION_VERSION = "v1.0.0";
|
||||
/** SP存储键:已保存的通知版本 */
|
||||
private static final String SP_KEY_NOTIFICATION_VERSION = "sp_key_notification_version";
|
||||
/** SP文件名 */
|
||||
private static final String SP_NAME_NOTIFICATION = "sp_notification_manager";
|
||||
// ******************************************
|
||||
// 通知渠道ID(API26+ 必需,区分通知类型)
|
||||
public static final String CHANNEL_ID_FOREGROUND = "cc.winboll.studio.contacts.channel.foreground";
|
||||
public static final String CHANNEL_ID_TEMPORARY = "cc.winboll.studio.contacts.channel.temporary";
|
||||
// 通知ID(唯一标识,避免重复)
|
||||
public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001;
|
||||
public static final int NOTIFY_ID_TEMPORARY = 1002;
|
||||
// 低版本兼容:默认通知图标(API<21 避免显示异常)
|
||||
private static final int NOTIFICATION_DEFAULT_ICON = R.drawable.ic_launcher;
|
||||
// 通知内容兜底常量
|
||||
private static final String FOREGROUND_NOTIFY_TITLE_DEFAULT = "拨号服务前台通知";
|
||||
private static final String FOREGROUND_NOTIFY_CONTENT_DEFAULT = "前台通知内容";
|
||||
private static final String TEMPORARY_NOTIFY_TITLE_DEFAULT = "拨号服务临时通知";
|
||||
private static final String TEMPORARY_NOTIFY_CONTENT_DEFAULT = "临时通知内容";
|
||||
// PendingIntent请求码
|
||||
private static final int PENDING_INTENT_REQUEST_CODE_FOREGROUND = 0;
|
||||
private static final int PENDING_INTENT_REQUEST_CODE_TEMPORARY = 1;
|
||||
// 消息通知自增ID(起始值)
|
||||
private static int snMessageNotificationID = 10000;
|
||||
|
||||
// ================================== 成员变量(私有封装,按依赖优先级排序)=================================
|
||||
// 核心上下文(应用级,避免内存泄漏)
|
||||
private Context mContext;
|
||||
// 系统通知服务(核心依赖)
|
||||
private NotificationManager mNotificationManager;
|
||||
// 前台服务通知实例(单独持有,便于更新/取消)
|
||||
private Notification mForegroundServiceNotify;
|
||||
// ********** 新增:SP实例(用于版本存储) **********
|
||||
private SharedPreferences mSp;
|
||||
|
||||
// ================================== 构造方法(初始化核心资源,前置校验)=================================
|
||||
public NotificationManagerUtils(Context context) {
|
||||
LogUtils.d(TAG, "NotificationManagerUtils() 构造方法调用 | context=" + context);
|
||||
// 前置校验:Context非空
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "NotificationManagerUtils() 构造失败:context is null");
|
||||
return;
|
||||
}
|
||||
// 初始化核心资源
|
||||
this.mContext = context.getApplicationContext();
|
||||
this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
// ********** 新增:初始化SP **********
|
||||
this.mSp = mContext.getSharedPreferences(SP_NAME_NOTIFICATION, Context.MODE_PRIVATE);
|
||||
LogUtils.d(TAG, "NotificationManagerUtils() 核心资源初始化完成 | mContext=" + mContext + " | mNotificationManager=" + mNotificationManager + " | mSp=" + mSp);
|
||||
|
||||
// ********** 新增:版本检查与旧渠道清理 **********
|
||||
checkNotificationVersionAndCleanOldChannels();
|
||||
|
||||
// 初始化通知渠道(API26+ 必需)
|
||||
initNotificationChannels();
|
||||
LogUtils.d(TAG, "NotificationManagerUtils() 构造完成");
|
||||
}
|
||||
|
||||
// ================================== 新增核心方法:通知版本检查与旧渠道清理 =================================
|
||||
/**
|
||||
* 检查当前通知版本与SP中保存的版本是否一致
|
||||
* 若不一致,清理所有旧通知渠道,并更新SP中的版本记录
|
||||
*/
|
||||
private void checkNotificationVersionAndCleanOldChannels() {
|
||||
LogUtils.d(TAG, "checkNotificationVersionAndCleanOldChannels() 方法调用 | 源码版本=" + NOTIFICATION_VERSION + " | SP版本=" + getSavedNotificationVersion());
|
||||
// 1. 版本一致,无需处理
|
||||
if (NOTIFICATION_VERSION.equals(getSavedNotificationVersion())) {
|
||||
LogUtils.d(TAG, "checkNotificationVersionAndCleanOldChannels() 版本一致,无需清理渠道");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 版本不一致,清理所有旧渠道
|
||||
LogUtils.w(TAG, "checkNotificationVersionAndCleanOldChannels() 版本不一致,开始清理所有旧通知渠道");
|
||||
cleanAllNotificationChannels();
|
||||
|
||||
// 3. 取消所有旧通知
|
||||
cancelAllNotifications();
|
||||
LogUtils.d(TAG, "checkNotificationVersionAndCleanOldChannels() 已取消所有旧通知");
|
||||
|
||||
// 4. 更新SP中的版本记录
|
||||
saveNotificationVersion(NOTIFICATION_VERSION);
|
||||
LogUtils.d(TAG, "checkNotificationVersionAndCleanOldChannels() 已更新SP版本记录为:" + NOTIFICATION_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SP中获取已保存的通知版本
|
||||
* @return 已保存的版本号,无则返回空字符串
|
||||
*/
|
||||
private String getSavedNotificationVersion() {
|
||||
return mSp.getString(SP_KEY_NOTIFICATION_VERSION, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前通知版本保存到SP
|
||||
* @param version 要保存的版本号
|
||||
*/
|
||||
private void saveNotificationVersion(String version) {
|
||||
mSp.edit().putString(SP_KEY_NOTIFICATION_VERSION, version).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有已创建的通知渠道(API26+ 有效)
|
||||
*/
|
||||
private void cleanAllNotificationChannels() {
|
||||
LogUtils.d(TAG, "cleanAllNotificationChannels() 方法调用");
|
||||
// API<26 无渠道机制,直接返回
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || mNotificationManager == null) {
|
||||
LogUtils.d(TAG, "cleanAllNotificationChannels() API<26 或 NotificationManager为空,无需清理");
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历所有渠道并删除
|
||||
for (NotificationChannel channel : mNotificationManager.getNotificationChannels()) {
|
||||
LogUtils.d(TAG, "cleanAllNotificationChannels() 正在删除渠道:" + channel.getId() + " | " + channel.getName());
|
||||
mNotificationManager.deleteNotificationChannel(channel.getId());
|
||||
}
|
||||
LogUtils.d(TAG, "cleanAllNotificationChannels() 所有旧渠道清理完成");
|
||||
}
|
||||
|
||||
// ================================== 核心初始化方法(通知渠道,API分级适配)=================================
|
||||
/**
|
||||
* 初始化通知渠道:前台服务渠道(无铃声+无振动)、临时提醒渠道(系统默认铃声+无振动)
|
||||
*/
|
||||
private void initNotificationChannels() {
|
||||
LogUtils.d(TAG, "initNotificationChannels() 方法调用");
|
||||
// API<26 无渠道机制,直接返回
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
LogUtils.d(TAG, "initNotificationChannels() API<26,无需创建渠道,直接返回");
|
||||
return;
|
||||
}
|
||||
// 通知服务为空,避免空指针
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "initNotificationChannels() 失败:NotificationManager is null");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 前台服务渠道(低优先级,后台保活无打扰)
|
||||
NotificationChannel foregroundChannel = new NotificationChannel(
|
||||
CHANNEL_ID_FOREGROUND,
|
||||
"拨号服务保活",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
foregroundChannel.setDescription("拨号服务后台运行,无声音、无振动");
|
||||
foregroundChannel.enableLights(false);
|
||||
foregroundChannel.enableVibration(false);
|
||||
foregroundChannel.setSound(null, null);
|
||||
foregroundChannel.setShowBadge(false);
|
||||
foregroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
|
||||
LogUtils.d(TAG, "initNotificationChannels() 前台服务渠道配置完成");
|
||||
|
||||
// 2. 临时提醒渠道(中优先级,系统默认铃声,无振动)
|
||||
NotificationChannel temporaryChannel = new NotificationChannel(
|
||||
CHANNEL_ID_TEMPORARY,
|
||||
"临时通知提醒",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
);
|
||||
temporaryChannel.setDescription("拨号服务临时通知,系统默认铃声,无振动");
|
||||
temporaryChannel.enableLights(true);
|
||||
temporaryChannel.enableVibration(false);
|
||||
temporaryChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
||||
temporaryChannel.setShowBadge(false);
|
||||
temporaryChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
LogUtils.d(TAG, "initNotificationChannels() 临时提醒渠道配置完成");
|
||||
|
||||
// 注册渠道到系统
|
||||
mNotificationManager.createNotificationChannel(foregroundChannel);
|
||||
mNotificationManager.createNotificationChannel(temporaryChannel);
|
||||
LogUtils.d(TAG, "initNotificationChannels() 成功:创建前台服务+临时提醒渠道");
|
||||
}
|
||||
|
||||
// ================================== 对外核心方法(前台服务通知:启动/更新/取消)=================================
|
||||
/**
|
||||
* 启动前台服务通知(API30适配,无铃声)
|
||||
* @param service 前台服务实例
|
||||
* @param message 通知内容
|
||||
*/
|
||||
public void startForegroundServiceNotify(Service service, String message) {
|
||||
LogUtils.d(TAG, "startForegroundServiceNotify() 方法调用 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE + " | service=" + service + " | message=" + message);
|
||||
// 前置校验:参数非空
|
||||
if (service == null || mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify() 失败:param is null | service=" + service + " | mNotificationManager=" + mNotificationManager);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建前台通知
|
||||
mForegroundServiceNotify = buildForegroundNotification(message);
|
||||
if (mForegroundServiceNotify == null) {
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify() 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动前台服务(API30无FOREGROUND_SERVICE_TYPE限制)
|
||||
try {
|
||||
service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
|
||||
LogUtils.d(TAG, "startForegroundServiceNotify() 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify() 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前台服务通知内容(复用通知ID,保持无铃声)
|
||||
* @param message 新的通知内容
|
||||
*/
|
||||
public void updateForegroundServiceNotify(String message) {
|
||||
LogUtils.d(TAG, "updateForegroundServiceNotify() 方法调用 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE + " | message=" + message);
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify() 失败:mNotificationManager is null");
|
||||
return;
|
||||
}
|
||||
|
||||
mForegroundServiceNotify = buildForegroundNotification(message);
|
||||
if (mForegroundServiceNotify == null) {
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify() 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
|
||||
LogUtils.d(TAG, "updateForegroundServiceNotify() 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify() 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消前台服务通知(Service销毁时调用)
|
||||
*/
|
||||
public void cancelForegroundServiceNotify() {
|
||||
LogUtils.d(TAG, "cancelForegroundServiceNotify() 方法调用 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
cancelNotification(NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
mForegroundServiceNotify = null;
|
||||
LogUtils.d(TAG, "cancelForegroundServiceNotify() 成功");
|
||||
}
|
||||
|
||||
// ================================== 对外核心方法(临时通知:发送)=================================
|
||||
/**
|
||||
* 发送临时通知(自增ID,避免覆盖,系统默认铃声)
|
||||
* @param context 上下文
|
||||
* @param message 通知内容
|
||||
*/
|
||||
public synchronized void showTemporaryNotification(Context context, String message) {
|
||||
snMessageNotificationID++;
|
||||
LogUtils.d(TAG, "showTemporaryNotification() 方法调用 | notifyId=" + snMessageNotificationID + " | context=" + context + " | message=" + message);
|
||||
// 前置校验:参数非空
|
||||
if (context == null || mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "showTemporaryNotification() 失败:param is null | context=" + context + " | mNotificationManager=" + mNotificationManager);
|
||||
return;
|
||||
}
|
||||
|
||||
Notification temporaryNotify = buildTemporaryNotification(context, message);
|
||||
if (temporaryNotify == null) {
|
||||
LogUtils.e(TAG, "showTemporaryNotification() 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mNotificationManager.notify(snMessageNotificationID, temporaryNotify);
|
||||
LogUtils.d(TAG, "showTemporaryNotification() 成功 | notifyId=" + snMessageNotificationID);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "showTemporaryNotification() 异常 | notifyId=" + snMessageNotificationID, e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================== 对外工具方法(通知取消:单个/全部)=================================
|
||||
/**
|
||||
* 取消指定ID的通知
|
||||
* @param notifyId 通知唯一标识
|
||||
*/
|
||||
public void cancelNotification(int notifyId) {
|
||||
LogUtils.d(TAG, "cancelNotification() 方法调用 | notifyId=" + notifyId);
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "cancelNotification() 失败:NotificationManager is null");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mNotificationManager.cancel(notifyId);
|
||||
LogUtils.d(TAG, "cancelNotification() 成功 | notifyId=" + notifyId);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "cancelNotification() 异常 | notifyId=" + notifyId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有通知(兜底场景使用)
|
||||
*/
|
||||
public void cancelAllNotifications() {
|
||||
LogUtils.d(TAG, "cancelAllNotifications() 方法调用");
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "cancelAllNotifications() 失败:NotificationManager is null");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mNotificationManager.cancelAll();
|
||||
LogUtils.d(TAG, "cancelAllNotifications() 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "cancelAllNotifications() 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(通知构建:前台服务通知)=================================
|
||||
/**
|
||||
* 构建前台服务通知(全版本无铃声+无振动)
|
||||
* @param message 通知内容
|
||||
* @return 构建完成的前台通知实例
|
||||
*/
|
||||
private Notification buildForegroundNotification(String message) {
|
||||
LogUtils.d(TAG, "buildForegroundNotification() 方法调用 | message=" + message);
|
||||
if (mContext == null) {
|
||||
LogUtils.e(TAG, "buildForegroundNotification() 失败:mContext is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 内容兜底
|
||||
String title = FOREGROUND_NOTIFY_TITLE_DEFAULT;
|
||||
String content = (message != null && !message.isEmpty()) ? message : FOREGROUND_NOTIFY_CONTENT_DEFAULT;
|
||||
LogUtils.d(TAG, "buildForegroundNotification() 内容兜底完成 | title=" + title + " | content=" + content);
|
||||
|
||||
Notification.Builder builder;
|
||||
// API分级构建
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder = new Notification.Builder(mContext, CHANNEL_ID_FOREGROUND);
|
||||
LogUtils.d(TAG, "buildForegroundNotification() 使用API26+渠道构建");
|
||||
} else {
|
||||
builder = new Notification.Builder(mContext);
|
||||
builder.setSound(null);
|
||||
builder.setVibrate(new long[]{0});
|
||||
builder.setDefaults(0);
|
||||
LogUtils.d(TAG, "buildForegroundNotification() 使用API<26手动配置");
|
||||
}
|
||||
|
||||
// 通用配置
|
||||
builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setAutoCancel(false)
|
||||
.setOngoing(true)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentIntent(createJumpPendingIntent(mContext, PENDING_INTENT_REQUEST_CODE_FOREGROUND));
|
||||
|
||||
// API21+ 新增大图标+主题色
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.setLargeIcon(getAppIcon(mContext))
|
||||
.setColor(mContext.getResources().getColor(R.color.colorPrimary))
|
||||
.setPriority(Notification.PRIORITY_LOW);
|
||||
LogUtils.d(TAG, "buildForegroundNotification() 补充API21+配置");
|
||||
}
|
||||
|
||||
Notification notification = builder.build();
|
||||
LogUtils.d(TAG, "buildForegroundNotification() 成功构建前台通知");
|
||||
return notification;
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(通知构建:临时通知)=================================
|
||||
/**
|
||||
* 构建临时通知(全版本系统默认铃声+无振动)
|
||||
* @param context 上下文
|
||||
* @param message 通知内容
|
||||
* @return 构建完成的临时通知实例
|
||||
*/
|
||||
private Notification buildTemporaryNotification(Context context, String message) {
|
||||
LogUtils.d(TAG, "buildTemporaryNotification() 方法调用 | context=" + context + " | message=" + message);
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "buildTemporaryNotification() 失败:context is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 内容兜底
|
||||
String title = TEMPORARY_NOTIFY_TITLE_DEFAULT;
|
||||
String content = (message != null && !message.isEmpty()) ? message : TEMPORARY_NOTIFY_CONTENT_DEFAULT;
|
||||
LogUtils.d(TAG, "buildTemporaryNotification() 内容兜底完成 | title=" + title + " | content=" + content);
|
||||
|
||||
Notification.Builder builder;
|
||||
// API分级构建
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder = new Notification.Builder(context, CHANNEL_ID_TEMPORARY);
|
||||
LogUtils.d(TAG, "buildTemporaryNotification() 使用API26+渠道构建");
|
||||
} else {
|
||||
builder = new Notification.Builder(context);
|
||||
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
||||
builder.setVibrate(new long[]{0});
|
||||
builder.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_SOUND);
|
||||
LogUtils.d(TAG, "buildTemporaryNotification() 使用API<26手动配置");
|
||||
}
|
||||
|
||||
// 通用配置
|
||||
builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), Notification.AUDIO_ATTRIBUTES_DEFAULT)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentIntent(createJumpPendingIntent(context, PENDING_INTENT_REQUEST_CODE_TEMPORARY));
|
||||
|
||||
// API21+ 新增大图标+主题色
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.setLargeIcon(getAppIcon(context))
|
||||
.setColor(context.getResources().getColor(R.color.colorPrimary))
|
||||
.setPriority(Notification.PRIORITY_DEFAULT);
|
||||
LogUtils.d(TAG, "buildTemporaryNotification() 补充API21+配置");
|
||||
}
|
||||
|
||||
Notification notification = builder.build();
|
||||
LogUtils.d(TAG, "buildTemporaryNotification() 成功构建临时通知");
|
||||
return notification;
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(创建跳转PendingIntent,API30安全适配)=================================
|
||||
/**
|
||||
* 创建跳转MainActivity的PendingIntent,API23+ 添加IMMUTABLE标记
|
||||
* @param context 上下文
|
||||
* @param requestCode 请求码
|
||||
* @return 构建完成的PendingIntent
|
||||
*/
|
||||
private PendingIntent createJumpPendingIntent(Context context, int requestCode) {
|
||||
LogUtils.d(TAG, "createJumpPendingIntent() 方法调用 | requestCode=" + requestCode + " | context=" + context);
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
LogUtils.d(TAG, "createJumpPendingIntent() 跳转Intent配置完成");
|
||||
|
||||
// API23+ 必需添加IMMUTABLE,适配API30安全规范
|
||||
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
flags |= PendingIntent.FLAG_IMMUTABLE;
|
||||
LogUtils.d(TAG, "createJumpPendingIntent() 添加FLAG_IMMUTABLE标记(API23+)");
|
||||
}
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, flags);
|
||||
LogUtils.d(TAG, "createJumpPendingIntent() 成功 | requestCode=" + requestCode);
|
||||
return pendingIntent;
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(获取APP图标,异常兜底)=================================
|
||||
/**
|
||||
* 获取APP图标,失败返回默认图标
|
||||
* @param context 上下文
|
||||
* @return APP图标Bitmap实例
|
||||
*/
|
||||
private Bitmap getAppIcon(Context context) {
|
||||
LogUtils.d(TAG, "getAppIcon() 方法调用 | context=" + context);
|
||||
try {
|
||||
PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
Bitmap appIcon = BitmapFactory.decodeResource(context.getResources(), pkgInfo.applicationInfo.icon);
|
||||
LogUtils.d(TAG, "getAppIcon() 成功:获取应用图标");
|
||||
return appIcon;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.e(TAG, "getAppIcon() 异常:获取应用图标失败,使用默认图标", e);
|
||||
return BitmapFactory.decodeResource(context.getResources(), NOTIFICATION_DEFAULT_ICON);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================== 资源释放方法(避免内存泄漏)=================================
|
||||
/**
|
||||
* 释放资源,销毁时调用
|
||||
*/
|
||||
public void release() {
|
||||
LogUtils.d(TAG, "release() 方法调用:执行资源释放");
|
||||
cancelForegroundServiceNotify();
|
||||
mNotificationManager = null;
|
||||
mContext = null;
|
||||
mSp = null; // ********** 新增:释放SP实例 **********
|
||||
LogUtils.d(TAG, "release() 成功:所有资源已释放");
|
||||
}
|
||||
|
||||
// ================================== 对外 getter 方法(仅前台通知实例,只读)=================================
|
||||
public Notification getForegroundServiceNotify() {
|
||||
return mForegroundServiceNotify;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user