From b38a8df4620e2339cc0ab6589a96540876b84840 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 21 Dec 2025 09:20:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=94=E7=94=A8=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=95=B4=E7=90=86=E5=90=8E=E9=87=8D=E6=96=B0=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E4=BD=8D=E5=9B=BE=E7=9A=84=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../java/cc/winboll/studio/powerbell/App.java | 106 ++++++++++++++++-- .../utils/NotificationManagerUtils.java | 106 +++++++++++++++++- 3 files changed, 201 insertions(+), 15 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index 2563502..0de9b5a 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sat Dec 20 22:59:40 HKT 2025 +#Sun Dec 21 01:13:54 GMT 2025 stageCount=13 libraryProject= baseVersion=15.14 publishVersion=15.14.12 -buildCount=0 +buildCount=18 baseBetaVersion=15.14.13 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java index b51f7e3..0cc2dd7 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java @@ -1,5 +1,6 @@ package cc.winboll.studio.powerbell; +import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Environment; import android.os.Handler; @@ -9,11 +10,13 @@ import cc.winboll.studio.libappbase.GlobalApplication; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.powerbell.models.BackgroundBean; +import cc.winboll.studio.powerbell.models.NotificationMessage; import cc.winboll.studio.powerbell.receivers.GlobalApplicationReceiver; import cc.winboll.studio.powerbell.utils.AppCacheUtils; import cc.winboll.studio.powerbell.utils.AppConfigUtils; import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils; import cc.winboll.studio.powerbell.utils.BitmapCacheUtils; +import cc.winboll.studio.powerbell.utils.NotificationManagerUtils; import java.io.File; public class App extends GlobalApplication { @@ -26,14 +29,20 @@ public class App extends GlobalApplication { public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1"; public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2"; + // 内存紧张通知常量 + private static final String TRIM_MEMORY_NOTIFY_TITLE = "应用使用时内存紧张提醒"; + private static final String TRIM_MEMORY_NOTIFY_CONTENT = "由于本应用使用时,系统通知内存紧张程度级别较高,图片缓存功能暂时不启用。"; + // 数据配置存储工具 static AppConfigUtils _mAppConfigUtils; static AppCacheUtils _mAppCacheUtils; - // 新增:全局 Bitmap 缓存工具(常驻内存) + // 全局 Bitmap 缓存工具(常驻内存) public static BitmapCacheUtils _mBitmapCacheUtils; GlobalApplicationReceiver mReceiver; static String szTempDir = ""; + // 通知工具类实例(用于发送内存紧张通知) + private NotificationManagerUtils mNotificationManager; public static String getTempDirPath() { return szTempDir; @@ -60,13 +69,17 @@ public class App extends GlobalApplication { // 设置数据配置存储工具 _mAppConfigUtils = getAppConfigUtils(this); _mAppCacheUtils = getAppCacheUtils(this); - // 初始化全局 Bitmap 缓存工具(关键:App 启动时初始化,常驻内存) + // 初始化全局 Bitmap 缓存工具 _mBitmapCacheUtils = BitmapCacheUtils.getInstance(); + // 初始化通知工具类(使用整理后的 NotificationManagerUtils) + mNotificationManager = new NotificationManagerUtils(this); + LogUtils.d(TAG, "onCreate: 通知工具类初始化完成"); + mReceiver = new GlobalApplicationReceiver(this); mReceiver.registerAction(); - // ======================== 新增:异步预加载背景图 ======================== + // 异步预加载背景图(保持原有逻辑) new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { @@ -74,23 +87,19 @@ public class App extends GlobalApplication { @Override public void run() { try { - // 1. 获取背景源工具类实例 BackgroundSourceUtils bgSourceUtils = BackgroundSourceUtils.getInstance(App.this); if (bgSourceUtils == null) { LogUtils.e(TAG, "preloadBitmap: BackgroundSourceUtils 实例为空"); return; } - // 2. 获取当前背景Bean BackgroundBean bgBean = bgSourceUtils.getCurrentBackgroundBean(); if (bgBean == null || !bgBean.isUseBackgroundFile()) { LogUtils.d(TAG, "preloadBitmap: 无有效背景文件,跳过预加载"); return; } - // 3. 获取背景图路径(优先取压缩图路径) String bgPath = bgBean.isUseBackgroundScaledCompressFile() ? bgBean.getBackgroundScaledCompressFilePath() : bgBean.getBackgroundFilePath(); - // 4. 预加载到全局缓存 if (_mBitmapCacheUtils != null) { _mBitmapCacheUtils.cacheBitmap(bgPath); LogUtils.d(TAG, "preloadBitmap: 应用启动时预加载成功 - " + bgPath); @@ -103,8 +112,7 @@ public class App extends GlobalApplication { } }).start(); } - }, 1000); // 延迟1秒执行,避免阻塞应用初始化 - // ======================== 预加载逻辑结束 ======================== + }, 1000); } // 保持原有方法不变 @@ -130,11 +138,91 @@ public class App extends GlobalApplication { public void onTerminate() { super.onTerminate(); ToastUtils.release(); + // 释放通知工具类资源,避免内存泄漏 + if (mNotificationManager != null) { + mNotificationManager.release(); + mNotificationManager = null; + LogUtils.d(TAG, "onTerminate: 通知工具类资源已释放"); + } } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); + LogUtils.d(TAG, "onTrimMemory: 内存等级变化 | level=" + getTrimMemoryLevelDesc(level)); + + // 仅在中等及以上内存紧张等级发送通知,避免频繁打扰 + if (mNotificationManager == null) { + mNotificationManager = new NotificationManagerUtils(this); + } + if (level > ComponentCallbacks2.TRIM_MEMORY_MODERATE) { + sendTrimMemoryNotification(level); + } else { + // 再次缓存 Bitmap 缓存工具 + _mBitmapCacheUtils = BitmapCacheUtils.getInstance(); + LogUtils.d(TAG, "Bitmap 缓存启用中。"); + } + } + + /** + * 发送内存紧张通知(完全复用 NotificationManagerUtils 的 showRemindNotification 方法) + */ + private void sendTrimMemoryNotification(int level) { + LogUtils.d(TAG, "sendTrimMemoryNotification: 准备发送内存紧张通知"); + // 构建通知消息体 + NotificationMessage message = new NotificationMessage(); + message.setTitle(TRIM_MEMORY_NOTIFY_TITLE); + message.setContent(String.format("%s [ 缓存紧张级别描述: Level %d | %s ]",TRIM_MEMORY_NOTIFY_CONTENT, level, getTrimMemoryLevelDesc(level))); + // 使用整理后的 NotificationManagerUtils 发送通知(复用提醒渠道配置) + mNotificationManager.showConfigNotification(this, message); + LogUtils.d(TAG, "sendTrimMemoryNotification: 通知已通过 NotificationManagerUtils 发送"); + } + + /** + * 转换内存等级为可读描述,便于日志调试 + * 排序规则:按 ComponentCallbacks2 枚举数值从高到低排列(数值越高,内存越紧张) + */ + /** + * 转换内存等级为可读描述,便于日志调试 + * 排序规则:按 ComponentCallbacks2 枚举实际数值(10进制)从高到低排列 + * 数值来源:接口中定义的 16进制(注释10进制)数值 + */ + private String getTrimMemoryLevelDesc(int level) { + switch (level) { + // 数值 80(0x50):应用内存完全紧张(补充接口中实际存在的枚举项) + case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: + return "TRIM_MEMORY_COMPLETE(应用内存完全紧张)"; + // 数值 60(0x3c):中等内存紧张 + case ComponentCallbacks2.TRIM_MEMORY_MODERATE: + return "MODERATE(中等内存紧张)"; + // 数值 40(0x28):应用进入后台 + case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: + return "BACKGROUND(应用进入后台)"; + // 数值 20(0x14):应用UI隐藏 + case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: + return "BACKGROUND(应用UI隐藏)"; + // 数值 15(0xf):应用运行时关键级紧张 + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: + return "RUNNING_CRITICAL(应用运行关键级紧张)"; + // 数值 10(0xa):应用运行时低内存 + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: + return "RUNNING_LOW(应用运行低内存)"; + // 数值 5(0x5):应用运行时中等紧张 + case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: + return "RUNNING_MODERATE(应用运行中等内存紧张)"; + + // 以下为注释备用项(接口中未提供,按你的原有注释保留) + // 数值 100:内存极度紧张(系统可能强制杀死应用) + // case ComponentCallbacks2.TRIM_MEMORY_URGENT: + // return "URGENT(内存极度紧张)"; + // 数值 20:用户正在离开应用(如按Home键) + // case ComponentCallbacks2.TRIM_MEMORY_USER_LEAVING: + // return "USER_LEAVING(用户正在离开应用)"; + + // 未知等级 + default: + return "UNKNOWN(" + level + ")"; + } } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java index f34bf3a..6cfe53b 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java @@ -20,9 +20,9 @@ import cc.winboll.studio.powerbell.R; import cc.winboll.studio.powerbell.models.NotificationMessage; /** - * 通知工具类:统一管理前台服务/电池提醒通知 + * 通知工具类:统一管理前台服务/电池提醒/应用配置信息通知 * 适配:API19-30 | Java7 | 小米手机 - * 特性:前台服务无铃声、提醒通知系统默认铃声、API分级适配、内存泄漏防护 + * 特性:前台服务无铃声、提醒通知系统默认铃声、配置通知低优先级无打扰、API分级适配、内存泄漏防护 */ public class NotificationManagerUtils { // ================================== 静态常量(置顶统一管理,杜绝魔法值)================================= @@ -30,9 +30,11 @@ public class NotificationManagerUtils { // 通知渠道ID(API26+ 必需,区分通知类型) public static final String CHANNEL_ID_FOREGROUND = "cc.winboll.studio.powerbell.channel.foreground"; public static final String CHANNEL_ID_REMIND = "cc.winboll.studio.powerbell.channel.remind"; + public static final String CHANNEL_ID_CONFIG = "cc.winboll.studio.powerbell.channel.config"; // 新增:应用配置信息渠道 // 通知ID(唯一标识,避免重复) public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001; public static final int NOTIFY_ID_REMIND = 1002; + public static final int NOTIFY_ID_CONFIG = 1003; // 新增:应用配置信息通知ID // 低版本兼容:默认通知图标(API<21 避免显示异常) private static final int NOTIFICATION_DEFAULT_ICON = R.drawable.ic_launcher; // 通知内容兜底常量 @@ -40,9 +42,12 @@ public class NotificationManagerUtils { private static final String FOREGROUND_NOTIFY_CONTENT_DEFAULT = "后台监测电池状态"; private static final String REMIND_NOTIFY_TITLE_DEFAULT = "电池状态提醒"; private static final String REMIND_NOTIFY_CONTENT_DEFAULT = "电池状态异常,请及时处理"; + private static final String CONFIG_NOTIFY_TITLE_DEFAULT = "应用配置更新"; // 新增:配置通知默认标题 + private static final String CONFIG_NOTIFY_CONTENT_DEFAULT = "配置信息已更新,生效中"; // 新增:配置通知默认内容 // PendingIntent请求码 private static final int PENDING_INTENT_REQUEST_CODE_FOREGROUND = 0; private static final int PENDING_INTENT_REQUEST_CODE_REMIND = 1; + private static final int PENDING_INTENT_REQUEST_CODE_CONFIG = 2; // 新增:配置通知请求码 // ================================== 成员变量(私有封装,按依赖优先级排序)================================= // 核心上下文(应用级,避免内存泄漏) @@ -71,7 +76,7 @@ public class NotificationManagerUtils { // ================================== 核心初始化方法(通知渠道,API分级适配)================================= /** - * 初始化通知渠道:前台服务渠道(无铃声+无振动)、提醒渠道(系统默认铃声+无振动) + * 初始化通知渠道:前台服务渠道(无铃声+无振动)、提醒渠道(系统默认铃声+无振动)、配置信息渠道(低优先级无打扰) */ private void initNotificationChannels() { LogUtils.d(TAG, "initNotificationChannels: 执行通知渠道初始化"); @@ -114,10 +119,25 @@ public class NotificationManagerUtils { remindChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); LogUtils.d(TAG, "initNotificationChannels: 电池提醒渠道配置完成"); + // 3. 应用配置信息渠道(新增:最低优先级,无铃声无振动,仅提示不打扰) + NotificationChannel configChannel = new NotificationChannel( + CHANNEL_ID_CONFIG, + "应用配置信息", + NotificationManager.IMPORTANCE_MIN + ); + configChannel.setDescription("应用配置更新、参数变更等提示,无声音、无振动"); + configChannel.enableLights(false); + configChannel.enableVibration(false); + configChannel.setSound(null, null); + configChannel.setShowBadge(false); + configChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + LogUtils.d(TAG, "initNotificationChannels: 应用配置信息渠道配置完成"); + // 注册渠道到系统 mNotificationManager.createNotificationChannel(foregroundChannel); mNotificationManager.createNotificationChannel(remindChannel); - LogUtils.d(TAG, "initNotificationChannels: 成功:创建前台服务+电池提醒渠道"); + mNotificationManager.createNotificationChannel(configChannel); // 注册新增渠道 + LogUtils.d(TAG, "initNotificationChannels: 成功:创建前台服务+电池提醒+应用配置信息渠道"); } // ================================== 对外核心方法(前台服务通知:启动/更新/取消)================================= @@ -207,6 +227,31 @@ public class NotificationManagerUtils { } } + // ================================== 对外核心方法(应用配置信息通知:发送)================================= + /** + * 发送应用配置信息通知(新增:低优先级无铃声,仅提示不打扰) + */ + public void showConfigNotification(Context context, NotificationMessage message) { + LogUtils.d(TAG, "showConfigNotification: 执行 | notifyId=" + NOTIFY_ID_CONFIG + " | context=" + context + " | message=" + message); + if (context == null || message == null || mNotificationManager == null) { + LogUtils.e(TAG, "showConfigNotification: 失败:param is null | context=" + context + " | message=" + message + " | mNotificationManager=" + mNotificationManager); + return; + } + + Notification configNotify = buildConfigNotification(context, message); + if (configNotify == null) { + LogUtils.e(TAG, "showConfigNotification: 失败:构建通知为空"); + return; + } + + try { + mNotificationManager.notify(NOTIFY_ID_CONFIG, configNotify); + LogUtils.d(TAG, "showConfigNotification: 成功"); + } catch (Exception e) { + LogUtils.e(TAG, "showConfigNotification: 异常", e); + } + } + // ================================== 对外工具方法(通知取消:单个/全部)================================= /** * 取消指定ID的通知 @@ -349,6 +394,59 @@ public class NotificationManagerUtils { return notification; } + // ================================== 内部辅助方法(通知构建:应用配置信息通知)================================= + /** + * 构建应用配置信息通知(新增:全版本无铃声+无振动,低优先级) + */ + private Notification buildConfigNotification(Context context, NotificationMessage message) { + LogUtils.d(TAG, "buildConfigNotification: 执行 | context=" + context + " | message=" + message); + if (context == null || message == null) { + LogUtils.e(TAG, "buildConfigNotification: 失败:param is null | context=" + context + " | message=" + message); + return null; + } + + // 内容兜底 + String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : CONFIG_NOTIFY_TITLE_DEFAULT; + String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : CONFIG_NOTIFY_CONTENT_DEFAULT; + LogUtils.d(TAG, "buildConfigNotification: 内容兜底完成 | title=" + title + " | content=" + content); + + Notification.Builder builder; + // API分级构建 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // API26+:绑定配置渠道(渠道已配置无铃声) + builder = new Notification.Builder(context, CHANNEL_ID_CONFIG); + LogUtils.d(TAG, "buildConfigNotification: 使用API26+渠道构建"); + } else { + // API<26:直接构建,手动禁用铃声振动 + builder = new Notification.Builder(context); + builder.setSound(null); + builder.setVibrate(new long[]{0}); + builder.setDefaults(0); + LogUtils.d(TAG, "buildConfigNotification: 使用API<26手动配置"); + } + + // 通用配置 + builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setContentTitle(title) + .setContentText(content) + .setAutoCancel(true) // 点击关闭 + .setOngoing(false) + .setWhen(System.currentTimeMillis()) + .setContentIntent(createJumpPendingIntent(context, PENDING_INTENT_REQUEST_CODE_CONFIG)); + + // API21+ 新增大图标+主题色 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setLargeIcon(getAppIcon(context)) + .setColor(context.getResources().getColor(R.color.colorPrimary)) + .setPriority(Notification.PRIORITY_MIN); // 最低优先级 + LogUtils.d(TAG, "buildConfigNotification: 补充API21+配置"); + } + + Notification notification = builder.build(); + LogUtils.d(TAG, "buildConfigNotification: 成功构建配置信息通知"); + return notification; + } + // ================================== 内部辅助方法(创建跳转PendingIntent,API30安全适配)================================= /** * 创建跳转MainActivity的PendingIntent,API23+ 添加IMMUTABLE标记(避免安全异常)