源码整理

This commit is contained in:
2025-12-17 13:50:33 +08:00
parent 47601ef542
commit 29b9f3c82b
8 changed files with 1330 additions and 1007 deletions

View File

@@ -8,66 +8,83 @@ import cc.winboll.studio.powerbell.services.ControlCenterService;
import cc.winboll.studio.powerbell.models.NotificationMessage;
/**
* 服务通信Handler弱引用持有服务避免内存泄漏适配Java7
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:41
* @Describe 服务通信Handler弱引用持有服务避免内存泄漏适配Java7+API30统一处理服务消息
*/
public class ControlCenterServiceHandler extends Handler {
// 消息类型常量与RemindThread对应
public static final int MSG_REMIND_TEXT = 1001; // 提醒消息
private static final String TAG = "ControlCenterServiceHandler";
// ================================== 静态常量(置顶统一管理,清晰区分消息类型)=================================
public static final String TAG = "ControlCenterServiceHandler";
public static final int MSG_REMIND_TEXT = 1001; // 电量提醒消息(+充电/-耗电)
// 弱引用持有服务实例(避免内存泄漏)
private WeakReference<ControlCenterService> mwrControlCenterService;
// ================================== 成员变量(弱引用服务,杜绝内存泄漏)=================================
private final WeakReference<ControlCenterService> mwrControlCenterService;
// 构造方法(传入服务实例)
// ================================== 构造方法(强制传入服务,初始化弱引用)=================================
public ControlCenterServiceHandler(ControlCenterService service) {
LogUtils.d(TAG, "初始化Handler绑定服务实例" + (service != null ? service.getClass().getSimpleName() : "null"));
this.mwrControlCenterService = new WeakReference<>(service);
}
// ================================== 核心消息处理重写handleMessage按类型分发=================================
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LogUtils.d(TAG, "接收消息what" + msg.what + "obj" + (msg.obj != null ? msg.obj : "null"));
// 弱引用获取服务,避免持有强引用导致内存泄漏
ControlCenterService service = mwrControlCenterService.get();
if (service == null) {
LogUtils.e(TAG, "handleMessage: 服务实例为空,处理失败");
LogUtils.e(TAG, "服务实例已回收,消息处理失败");
return;
}
// 处理不同类型消息
// 按消息类型分发处理,避免逻辑冗余
switch (msg.what) {
case MSG_REMIND_TEXT:
// 接收RemindThread的提醒消息调用通知工具类发送通知
handleRemindMessage(service, (String) msg.obj);
break;
default:
LogUtils.d(TAG, "handleMessage: 未知消息类型what=" + msg.what);
LogUtils.w(TAG, "未知消息类型what" + msg.what);
break;
}
}
// 处理提醒消息(发送通知)
// ================================== 业务辅助方法(单独处理提醒消息,职责单一)=================================
/**
* 处理电量提醒消息,构建通知模型并调用工具类发送
* @param service 服务实例(非空,已前置校验)
* @param content 消息内容(+:充电提醒,-:耗电提醒)
*/
private void handleRemindMessage(ControlCenterService service, String content) {
LogUtils.d(TAG, "handleRemindMessage: 处理提醒消息,内容=" + content);
// 复用服务的通知工具类和前台通知模型
LogUtils.d(TAG, "开始处理提醒消息,内容" + content);
// 校验通知工具类,避免空指针
if (service.getNotificationManager() == null) {
LogUtils.e(TAG, "handleRemindMessage: 通知工具类为空,发送失败");
LogUtils.e(TAG, "通知管理工具类为空,无法发送提醒");
return;
}
// 构建提醒通知消息(可按需求调整标题/内容)
// 构建通知消息模型,区分充电/耗电场景
NotificationMessage remindMsg = new NotificationMessage();
if ("+".equals(content)) { // 充电提醒
if ("+".equals(content)) {
remindMsg.setTitle("充电提醒");
remindMsg.setContent("电池电量已达标,建议及时断电保护电池~");
remindMsg.setContent("电池电量已达标,建议及时断电保护电池寿命");
remindMsg.setRemindMSG("charge_remind");
} else if ("-".equals(content)) { // 耗电提醒
LogUtils.d(TAG, "构建充电提醒通知标识charge_remind");
} else if ("-".equals(content)) {
remindMsg.setTitle("耗电提醒");
remindMsg.setContent("电池电量偏低,建议及时充电~");
remindMsg.setContent("电池电量偏低,建议及时充电,避免设备关机");
remindMsg.setRemindMSG("usage_remind");
LogUtils.d(TAG, "构建耗电提醒通知标识usage_remind");
} else {
LogUtils.w(TAG, "无效提醒消息内容,跳过发送:" + content);
return;
}
// 调用通知工具类发送提醒通知复用现有逻辑
// 调用服务工具类发送通知复用现有逻辑
service.getNotificationManager().showRemindNotification(service, remindMsg);
LogUtils.d(TAG, "handleRemindMessage: 提醒通知发送完成");
LogUtils.d(TAG, "提醒通知发送完成,标题:" + remindMsg.getTitle());
}
}

View File

@@ -8,365 +8,344 @@ import cc.winboll.studio.powerbell.models.AppConfigBean;
import java.lang.ref.WeakReference;
/**
* 提醒线程(单例模式):统一管理充电/耗电提醒逻辑适配Java7+API30+
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:38
* @Describe 提醒线程(单例模式):统一管理充电/耗电提醒逻辑适配Java7+API30+,保障线程安全与内存安全
*/
public class RemindThread extends Thread {
// 常量定义(优化命名规范,补充注释)
// ================================== 静态常量(置顶,统一管理魔法值)=================================
public static final String TAG = "RemindThread";
private static final long INIT_DELAY_TIME = 500L; // 初始化延迟ms等待服务就绪
private static final int MIN_SLEEP_TIME = 500; // 最小检测间隔ms避免频繁轮询)
private static final int MIN_SLEEP_TIME = 500; // 最小检测间隔ms防高频轮询)
private static final int DEFAULT_SLEEP_TIME = 1000; // 默认检测间隔ms
private static final long REMIND_INTERVAL = 30000L; // 重复提醒间隔ms避免轰炸用户
private static final int CONFIG_RETRY_MAX = 3; // 配置同步重试次数(兜底配置有效性
private static final long REMIND_INTERVAL = 30000L; // 重复提醒间隔ms防骚扰
private static final int CONFIG_RETRY_MAX = 3; // 配置同步重试次数(兜底)
// 单例核心volatile+双重校验锁,线程安全,修复复用漏洞)
// ================================== 单例核心(线程安全,避免复用旧实例)=================================
private static volatile RemindThread sInstance;
// 成员变量volatile+锁保护,确保多线程可见性,优化内存泄漏防护)
// ================================== 成员变量按功能分类volatile+锁保护)=================================
// 依赖资源(防内存泄漏)
private Context mContext;
private WeakReference<ControlCenterServiceHandler> mwrControlCenterServiceHandler;
private volatile boolean isExist; // 线程退出标记true=触发退出)
private volatile boolean isReminding; // 全局提醒功能开关
private volatile boolean isThreadStarted; // 线程启动状态标记区分isAlive()
private volatile boolean isEnableUsageReminder; // 耗电提醒单独开关
private volatile boolean isEnableChargeReminder; // 充电提醒单独开关
private volatile int sleepTime; // 电量检测间隔ms
// 线程状态(核心标记,多线程可见)
private volatile boolean isExist; // 退出标记true=退出
private volatile boolean isThreadStarted; // 启动标记区分isAlive()
private volatile boolean isReminding; // 全局提醒开关
private volatile boolean isRemindTimerRunning; // 提醒恢复计时器状态
// 业务配置(实时同步,范围校验)
private volatile boolean isEnableChargeReminder; // 充电提醒开关
private volatile boolean isEnableUsageReminder; // 耗电提醒开关
private volatile int sleepTime; // 检测间隔ms
private volatile int chargeReminderValue; // 充电提醒阈值0-100
private volatile int usageReminderValue; // 耗电提醒阈值0-100
private volatile int quantityOfElectricity; // 当前电量0-100
private volatile boolean isCharging; // 是否处于充电状态
private volatile boolean isRemindTimerRunning; // 提醒恢复计时器运行标记
private volatile long lastRemindTime; // 上次提醒时间戳ms
private final Object mLock = new Object(); // 全局锁(保护所有状态变量,解决并发安全)
private volatile boolean isCharging; // 充电状态标记
// 私有构造器(单例模式,强化上下文弱引用+状态重置
// 辅助变量(并发安全+防重复提醒
private volatile long lastRemindTime; // 上次提醒时间戳ms
private final Object mLock = new Object(); // 全局锁(保护所有状态变量)
// ================================== 私有构造器(单例专用,初始化状态+防护泄漏)=================================
private RemindThread(Context context, ControlCenterServiceHandler handler) {
LogUtils.d(TAG, "RemindThread: 构造方法初始化,线程名称=" + getName());
// 应用上下文+弱引用Handler双重防护内存泄漏
LogUtils.d(TAG, "构造线程实例,线程名称" + getName());
this.mContext = context.getApplicationContext();
this.mwrControlCenterServiceHandler = new WeakReference<>(handler);
resetThreadStateInternal(); // 初始化所有状态变量,避免脏数据
resetThreadStateInternal(); // 初始化所有状态,避免脏数据
}
// 单例获取(核心修复:线程停止后自动销毁单例,杜绝复用旧线程对象)
// ================================== 单例管理(获取+销毁,彻底释放资源)=================================
/**
* 获取单例实例(旧线程停止自动销毁,强制入参校验)
*/
public static RemindThread getInstance(Context context, ControlCenterServiceHandler handler) {
LogUtils.d(TAG, "getInstance: 获取线程实例,当前单例状态=" + (sInstance != null));
// 入参校验(非空抛出异常,强制上游传参正确)
LogUtils.d(TAG, "获取单例,当前实例是否存在:" + (sInstance != null));
if (context == null || handler == null) {
LogUtils.e(TAG, "getInstance: 致命错误 - Context/Handler不能为空");
LogUtils.e(TAG, "致命错误Context/ControlCenterServiceHandler不能为空");
throw new IllegalArgumentException("Context and ControlCenterServiceHandler cannot be null");
}
// 关键修复:旧线程停止(未运行),直接销毁单例,确保新实例创建
// 旧线程停止则销毁,确保新实例有效
if (sInstance != null && !sInstance.isRunning()) {
destroyInstance();
LogUtils.d(TAG, "getInstance: 旧线程已停止,销毁单例准备创建新实例");
LogUtils.d(TAG, "旧线程已停止,销毁单例准备创建新实例");
}
// 双重校验锁,线程安全创建单例
// 双重校验锁创建单例
if (sInstance == null) {
synchronized (RemindThread.class) {
if (sInstance == null) {
sInstance = new RemindThread(context, handler);
LogUtils.d(TAG, "getInstance: 新线程实例创建成功线程ID=" + sInstance.getId());
LogUtils.d(TAG, "新线程实例创建成功线程ID" + sInstance.getId());
}
}
}
return sInstance;
}
// 单例销毁(核心优化:彻底释放资源+终止线程与Service停止逻辑联动
/**
* 销毁单例(安全停止线程+释放资源,避免内存泄漏)
*/
public static void destroyInstance() {
LogUtils.d(TAG, "destroyInstance: 开始销毁线程单例");
synchronized (RemindThread.class) { // 类锁保护,避免并发销毁冲突
LogUtils.d(TAG, "开始销毁线程单例");
synchronized (RemindThread.class) {
if (sInstance != null) {
// 1. 安全停止线程(触发退出+中断休眠)
sInstance.stopThread();
// 2. 强制释放所有资源(避免内存泄漏)
sInstance.releaseResources();
// 3. 置空单例,杜绝后续复用
sInstance = null;
LogUtils.d(TAG, "destroyInstance: 线程单例销毁完成");
LogUtils.d(TAG, "线程单例销毁完成");
} else {
LogUtils.w(TAG, "destroyInstance: 单例已为空,跳过销毁");
LogUtils.w(TAG, "单例已为空,跳过销毁");
}
}
}
// 线程核心逻辑(彻底修复重复启动、优化退出效率、强化异常容错)
// ================================== 线程核心逻辑run方法高效退出+异常容错)=================================
@Override
public void run() {
LogUtils.d(TAG, "run: 线程启动执行,线程ID=" + Thread.currentThread().getId() + "当前状态=" + getState());
// 核心修复1杜绝重复执行run()Thread.start()后会自动调用run(),手动调用/重复start()直接拦截)
LogUtils.d(TAG, "线程启动,ID" + Thread.currentThread().getId() + ",状态" + getState());
// 防止run方法重复执行
synchronized (mLock) {
if (isThreadStarted || isExist) {
LogUtils.e(TAG, "run: 线程已启动/待退出禁止重复执行run()");
LogUtils.e(TAG, "线程已启动/待退出禁止重复执行run");
return;
}
isThreadStarted = true; // 标记线程启动成功(仅执行一次)
lastRemindTime = 0; // 初始化上次提醒时间
isThreadStarted = true;
lastRemindTime = 0;
}
// 步骤1配置同步重试(等待Service同步配置避免阈值无效
// 配置同步重试(失败则兜底默认值
boolean configValid = syncConfigRetry();
if (!configValid) {
LogUtils.e(TAG, "run: 配置同步失败,使用默认配置兜底启动");
LogUtils.e(TAG, "配置同步失败,使用默认阈值兜底启动");
}
// 步骤2初始化延迟(等待服务/系统资源就绪,避免启动初期异常
// 初始化延迟(等待服务就绪
try {
LogUtils.d(TAG, "run: 初始化延迟" + INIT_DELAY_TIME + "ms");
LogUtils.d(TAG, "初始化延迟" + INIT_DELAY_TIME + "ms");
Thread.sleep(INIT_DELAY_TIME);
// 延迟期间检测退出标记,避免线程启动后立即销毁
if (isExist) {
LogUtils.d(TAG, "run: 初始化延迟期间收到退出指令,线程终止");
LogUtils.d(TAG, "延迟期间收到退出指令,线程终止");
cleanThreadState();
return;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 保留中断标记
LogUtils.e(TAG, "run: 初始化延迟被中断,线程终止");
Thread.currentThread().interrupt();
LogUtils.e(TAG, "初始化延迟被中断,线程终止", e);
cleanThreadState();
return;
}
// 步骤3核心业务循环isExist=true时立即退出优化轮询效率
LogUtils.d(TAG, "run: 进入电量检测核心循环,检测间隔=" + sleepTime + "ms");
// 核心检测循环(快速退出+并发安全
LogUtils.d(TAG, "进入电量检测循环,间隔" + sleepTime + "ms");
while (!isExist) {
try {
// 快速退出判断(循环头部+尾部双重校验,提升退出效率)
if (isExist) break;
// 状态1提醒功能关闭 → 仅休眠,不执行检测逻辑
// 提醒关闭/电量无效,直接休眠
if (!isReminding) {
safeSleep(sleepTime);
continue;
}
// 状态2电量数据无效 → 休眠重试(避免无效计算)
if (quantityOfElectricity < 0 || quantityOfElectricity > 100) {
LogUtils.w(TAG, "run: 电量数据无效(" + quantityOfElectricity + "),休眠重试");
LogUtils.w(TAG, "电量无效(" + quantityOfElectricity + "),休眠重试");
safeSleep(sleepTime);
continue;
}
// 状态3正常检测 → 锁保护核心逻辑,解决并发安全(计时器+提醒判断)
// 锁保护核心提醒逻辑
synchronized (mLock) {
// 快速退出(锁内再次校验,避免持有锁期间收到退出指令)
if (isExist) break;
// 防重复提醒间隔未到/计时器运行中 → 跳过
// 防重复提醒间隔未到/计时器运行中
long currentTime = System.currentTimeMillis();
if ((currentTime - lastRemindTime < REMIND_INTERVAL) || isRemindTimerRunning) {
safeSleep(sleepTime);
continue;
}
// 充电状态提醒(开关+阈值双重校验)
if (isCharging && isEnableChargeReminder) {
if (quantityOfElectricity >= chargeReminderValue) {
sendNotificationMessage("+"); // 充电提醒标识
lastRemindTime = currentTime;
LogUtils.d(TAG, "run: 触发充电提醒 → 电量:" + quantityOfElectricity + ",阈值:" + chargeReminderValue);
startRemindRecoveryTimer(); // 启动恢复计时器,避免重复提醒
}
// 充电提醒触发
if (isCharging && isEnableChargeReminder && quantityOfElectricity >= chargeReminderValue) {
LogUtils.d(TAG, "触发充电提醒:电量" + quantityOfElectricity + "≥阈值" + chargeReminderValue);
sendNotificationMessage("+");
lastRemindTime = currentTime;
startRemindRecoveryTimer();
}
// 耗电状态提醒(同理,开关+阈值双重校验)
else if (!isCharging && isEnableUsageReminder) {
if (quantityOfElectricity <= usageReminderValue) {
sendNotificationMessage("-"); // 耗电提醒标识
lastRemindTime = currentTime;
LogUtils.d(TAG, "run: 触发耗电提醒 → 电量:" + quantityOfElectricity + ",阈值:" + usageReminderValue);
startRemindRecoveryTimer(); // 启动恢复计时器
}
// 耗电提醒触发
else if (!isCharging && isEnableUsageReminder && quantityOfElectricity <= usageReminderValue) {
LogUtils.d(TAG, "触发耗电提醒:电量" + quantityOfElectricity + "≤阈值" + usageReminderValue);
sendNotificationMessage("-");
lastRemindTime = currentTime;
startRemindRecoveryTimer();
}
}
// 检测间隔休眠(循环尾部休眠,避免空轮询)
safeSleep(sleepTime);
} catch (Exception e) {
LogUtils.e(TAG, "run: 线程运行异常,休眠重试", e);
safeSleep(sleepTime); // 异常后强制休眠避免死循环占用CPU
LogUtils.e(TAG, "线程运行异常,休眠重试", e);
safeSleep(sleepTime);
}
}
// 步骤4线程退出清理无论正常/异常退出,必执行)
// 退出清理
cleanThreadState();
LogUtils.d(TAG, "run: 线程核心循环退出,线程ID=" + Thread.currentThread().getId() + ",最终状态=" + getState());
LogUtils.d(TAG, "线程循环退出ID" + Thread.currentThread().getId());
}
// 新增:配置同步重试(独立封装,提升代码可读性,修复阈值校验逻辑)
// ================================== 核心业务辅助方法(配置同步+消息发送+计时器)=================================
/**
* 配置同步重试(确保阈值有效,失败兜底)
*/
private boolean syncConfigRetry() {
int retryCount = 0;
while (retryCount < CONFIG_RETRY_MAX) {
synchronized (mLock) {
// 配置有效阈值0-100→ 重试成功
if ((chargeReminderValue >= 0 && chargeReminderValue <= 100)
&& (usageReminderValue >= 0 && usageReminderValue <= 100)) {
LogUtils.d(TAG, "syncConfigRetry: 配置同步成功,重试次数=" + retryCount);
&& (usageReminderValue >= 0 && usageReminderValue <= 100)) {
LogUtils.d(TAG, "配置同步成功,重试次数" + retryCount);
return true;
}
}
// 配置无效 → 休眠1秒重试
LogUtils.w(TAG, "syncConfigRetry: 配置未同步(阈值无效),重试次数=" + (retryCount + 1));
LogUtils.w(TAG, "配置未同步,重试次数:" + (retryCount + 1));
try {
Thread.sleep(1000);
retryCount++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LogUtils.e(TAG, "syncConfigRetry: 配置重试被中断", e);
LogUtils.e(TAG, "配置重试被中断", e);
return false;
}
}
// 重试失败 → 用默认阈值兜底(确保线程能正常启动)
// 兜底默认阈值
synchronized (mLock) {
chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100);
if (chargeReminderValue < 0 || chargeReminderValue > 100) {
chargeReminderValue = 80;
}
usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100);
if (usageReminderValue < 0 || usageReminderValue > 100) {
usageReminderValue = 20;
}
LogUtils.e(TAG, "syncConfigRetry: 配置重试失败,使用默认阈值 → 充电:" + chargeReminderValue + ",耗电:" + usageReminderValue);
chargeReminderValue = (chargeReminderValue < 0 || chargeReminderValue > 100) ? 80 : chargeReminderValue;
usageReminderValue = (usageReminderValue < 0 || usageReminderValue > 100) ? 20 : usageReminderValue;
LogUtils.e(TAG, "配置重试失败,兜底阈值:充电" + chargeReminderValue + ",耗电" + usageReminderValue);
}
return false;
}
// 发送提醒消息核心修复Message回收写法+优化容错适配全Android版本
/**
* 发送提醒消息弱引用Handler+Message复用防泄漏
*/
private void sendNotificationMessage(String content) {
LogUtils.d(TAG, "sendNotificationMessage: 准备发送提醒消息,内容=" + content);
// 前置校验:线程退出/提醒关闭 → 跳过
LogUtils.d(TAG, "准备发送提醒消息" + content);
if (isExist || !isReminding) {
LogUtils.d(TAG, "sendNotificationMessage: 线程退出/提醒关闭,跳过发送");
LogUtils.d(TAG, "线程退出/提醒关闭,跳过发送");
return;
}
// 弱引用获取Handler避免持有Service引用导致内存泄漏
ControlCenterServiceHandler handler = null;
if (mwrControlCenterServiceHandler != null) {
handler = mwrControlCenterServiceHandler.get();
}
ControlCenterServiceHandler handler = mwrControlCenterServiceHandler != null ? mwrControlCenterServiceHandler.get() : null;
if (handler == null) {
LogUtils.w(TAG, "sendNotificationMessage: Handler已回收发送失败");
LogUtils.w(TAG, "Handler已回收发送失败");
return;
}
// 复用Message减少内存分配发送提醒指令
Message message = Message.obtain(handler, ControlCenterServiceHandler.MSG_REMIND_TEXT, content);
try {
handler.sendMessage(message);
LogUtils.d(TAG, "sendNotificationMessage: 提醒消息发送成功");
LogUtils.d(TAG, "提醒消息发送成功");
} catch (Exception e) {
LogUtils.e(TAG, "sendNotificationMessage: 消息发送异常", e);
// 核心修复Message回收改为实例方法兼容所有Android版本且仅在发送失败时回收
if (message != null) {
message.recycle(); // 正确写法:实例方法回收,避免内存泄漏
}
LogUtils.e(TAG, "消息发送异常", e);
if (message != null) message.recycle();
}
}
// 提醒恢复计时器(核心优化:弱引用线程实例+双重锁保护,彻底解决并发安全)
/**
* 启动提醒恢复计时器(防重复提醒,弱引用线程+锁保护)
*/
private void startRemindRecoveryTimer() {
LogUtils.d(TAG, "startRemindRecoveryTimer: 启动提醒恢复计时器,间隔=" + REMIND_INTERVAL + "ms");
LogUtils.d(TAG, "启动提醒恢复计时器,间隔" + REMIND_INTERVAL + "ms");
synchronized (mLock) {
// 前置校验:线程退出/计时器已运行/单例已销毁 → 跳过
if (isExist || isRemindTimerRunning || sInstance != this) {
LogUtils.w(TAG, "startRemindRecoveryTimer: 计时器启动条件不满足,跳过");
LogUtils.w(TAG, "计时器启动条件不满足,跳过");
return;
}
// 临时关闭提醒(避免恢复前重复触发),标记计时器运行中
isReminding = false;
isRemindTimerRunning = true;
}
// 计时器线程弱引用当前RemindThread实例避免计时器持有线程导致内存泄漏
final WeakReference<RemindThread> threadWeakRef = new WeakReference<>(this);
final WeakReference<RemindThread> threadRef = new WeakReference<>(this);
new Thread(new Runnable() {
@Override
public void run() {
try {
// 计时器休眠(间隔时间到后恢复提醒)
Thread.sleep(REMIND_INTERVAL);
// 弱引用获取线程实例(避免内存泄漏)
RemindThread thread = threadWeakRef.get();
RemindThread thread = threadRef.get();
if (thread == null || thread.isExist || sInstance != thread) {
LogUtils.d(TAG, "startRemindRecoveryTimer: 线程已销毁/退出,不恢复提醒");
LogUtils.d(TAG, "线程已销毁,不恢复提醒");
return;
}
// 锁保护恢复逻辑(确保状态一致性)
synchronized (thread.mLock) {
// 再次校验:线程未退出+单例未切换+有任一提醒开关开启 → 恢复提醒
if (!thread.isExist && sInstance == thread
&& (thread.isEnableChargeReminder || thread.isEnableUsageReminder)) {
&& (thread.isEnableChargeReminder || thread.isEnableUsageReminder)) {
thread.isReminding = true;
LogUtils.d(TAG, "startRemindRecoveryTimer: 提醒功能恢复开启");
LogUtils.d(TAG, "提醒功能恢复开启");
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LogUtils.e(TAG, "startRemindRecoveryTimer: 计时器被中断", e);
LogUtils.e(TAG, "计时器被中断", e);
} finally {
// 无论是否异常,都重置计时器标记(锁保护,避免并发冲突)
RemindThread thread = threadWeakRef.get();
RemindThread thread = threadRef.get();
if (thread != null) {
synchronized (thread.mLock) {
if (sInstance == thread) {
thread.isRemindTimerRunning = false;
}
if (sInstance == thread) thread.isRemindTimerRunning = false;
}
}
LogUtils.d(TAG, "startRemindRecoveryTimer: 提醒恢复计时器执行完成");
LogUtils.d(TAG, "提醒恢复计时器执行完成");
}
}
}, "RemindRecoveryTimer").start(); // 给计时器线程命名,便于调试
}, "RemindRecoveryTimer").start();
}
// 安全停止线程(核心修复:触发退出+中断休眠+重置状态,确保快速退出)
// ================================== 线程控制方法(停止+休眠+状态清理)=================================
/**
* 安全停止线程(快速退出+中断休眠)
*/
public void stopThread() {
LogUtils.d(TAG, "stopThread: 开始安全停止线程,当前线程状态=" + getState());
LogUtils.d(TAG, "开始停止线程,当前状态" + getState());
synchronized (mLock) {
// 标记退出+关闭所有开关+停止计时器(原子操作,避免状态混乱)
isExist = true;
isReminding = false;
isRemindTimerRunning = false;
LogUtils.d(TAG, "stopThread: 线程退出标记已置位,所有开关已关闭");
}
// 中断线程唤醒sleep/wait状态加速线程退出核心循环避免阻塞
if (isAlive()) {
interrupt();
LogUtils.d(TAG, "stopThread: 线程已中断,加速退出");
}
// 兜底单例置空避免Service销毁后单例残留
if (sInstance == this) {
sInstance = null;
LogUtils.d(TAG, "线程已中断,加速退出");
}
if (sInstance == this) sInstance = null;
}
// 废弃原强制停止方法统一用stopThread(),避免方法冗余导致调用混淆)
/**
* 废弃方法统一用stopThread
*/
@Deprecated
public void forceStopThread() {
LogUtils.w(TAG, "forceStopThread: 该方法已废弃请调用stopThread()");
LogUtils.w(TAG, "forceStopThread已废弃请调用stopThread");
stopThread();
}
// 安全休眠(修复:保留中断标记,避免线程退出逻辑失效)
/**
* 安全休眠(保留中断标记,确保退出逻辑生效)
*/
private void safeSleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 关键:保留中断标记,确保线程能快速退出
LogUtils.w(TAG, "safeSleep: 休眠被中断,线程准备退出");
Thread.currentThread().interrupt();
LogUtils.w(TAG, "休眠被中断,线程准备退出");
}
}
// 清理线程运行状态(原子操作,确保退出后状态干净,无残留)
/**
* 清理线程运行状态(退出后重置所有标记)
*/
private void cleanThreadState() {
LogUtils.d(TAG, "cleanThreadState: 清理线程运行状态");
LogUtils.d(TAG, "清理线程运行状态");
synchronized (mLock) {
isReminding = false;
isExist = true;
@@ -374,74 +353,76 @@ public class RemindThread extends Thread {
isRemindTimerRunning = false;
lastRemindTime = 0;
}
// 辅助:中断线程(兜底加速退出)
if (isAlive()) {
interrupt();
}
// 辅助:单例置空(终极兜底)
if (sInstance == this) {
sInstance = null;
}
if (isAlive()) interrupt();
if (sInstance == this) sInstance = null;
}
// 重置线程初始状态(构造方法专用,初始化所有变量,避免脏数据)
// ================================== 状态重置方法(初始化+运行时重置)=================================
/**
* 初始状态重置(构造器专用)
*/
private void resetThreadStateInternal() {
LogUtils.d(TAG, "resetThreadStateInternal: 重置线程初始状态");
LogUtils.d(TAG, "重置线程初始状态");
synchronized (mLock) {
// 线程状态
isExist = false;
isReminding = false;
isThreadStarted = false;
isEnableUsageReminder = false;
isRemindTimerRunning = false;
lastRemindTime = 0;
// 业务配置
isEnableChargeReminder = false;
isEnableUsageReminder = false;
sleepTime = DEFAULT_SLEEP_TIME;
chargeReminderValue = -1;
usageReminderValue = -1;
quantityOfElectricity = -1;
isCharging = false;
isRemindTimerRunning = false;
lastRemindTime = 0;
}
}
// 外部调用:重置线程运行状态(不重置配置,仅重置运行时标记)
/**
* 运行时状态重置(外部调用,不重置配置)
*/
public void resetThreadState() {
LogUtils.d(TAG, "resetThreadState: 重置线程运行状态");
LogUtils.d(TAG, "重置线程运行状态");
synchronized (mLock) {
isExist = false;
isReminding = (isEnableChargeReminder || isEnableUsageReminder); // 按开关自动恢复提醒状态
isRemindTimerRunning = false;
lastRemindTime = 0;
isReminding = (isEnableChargeReminder || isEnableUsageReminder);
}
LogUtils.d(TAG, "运行状态重置完成,全局提醒:" + isReminding);
}
// 核心:同步完整配置(优化参数校验+原子操作,确保配置一致性)
// ================================== 配置同步方法(完整同步配置,原子操作)=================================
/**
* 同步应用配置(参数校验+范围限制,立即生效)
*/
public void setAppConfigBean(AppConfigBean config) {
if (config == null) {
LogUtils.e(TAG, "setAppConfigBean: 配置Bean为空同步失败");
LogUtils.e(TAG, "配置Bean为空同步失败");
return;
}
LogUtils.d(TAG, "setAppConfigBean: 开始同步配置充电阈值" + config.getChargeReminderValue() + ",耗电阈值" + config.getUsageReminderValue());
LogUtils.d(TAG, "开始同步配置充电阈值" + config.getChargeReminderValue() + ",耗电阈值" + config.getUsageReminderValue());
synchronized (mLock) {
// 1. 同步提醒开关(单独开关+全局开关联动)
this.isEnableChargeReminder = config.isEnableChargeReminder();
this.isEnableUsageReminder = config.isEnableUsageReminder();
this.isReminding = (this.isEnableChargeReminder || this.isEnableUsageReminder);
// 2. 同步阈值强制0-100范围避免无效值
this.chargeReminderValue = Math.min(Math.max(config.getChargeReminderValue(), 0), 100);
this.usageReminderValue = Math.min(Math.max(config.getUsageReminderValue(), 0), 100);
// 3. 同步检测间隔(强制≥最小间隔,避免频繁轮询耗电)
this.sleepTime = Math.max(config.getBatteryDetectInterval(), MIN_SLEEP_TIME);
// 4. 同步实时电池状态(配置中携带最新状态,立即生效)
this.quantityOfElectricity = Math.min(Math.max(config.getCurrentBatteryValue(), 0), 100);
this.isCharging = config.isCharging();
// 同步开关
isEnableChargeReminder = config.isEnableChargeReminder();
isEnableUsageReminder = config.isEnableUsageReminder();
isReminding = (isEnableChargeReminder || isEnableUsageReminder);
// 同步阈值0-100限制
chargeReminderValue = Math.min(Math.max(config.getChargeReminderValue(), 0), 100);
usageReminderValue = Math.min(Math.max(config.getUsageReminderValue(), 0), 100);
// 同步检测间隔(≥最小间隔)
sleepTime = Math.max(config.getBatteryDetectInterval(), MIN_SLEEP_TIME);
// 同步电池状态
quantityOfElectricity = Math.min(Math.max(config.getCurrentBatteryValue(), 0), 100);
isCharging = config.isCharging();
}
LogUtils.d(TAG, "setAppConfigBean: 配置同步完成检测间隔" + sleepTime + "ms提醒开启:" + isReminding);
LogUtils.d(TAG, "配置同步完成检测间隔" + sleepTime + "ms全局提醒" + isReminding);
}
// ====================== Setter/Getter 方法(全量锁保护,确保线程安全,优化参数校验) ======================
// ================================== Setter/Getter锁保护,线程安全+精准日志)=================================
public boolean isExist() {
synchronized (mLock) {
return isExist;
@@ -451,7 +432,7 @@ public class RemindThread extends Thread {
public void setIsExist(boolean isExist) {
synchronized (mLock) {
this.isExist = isExist;
LogUtils.d(TAG, "setIsExist: 线程退出标记设置为" + isExist);
LogUtils.d(TAG, "设置线程退出标记" + isExist);
}
}
@@ -464,7 +445,7 @@ public class RemindThread extends Thread {
public void setIsReminding(boolean isReminding) {
synchronized (mLock) {
this.isReminding = isReminding;
LogUtils.d(TAG, "setIsReminding: 全局提醒开关设置为" + isReminding);
LogUtils.d(TAG, "设置全局提醒开关" + isReminding);
}
}
@@ -483,8 +464,8 @@ public class RemindThread extends Thread {
public void setIsEnableUsageReminder(boolean isEnableUsageReminder) {
synchronized (mLock) {
this.isEnableUsageReminder = isEnableUsageReminder;
this.isReminding = (this.isEnableChargeReminder || this.isEnableUsageReminder); // 联动全局开关
LogUtils.d(TAG, "setIsEnableUsageReminder: 耗电提醒开关设置为" + isEnableUsageReminder + ",全局提醒:" + this.isReminding);
isReminding = (isEnableChargeReminder || isEnableUsageReminder);
LogUtils.d(TAG, "设置耗电提醒开关" + isEnableUsageReminder + ",全局提醒:" + isReminding);
}
}
@@ -497,8 +478,8 @@ public class RemindThread extends Thread {
public void setIsEnableChargeReminder(boolean isEnableChargeReminder) {
synchronized (mLock) {
this.isEnableChargeReminder = isEnableChargeReminder;
this.isReminding = (this.isEnableChargeReminder || this.isEnableUsageReminder); // 联动全局开关
LogUtils.d(TAG, "setIsEnableChargeReminder: 充电提醒开关设置为" + isEnableChargeReminder + ",全局提醒:" + this.isReminding);
isReminding = (isEnableChargeReminder || isEnableUsageReminder);
LogUtils.d(TAG, "设置充电提醒开关" + isEnableChargeReminder + ",全局提醒:" + isReminding);
}
}
@@ -510,8 +491,8 @@ public class RemindThread extends Thread {
public void setSleepTime(int sleepTime) {
synchronized (mLock) {
this.sleepTime = Math.max(sleepTime, MIN_SLEEP_TIME); // 强制≥最小间隔
LogUtils.d(TAG, "setSleepTime: 检测间隔设置为" + this.sleepTime + "ms");
this.sleepTime = Math.max(sleepTime, MIN_SLEEP_TIME);
LogUtils.d(TAG, "设置检测间隔" + this.sleepTime + "ms");
}
}
@@ -523,8 +504,8 @@ public class RemindThread extends Thread {
public void setChargeReminderValue(int chargeReminderValue) {
synchronized (mLock) {
this.chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100); // 强制0-100
LogUtils.d(TAG, "setChargeReminderValue: 充电提醒阈值设置为" + this.chargeReminderValue);
this.chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100);
LogUtils.d(TAG, "设置充电提醒阈值" + this.chargeReminderValue);
}
}
@@ -536,8 +517,8 @@ public class RemindThread extends Thread {
public void setUsageReminderValue(int usageReminderValue) {
synchronized (mLock) {
this.usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100); // 强制0-100
LogUtils.d(TAG, "setUsageReminderValue: 耗电提醒阈值设置为" + this.usageReminderValue);
this.usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100);
LogUtils.d(TAG, "设置耗电提醒阈值" + this.usageReminderValue);
}
}
@@ -549,8 +530,8 @@ public class RemindThread extends Thread {
public void setQuantityOfElectricity(int quantityOfElectricity) {
synchronized (mLock) {
this.quantityOfElectricity = Math.min(Math.max(quantityOfElectricity, 0), 100); // 强制0-100
LogUtils.d(TAG, "setQuantityOfElectricity: 当前电量更新为" + this.quantityOfElectricity);
this.quantityOfElectricity = Math.min(Math.max(quantityOfElectricity, 0), 100);
LogUtils.d(TAG, "更新当前电量" + this.quantityOfElectricity);
}
}
@@ -563,36 +544,34 @@ public class RemindThread extends Thread {
public void setIsCharging(boolean isCharging) {
synchronized (mLock) {
this.isCharging = isCharging;
LogUtils.d(TAG, "setIsCharging: 充电状态更新为" + isCharging);
LogUtils.d(TAG, "更新充电状态" + isCharging);
}
}
// 核心判断线程是否正在运行Service依赖此方法精准判断运行状态
// ================================== 核心状态判断Service依赖精准校验=================================
public boolean isRunning() {
synchronized (mLock) {
// 条件:未退出 + 已启动 + 线程存活 → 视为正在运行
return !isExist && isThreadStarted && isAlive();
boolean running = !isExist && isThreadStarted && isAlive();
LogUtils.d(TAG, "线程运行状态:" + running + "(退出:" + isExist + ",已启动:" + isThreadStarted + ",存活:" + isAlive() + "");
return running;
}
}
// 彻底释放线程资源Service销毁时调用避免内存泄漏
// ================================== 资源释放(彻底断开引用,防内存泄漏)=================================
public void releaseResources() {
LogUtils.d(TAG, "releaseResources: 开始释放线程资源");
LogUtils.d(TAG, "开始释放线程资源");
synchronized (mLock) {
// 1. 释放上下文(避免持有应用上下文导致内存泄漏)
mContext = null;
// 2. 清空Handler弱引用加速回收
if (mwrControlCenterServiceHandler != null) {
mwrControlCenterServiceHandler.clear();
mwrControlCenterServiceHandler = null;
}
// 3. 清理运行状态(确保所有标记干净)
cleanThreadState();
}
LogUtils.d(TAG, "releaseResources: 线程资源释放完成");
LogUtils.d(TAG, "线程资源释放完成");
}
// 重写toString(),便于调试(新增:打印线程核心状态)
// ================================== 调试辅助toString打印核心状态=================================
@Override
public String toString() {
return "RemindThread{" +
@@ -604,6 +583,7 @@ public class RemindThread extends Thread {
", usageThreshold=" + getUsageReminderValue() +
", currentBattery=" + getQuantityOfElectricity() +
", isCharging=" + isCharging() +
", sleepTime=" + getSleepTime() + "ms" +
'}';
}
}

View File

@@ -7,6 +7,10 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.MainActivity;
@@ -14,342 +18,393 @@ import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.models.NotificationMessage;
/**
* 通知工具类统一管理前台服务通知、提醒通知适配API19-34强化容错与兼容性
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:42
* @Describe 通知工具类统一管理前台服务通知、电池提醒通知适配API19-30强化兼容性与容错性取消所有振动
*/
public class NotificationManagerUtils {
private static final String TAG = "NotificationManagerUtils";
// 通知常量渠道ID、通知ID避免魔法值渠道名/描述优化用户感知)
// ================================== 静态常量(置顶统一管理,杜绝魔法值)=================================
public static final String TAG = "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";
// 通知ID唯一标识避免重复/混淆)
public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001;
public static final int NOTIFY_ID_REMIND = 1002;
// 成员变量(封装属性,提升安全性)
private Notification mForegroundServiceNotify;
private NotificationManager mNotificationManager;
private Context mContext;
// 低版本通知兼容配置API<21 必须,避免通知图标显示异常)
// 低版本兼容配置API<21 通知图标默认值,避免显示异常)
private static final int NOTIFICATION_DEFAULT_ICON = R.drawable.ic_launcher;
// 构造方法(强化判空,避免初始化失败+内存泄漏)
// ================================== 成员变量(按功能分类,封装保护,避免外部篡改)=================================
// 核心依赖资源
private Context mContext;
private NotificationManager mNotificationManager;
// 前台服务通知实例(单独持有,便于更新/取消)
private Notification mForegroundServiceNotify;
// ================================== 构造方法(初始化核心资源,前置校验防崩溃)=================================
public NotificationManagerUtils(Context context) {
LogUtils.d(TAG, "NotificationManagerUtils: 初始化通知工具类");
LogUtils.d(TAG, "初始化通知工具类");
// 前置校验Context非空避免后续空指针
if (context == null) {
LogUtils.e(TAG, "NotificationManagerUtils: Context is null初始化失败");
LogUtils.e(TAG, "初始化失败Context为空");
return;
}
// 持有应用上下文,杜绝内存泄漏
this.mContext = context.getApplicationContext();
// 获取系统通知服务,初始化核心依赖
this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
// 初始化通知渠道API26+ 必需)
initNotificationChannels();
LogUtils.d(TAG, "NotificationManagerUtils: 工具类初始化完成");
LogUtils.d(TAG, "通知工具类初始化完成");
}
// 通知渠道初始化适配API26+,保持稳定)
// ================================== 核心初始化通知渠道适配API26+,取消振动)=================================
/**
* 初始化通知渠道:前台服务渠道(低打扰)、电池提醒渠道(正常感知),均关闭振动
*/
private void initNotificationChannels() {
LogUtils.d(TAG, "initNotificationChannels: 初始化通知渠道");
LogUtils.d(TAG, "开始初始化通知渠道");
// API<26 无渠道机制,直接跳过;通知服务为空也不执行
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || mNotificationManager == null) {
LogUtils.d(TAG, "initNotificationChannels: API<26 或 NotificationManager为空,跳过渠道初始化");
LogUtils.d(TAG, "无需初始化渠道:API<26 或 NotificationManager为空");
return;
}
// 前台服务渠道(低优先级,无打扰
// 1. 前台服务渠道(低优先级,后台运行无打扰,关闭振动
NotificationChannel foregroundChannel = new NotificationChannel(
CHANNEL_ID_FOREGROUND,
"电池服务保活",
NotificationManager.IMPORTANCE_LOW
);
foregroundChannel.setDescription("电池提醒服务后台稳定运行,无弹窗、无震动、无声音");
foregroundChannel.setDescription("电池监测服务后台稳定运行,无弹窗、无震动、无声音");
foregroundChannel.enableLights(false);
foregroundChannel.enableVibration(false);
foregroundChannel.enableVibration(false); // 关闭振动
foregroundChannel.setSound(null, null);
foregroundChannel.setShowBadge(false);
foregroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
// 电池提醒渠道(中优先级,确保感知
// 2. 电池提醒渠道(中优先级,确保用户感知,关闭振动
NotificationChannel remindChannel = new NotificationChannel(
CHANNEL_ID_REMIND,
"电池状态提醒",
NotificationManager.IMPORTANCE_DEFAULT
);
remindChannel.setDescription("电池充电满/低电量提醒,及时保护电池健康");
remindChannel.setDescription("电池满/低电量提醒,及时保护电池健康(无振动)");
remindChannel.enableLights(true);
remindChannel.enableVibration(true);
remindChannel.setVibrationPattern(new long[]{100, 200, 100});
remindChannel.enableVibration(false); // 关闭振动(删除振动模式配置)
remindChannel.setSound(null, null);
remindChannel.setShowBadge(false);
remindChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
// 注册渠道到系统
mNotificationManager.createNotificationChannel(foregroundChannel);
mNotificationManager.createNotificationChannel(remindChannel);
LogUtils.d(TAG, "initNotificationChannels: 渠道初始化完成");
LogUtils.d(TAG, "通知渠道初始化完成(前台+提醒各1个均关闭振动");
}
// ====================== 核心修复适配低API移除 FOREGROUND_SERVICE_TYPE ======================
// 启动前台服务通知全版本兼容API19-34通用
// ================================== 对外核心方法(前台服务通知:启动+更新+取消)=================================
/**
* 启动前台服务通知适配API19-30无FOREGROUND_SERVICE_TYPE全版本通用
*/
public void startForegroundServiceNotify(Service service, NotificationMessage message) {
LogUtils.d(TAG, "startForegroundServiceNotify: 启动前台服务通知");
LogUtils.d(TAG, "启动前台服务通知通知ID" + NOTIFY_ID_FOREGROUND_SERVICE);
// 前置校验:依赖参数非空,避免崩溃
if (service == null || message == null || mNotificationManager == null) {
LogUtils.e(TAG, "startForegroundServiceNotify: 依赖参数为空,启动失败");
LogUtils.e(TAG, "启动失败:Service/消息/NotificationManager为空");
return;
}
// 构建前台通知实例
mForegroundServiceNotify = buildForegroundNotification(message);
if (mForegroundServiceNotify == null) {
LogUtils.e(TAG, "startForegroundServiceNotify: 通知构建失败,启动失败");
LogUtils.e(TAG, "启动失败:前台通知构建失败");
return;
}
// 修复:移除 API30+ 专属的 FOREGROUND_SERVICE_TYPE用全版本通用写法
// 启动前台服务全版本通用写法适配API30无类型限制
try {
// 所有API版本统一调用startForeground(通知ID, 通知实例)
service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "startForegroundServiceNotify: 启动成功通知ID=" + NOTIFY_ID_FOREGROUND_SERVICE);
LogUtils.d(TAG, "前台服务通知启动成功");
} catch (Exception e) {
LogUtils.e(TAG, "startForegroundServiceNotify: 启动异常(可能是权限或5秒内未调用", e);
LogUtils.e(TAG, "启动异常可能是5秒内未调用/权限缺失", e);
}
}
// 发送电池提醒通知(保持稳定)
public void showRemindNotification(Context context, NotificationMessage message) {
LogUtils.d(TAG, "showRemindNotification: 发送提醒通知,标题=" + (message.getTitle() != null ? message.getTitle() : "默认提醒"));
if (context == null || message == null || mNotificationManager == null) {
LogUtils.e(TAG, "showRemindNotification: 依赖参数为空,发送失败");
/**
* 更新前台服务通知内容复用通知ID实时刷新显示
*/
public void updateForegroundServiceNotify(NotificationMessage message) {
LogUtils.d(TAG, "更新前台服务通知通知ID" + NOTIFY_ID_FOREGROUND_SERVICE);
if (message == null || mNotificationManager == null) {
LogUtils.e(TAG, "更新失败:消息/NotificationManager为空");
return;
}
// 重新构建通知,覆盖旧实例
mForegroundServiceNotify = buildForegroundNotification(message);
if (mForegroundServiceNotify == null) {
LogUtils.e(TAG, "更新失败:前台通知构建失败");
return;
}
// 刷新通知显示
try {
mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "前台服务通知更新成功");
} catch (Exception e) {
LogUtils.e(TAG, "更新异常", e);
}
}
/**
* 取消前台服务通知销毁Service时调用避免通知残留
*/
public void cancelForegroundServiceNotify() {
LogUtils.d(TAG, "取消前台服务通知通知ID" + NOTIFY_ID_FOREGROUND_SERVICE);
// 先取消系统通知
cancelNotification(NOTIFY_ID_FOREGROUND_SERVICE);
// 置空实例加速GC回收
mForegroundServiceNotify = null;
LogUtils.d(TAG, "前台服务通知取消完成,实例已置空");
}
// ================================== 对外核心方法(电池提醒通知:发送)=================================
/**
* 发送电池提醒通知(满电/低电量,适配全版本提醒效果,无振动)
*/
public void showRemindNotification(Context context, NotificationMessage message) {
LogUtils.d(TAG, "发送电池提醒通知,标题:" + (message.getTitle() != null ? message.getTitle() : "默认提醒"));
// 前置校验:依赖参数非空
if (context == null || message == null || mNotificationManager == null) {
LogUtils.e(TAG, "发送失败Context/消息/NotificationManager为空");
return;
}
// 构建提醒通知实例
Notification remindNotify = buildRemindNotification(context, message);
if (remindNotify == null) {
LogUtils.e(TAG, "showRemindNotification: 通知构建失败,发送失败");
LogUtils.e(TAG, "发送失败:提醒通知构建失败");
return;
}
// 发送通知到系统
try {
mNotificationManager.notify(NOTIFY_ID_REMIND, remindNotify);
LogUtils.d(TAG, "电池提醒通知发送成功通知ID" + NOTIFY_ID_REMIND);
} catch (Exception e) {
LogUtils.e(TAG, "发送异常", e);
}
}
// ================================== 对外核心方法(通知取消:单个+全部)=================================
/**
* 取消指定ID的通知
*/
public void cancelNotification(int notifyId) {
LogUtils.d(TAG, "取消指定通知通知ID" + notifyId);
if (mNotificationManager == null) {
LogUtils.e(TAG, "取消失败NotificationManager为空");
return;
}
try {
mNotificationManager.notify(NOTIFY_ID_REMIND, remindNotify);
LogUtils.d(TAG, "showRemindNotification: 发送成功通知ID=" + NOTIFY_ID_REMIND);
mNotificationManager.cancel(notifyId);
LogUtils.d(TAG, "通知取消成功");
} catch (Exception e) {
LogUtils.e(TAG, "showRemindNotification: 发送异常", e);
LogUtils.e(TAG, "取消异常", e);
}
}
// 构建前台服务通知低版本API<21 兼容配置)
/**
* 取消所有通知(兜底场景使用)
*/
public void cancelAllNotifications() {
LogUtils.d(TAG, "取消所有通知");
if (mNotificationManager == null) {
LogUtils.e(TAG, "取消失败NotificationManager为空");
return;
}
try {
mNotificationManager.cancelAll();
LogUtils.d(TAG, "所有通知取消成功");
} catch (Exception e) {
LogUtils.e(TAG, "取消异常", e);
}
}
// ================================== 内部辅助方法(通知构建:前台服务通知)=================================
/**
* 构建前台服务通知API分级适配低版本无打扰高版本渠道管控无振动
*/
private Notification buildForegroundNotification(NotificationMessage message) {
if (message == null || mContext == null) {
LogUtils.e(TAG, "buildForegroundNotification: 参数为空,构建失败");
LogUtils.e(TAG, "前台通知构建失败:参数/Context为空");
return null;
}
String notifyTitle = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : "电池服务运行中";
String notifyContent = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : "后台持续监测电池状态,保护电池健康";
// 内容兜底:避免消息标题/内容为空
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : "电池服务运行中";
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : "后台持续监测电池状态,保护电池健康";
Notification.Builder builder;
// API分级构建确保全版本兼容
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID_FOREGROUND)
// API26+:绑定前台服务渠道,低优先级无打扰(渠道已关闭振动)
builder = new Notification.Builder(mContext, CHANNEL_ID_FOREGROUND)
.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(notifyTitle)
.setContentText(notifyContent)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(false)
.setOngoing(true)
.setOngoing(true) // 前台服务通知不可手动关闭
.setWhen(System.currentTimeMillis())
.setTicker(notifyTitle);
builder.setContentIntent(createJumpPendingIntent(mContext, 0));
notification = builder.build();
.setTicker(title);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Notification.Builder builder = new Notification.Builder(mContext)
// API21-25添加大图标+主题色,手动关闭声音震动
builder = new Notification.Builder(mContext)
.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setLargeIcon(getAppIcon(mContext))
.setColor(mContext.getResources().getColor(R.color.colorPrimary))
.setContentTitle(notifyTitle)
.setContentText(notifyContent)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(false)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setTicker(notifyTitle)
.setPriority(Notification.PRIORITY_LOW);
builder.setContentIntent(createJumpPendingIntent(mContext, 0));
notification = builder.build();
.setTicker(title)
.setPriority(Notification.PRIORITY_LOW); // 低优先级,减少打扰
} else {
Notification.Builder builder = new Notification.Builder(mContext)
// API<21基础配置适配旧机型
builder = new Notification.Builder(mContext)
.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(notifyTitle)
.setContentText(notifyContent)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(false)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setTicker(notifyTitle)
.setTicker(title)
.setPriority(Notification.PRIORITY_LOW);
builder.setContentIntent(createJumpPendingIntent(mContext, 0));
notification = builder.build();
}
// 低版本通知默认效果屏蔽(无渠道时手动关闭声音/震动)
// 绑定跳转意图:点击通知打开主页面
builder.setContentIntent(createJumpPendingIntent(mContext, 0));
Notification notification = builder.build();
// API<26无渠道手动屏蔽声音/震动,确保无打扰(彻底关闭振动)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
notification.defaults = 0;
notification.vibrate = new long[]{0};
notification.vibrate = new long[]{0}; // 振动时长设为0彻底关闭
notification.sound = null;
}
return notification;
}
// 构建电池提醒通知低版本API<21 兼容配置)
// ================================== 内部辅助方法(通知构建电池提醒通知=================================
/**
* 构建电池提醒通知API分级适配确保提醒效果无振动支持手动关闭
*/
private Notification buildRemindNotification(Context context, NotificationMessage message) {
if (context == null || message == null) {
LogUtils.e(TAG, "buildRemindNotification: 参数为空,构建失败");
LogUtils.e(TAG, "提醒通知构建失败:参数/Context为空");
return null;
}
String notifyTitle = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : "电池状态提醒";
String notifyContent = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : "电池状态异常,请及时处理(充电满/低电量)";
// 内容兜底:避免消息标题/内容为空
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : "电池状态提醒";
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : "电池状态异常,请及时处理";
// API分级构建确保提醒效果
Notification notification;
Notification.Builder builder;
// API分级构建确保提醒效果一致全版本关闭振动
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID_REMIND)
// API26+:绑定提醒渠道,渠道已关闭振动,无需额外配置
builder = new Notification.Builder(context, CHANNEL_ID_REMIND)
.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(notifyTitle)
.setContentText(notifyContent)
.setAutoCancel(true)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true) // 点击后自动关闭
.setOngoing(false)
.setWhen(System.currentTimeMillis())
.setTicker(notifyTitle)
.setVibrate(new long[]{100, 200, 100});
builder.setContentIntent(createJumpPendingIntent(context, 1));
notification = builder.build();
.setTicker(title);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Notification.Builder builder = new Notification.Builder(context)
// API21-25添加大图标+主题色关闭振动删除setVibrationPattern和振动默认值
builder = new Notification.Builder(context)
.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setLargeIcon(getAppIcon(context))
.setColor(context.getResources().getColor(R.color.colorPrimary))
.setContentTitle(notifyTitle)
.setContentText(notifyContent)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true)
.setOngoing(false)
.setWhen(System.currentTimeMillis())
.setTicker(notifyTitle)
.setPriority(Notification.PRIORITY_DEFAULT)
.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS)
.setVibrate(new long[]{100, 200, 100});
builder.setContentIntent(createJumpPendingIntent(context, 1));
notification = builder.build();
.setTicker(title)
.setPriority(Notification.PRIORITY_DEFAULT) // 中优先级,确保感知
.setDefaults(Notification.DEFAULT_LIGHTS); // 仅保留灯光提醒,关闭振动
} else {
Notification.Builder builder = new Notification.Builder(context)
// API<21基础配置仅保留灯光关闭振动
builder = new Notification.Builder(context)
.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(notifyTitle)
.setContentText(notifyContent)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true)
.setOngoing(false)
.setWhen(System.currentTimeMillis())
.setTicker(notifyTitle)
.setTicker(title)
.setPriority(Notification.PRIORITY_DEFAULT)
.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS)
.setVibrate(new long[]{100, 200, 100});
builder.setContentIntent(createJumpPendingIntent(context, 1));
notification = builder.build();
.setDefaults(Notification.DEFAULT_LIGHTS); // 仅保留灯光,关闭振动
}
return notification;
// 绑定跳转意图:点击通知打开主页面
builder.setContentIntent(createJumpPendingIntent(context, 1));
return builder.build();
}
// 内部辅助:创建跳转主页面的PendingIntent适配API31+安全要求)
// ================================== 内部辅助方法(通用创建跳转PendingIntent=================================
/**
* 创建跳转主页面的PendingIntent适配API30+安全要求添加IMMUTABLE标记
*/
private PendingIntent createJumpPendingIntent(Context context, int requestCode) {
// 跳转意图打开MainActivity清除栈顶重复页面
Intent jumpIntent = new Intent(context, MainActivity.class);
jumpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// API31+ 必须加FLAG_IMMUTABLE避免安全异常
// 适配API30+安全要求API23+ 必须添加FLAG_IMMUTABLE避免安全异常
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags |= PendingIntent.FLAG_IMMUTABLE;
}
return PendingIntent.getActivity(context, requestCode, jumpIntent, flags);
}
// 内部辅助获取APP图标API21+ 大图标显示)
private android.graphics.Bitmap getAppIcon(Context context) {
// ================================== 内部辅助方法通用获取APP图标=================================
/**
* 获取APP图标API21+ 大图标显示使用,失败则返回默认图标)
*/
private Bitmap getAppIcon(Context context) {
try {
android.content.pm.PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return android.graphics.BitmapFactory.decodeResource(context.getResources(), packageInfo.applicationInfo.icon);
} catch (Exception e) {
LogUtils.e(TAG, "getAppIcon: 获取图标异常", e);
return android.graphics.BitmapFactory.decodeResource(context.getResources(), NOTIFICATION_DEFAULT_ICON);
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return BitmapFactory.decodeResource(context.getResources(), packageInfo.applicationInfo.icon);
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "获取APP图标失败", e);
// 异常兜底:返回默认图标,避免显示空白
return BitmapFactory.decodeResource(context.getResources(), NOTIFICATION_DEFAULT_ICON);
}
}
// 取消前台服务通知适配Service销毁逻辑统一管理前台通知生命周期
public void cancelForegroundServiceNotify() {
LogUtils.d(TAG, "cancelForegroundServiceNotify: 取消前台服务通知ID=" + NOTIFY_ID_FOREGROUND_SERVICE);
// 1. 先取消通知实例(避免通知残留)
cancelNotification(NOTIFY_ID_FOREGROUND_SERVICE);
// 2. 置空前台通知引用帮助GC回收
if (mForegroundServiceNotify != null) {
mForegroundServiceNotify = null;
LogUtils.d(TAG, "cancelForegroundServiceNotify: 前台通知实例置空完成");
}
}
// 取消指定ID通知保持稳定
public void cancelNotification(int notifyId) {
LogUtils.d(TAG, "cancelNotification: 取消通知ID=" + notifyId);
if (mNotificationManager != null) {
try {
mNotificationManager.cancel(notifyId);
LogUtils.d(TAG, "cancelNotification: 取消成功");
} catch (Exception e) {
LogUtils.e(TAG, "cancelNotification: 取消异常", e);
}
} else {
LogUtils.e(TAG, "cancelNotification: NotificationManager为空取消失败");
}
}
// 取消所有通知(保持稳定)
public void cancelAllNotifications() {
LogUtils.d(TAG, "cancelAllNotifications: 取消所有通知");
if (mNotificationManager != null) {
try {
mNotificationManager.cancelAll();
LogUtils.d(TAG, "cancelAllNotifications: 取消成功");
} catch (Exception e) {
LogUtils.e(TAG, "cancelAllNotifications: 取消异常", e);
}
} else {
LogUtils.e(TAG, "cancelAllNotifications: NotificationManager为空取消失败");
}
}
// 更新前台服务通知(保持稳定)
public void updateForegroundServiceNotify(NotificationMessage message) {
LogUtils.d(TAG, "updateForegroundServiceNotify: 更新前台通知");
if (message == null || mNotificationManager == null) {
LogUtils.e(TAG, "updateForegroundServiceNotify: 参数为空,更新失败");
return;
}
mForegroundServiceNotify = buildForegroundNotification(message);
if (mForegroundServiceNotify != null) {
try {
mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "updateForegroundServiceNotify: 更新成功");
} catch (Exception e) {
LogUtils.e(TAG, "updateForegroundServiceNotify: 更新异常", e);
}
} else {
LogUtils.e(TAG, "updateForegroundServiceNotify: 通知构建失败,更新失败");
}
}
// 获取前台服务通知实例(封装属性,外部仅可读)
// ================================== 对外工具方法(获取前台通知实例,仅可读)=================================
public Notification getForegroundServiceNotify() {
return mForegroundServiceNotify;
}
// 释放资源(保持稳定)
// ================================== 资源释放(彻底释放依赖,避免内存泄漏)=================================
public void release() {
LogUtils.d(TAG, "release: 释放资源");
// 释放前先取消前台通知(兜底,避免资源泄漏
LogUtils.d(TAG, "开始释放通知工具类资源");
// 释放前先取消前台通知(兜底,避免残留
cancelForegroundServiceNotify();
// 置空核心依赖加速GC回收
mNotificationManager = null;
mContext = null;
LogUtils.d(TAG, "release: 资源释放完成");
LogUtils.d(TAG, "通知工具类资源释放完成");
}
}

View File

@@ -0,0 +1,14 @@
package cc.winboll.studio.powerbell.utils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:42
* @Describe NotificationManagerUtils
*/
public class NotificationManagerUtils6 {
public static final String TAG = "NotificationManagerUtils6";
}

View File

@@ -1,80 +1,179 @@
package cc.winboll.studio.powerbell.views;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 12:55
* @Describe 电池电量Drawable适配API30兼容小米机型支持能量/条纹两种绘制风格切换
*/
public class BatteryDrawable extends Drawable {
public static final String TAG = BatteryDrawable.class.getSimpleName();
// ====================== 静态常量(置顶,按重要性排序) ======================
public static final String TAG = "BatteryDrawable";
// 小米机型绘制偏移校准适配MIUI渲染特性避免绘制错位
private static final int MIUI_DRAW_OFFSET = 1;
// 默认电量透明度兼顾显示效果与API30渲染性能
private static final int DEFAULT_BATTERY_ALPHA = 210;
// 电量颜色画笔
final Paint mPaint;
// 电量值
int mnValue = -1;
// ====================== 核心成员变量按功能归类final优先 ======================
// 绘制画笔final修饰避免重复创建提升性能
private final Paint mBatteryPaint;
// 业务控制变量
private int mBatteryValue = -1; // 当前电量0-100-1=未初始化)
private boolean mIsEnergyStyle = true; // 绘制风格true=能量false=条纹)
// @int color 电量颜色
//
public BatteryDrawable(int color) {
mPaint = new Paint();
mPaint.setColor(color);
mPaint.setAlpha(210);
// ====================== 构造方法(重载适配,优先暴露常用构造) ======================
/**
* 构造方法(默认能量风格,常用场景)
* @param batteryColor 电量显示颜色
*/
public BatteryDrawable(int batteryColor) {
LogUtils.d(TAG, "constructor: 初始化(能量风格),颜色=" + Integer.toHexString(batteryColor));
mBatteryPaint = new Paint();
initPaintConfig(batteryColor);
}
// 设置电量值
//
public void setValue(int value) {
mnValue = value;
/**
* 构造方法(支持指定绘制风格,扩展场景)
* @param batteryColor 电量显示颜色
* @param isEnergyStyle 是否启用能量风格
*/
public BatteryDrawable(int batteryColor, boolean isEnergyStyle) {
LogUtils.d(TAG, "constructor: 初始化,颜色=" + Integer.toHexString(batteryColor) + ",风格=" + (isEnergyStyle ? "能量" : "条纹"));
mBatteryPaint = new Paint();
mIsEnergyStyle = isEnergyStyle;
initPaintConfig(batteryColor);
}
// ====================== 私有初始化方法(封装复用,隐藏内部逻辑) ======================
/**
* 初始化画笔配置适配API30渲染特性优化小米机型兼容性
*/
private void initPaintConfig(int color) {
mBatteryPaint.setColor(color);
mBatteryPaint.setAlpha(DEFAULT_BATTERY_ALPHA);
mBatteryPaint.setAntiAlias(true); // 抗锯齿,解决小米低分辨率锯齿问题
mBatteryPaint.setStyle(Paint.Style.FILL); // 固定填充模式,避免混乱
mBatteryPaint.setDither(false); // 禁用抖动提升API30颜色显示一致性
LogUtils.d(TAG, "initPaintConfig: 画笔配置完成");
}
// ====================== 核心绘制方法Drawable抽象方法优先级最高 ======================
@Override
public void draw(Canvas canvas) {
int nWidth = getBounds().width();
int nHeight = getBounds().height();
int mnDx = nHeight / 203;
// 未初始化/异常电量,直接跳过,避免无效绘制
if (mBatteryValue < 0) {
LogUtils.w(TAG, "draw: 电量未初始化,跳过绘制");
return;
}
// 强制校准电量范围0-100防止异常值导致绘制错误
int validBattery = Math.max(0, Math.min(mBatteryValue, 100));
Rect drawBounds = getBounds();
int drawHeight = drawBounds.height();
// 绘制耗电电量提醒值电量
// 能量绘图风格
int nTop;
int nLeft = 0;
int nBottom;
int nRight = nWidth;
// 小米机型绘制偏移校准解决MIUI系统渲染偏移问题
int offset = MIUI_DRAW_OFFSET;
int left = drawBounds.left + offset;
int right = drawBounds.right - offset;
//for (int i = 0; i < mnValue; i ++) {
nBottom = nHeight;
nTop = nHeight - (nHeight * mnValue / 100);
canvas.drawRect(new Rect(nLeft, nTop, nRight, nBottom), mPaint);
// 绘制耗电电量提醒值电量
// 意兴阑珊绘图风格
/*int nTop;
int nLeft = 0;
int nBottom;
int nRight = nWidth;
// 按风格执行绘制(精简日志,仅保留核心绘制参数)
LogUtils.d(TAG, "draw: 开始绘制,电量=" + validBattery + ",风格=" + (mIsEnergyStyle ? "能量" : "条纹"));
if (mIsEnergyStyle) {
drawEnergyStyle(canvas, validBattery, left, right, drawHeight);
} else {
drawStripeStyle(canvas, validBattery, left, right, drawHeight);
}
}
for (int i = 0; i < mnValue; i ++) {
nBottom = (nHeight * (100-i)/100) - mnDx;
nTop = nBottom + mnDx;
canvas.drawRect(new Rect(nLeft, nTop, nRight, nBottom), mPaint);
}*/
// ====================== 绘制风格实现(私有封装,按风格拆分) ======================
/**
* 能量风格绘制(整块填充,高效简洁,默认风格)
*/
private void drawEnergyStyle(Canvas canvas, int battery, int left, int right, int height) {
int top = height - (height * battery / 100); // 计算电量对应顶部坐标
canvas.drawRect(new Rect(left, top, right, height), mBatteryPaint);
LogUtils.d(TAG, "drawEnergyStyle: 绘制完成,顶部坐标=" + top);
}
/**
* 条纹风格绘制(分段条纹,扩展风格)
*/
private void drawStripeStyle(Canvas canvas, int battery, int left, int right, int height) {
int stripeHeight = height / 100; // 单条条纹高度(均匀拆分)
// 从底部向上绘制对应电量条纹
for (int i = 0; i < battery; i++) {
int bottom = height - (stripeHeight * i);
int top = bottom - stripeHeight;
canvas.drawRect(new Rect(left, top, right, bottom), mBatteryPaint);
}
LogUtils.d(TAG, "drawStripeStyle: 绘制完成,条纹数量=" + battery);
}
// ====================== 对外暴露方法(业务控制入口,按功能排序) ======================
/**
* 设置当前电量(外部核心调用入口)
* @param value 电量值0-100
*/
public void setBatteryValue(int value) {
LogUtils.d(TAG, "setBatteryValue: 电量更新,旧值=" + mBatteryValue + ",新值=" + value);
mBatteryValue = value;
invalidateSelf(); // 触发重绘确保UI实时更新
}
/**
* 切换绘制风格
* @param isEnergyStyle true=能量风格false=条纹风格
*/
public void switchDrawStyle(boolean isEnergyStyle) {
LogUtils.d(TAG, "switchDrawStyle: 风格切换,旧=" + (mIsEnergyStyle ? "能量" : "条纹") + ",新=" + (isEnergyStyle ? "能量" : "条纹"));
mIsEnergyStyle = isEnergyStyle;
invalidateSelf();
}
/**
* 更新电量显示颜色
* @param color 新颜色值
*/
public void updateBatteryColor(int color) {
LogUtils.d(TAG, "updateBatteryColor: 颜色更新,旧=" + Integer.toHexString(mBatteryPaint.getColor()) + ",新=" + Integer.toHexString(color));
mBatteryPaint.setColor(color);
invalidateSelf();
}
// ====================== Getter方法按需暴露简洁无冗余 ======================
public int getBatteryValue() {
return mBatteryValue;
}
public boolean isEnergyStyle() {
return mIsEnergyStyle;
}
// ====================== Drawable抽象方法必须实现精简逻辑 ======================
@Override
public void setAlpha(int alpha) {
LogUtils.d(TAG, "setAlpha: 透明度更新,旧=" + mBatteryPaint.getAlpha() + ",新=" + alpha);
mBatteryPaint.setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
// This method is required
}
@Override
public void setAlpha(int p1) {
LogUtils.d(TAG, "setColorFilter: 设置颜色过滤filter=" + colorFilter);
mBatteryPaint.setColorFilter(colorFilter);
invalidateSelf();
}
@Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
// 固定返回半透明适配API30透明度渲染机制兼容小米机型
return PixelFormat.TRANSLUCENT;
}
}

View File

@@ -0,0 +1,420 @@
package cc.winboll.studio.powerbell.views;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:14
* @Describe 主页面核心视图封装类:统一管理视图绑定、数据更新、事件监听,解耦 Activity 逻辑
* 适配Java7 | API30 | 小米手机,优化性能与资源回收,杜绝内存泄漏
*/
public class MainContentView {
// ======================== 静态常量(置顶,唯一标识)========================
public static final String TAG = "MainContentView";
// ======================== 核心成员变量(按「依赖→视图→内部资源」排序)========================
// 外部依赖实例(生命周期关联,优先声明)
private Context mContext;
private AppConfigUtils mAppConfigUtils;
private OnViewActionListener mActionListener;
// 视图控件(按「布局→开关→文本→进度条→图标」功能归类)
// 基础布局控件
public RelativeLayout mainLayout;
public BackgroundView backgroundView;
// 容器布局控件
public LinearLayout llLeftSeekBar;
public LinearLayout llRightSeekBar;
// 开关控件
public CheckBox cbEnableChargeReminder;
public CheckBox cbEnableUsageReminder;
public Switch swEnableService;
// 文本显示控件
public TextView tvTips;
public TextView tvChargeReminderValue;
public TextView tvUsageReminderValue;
public TextView tvCurrentBatteryValue;
// 进度条控件
public VerticalSeekBar sbChargeReminder;
public VerticalSeekBar sbUsageReminder;
// 图标显示控件
public ImageView ivCurrentBattery;
public ImageView ivChargeReminderBattery;
public ImageView ivUsageReminderBattery;
// 内部复用资源(避免重复创建,优化性能)
private BatteryDrawable mCurrentBatteryDrawable;
private BatteryDrawable mChargeReminderBatteryDrawable;
private BatteryDrawable mUsageReminderBatteryDrawable;
// ======================== 构造方法(初始化入口,逻辑闭环)========================
public MainContentView(Context context, View rootView, OnViewActionListener actionListener) {
LogUtils.d(TAG, "constructor: 开始初始化");
// 初始化外部依赖
this.mContext = context;
this.mActionListener = actionListener;
this.mAppConfigUtils = AppConfigUtils.getInstance(context.getApplicationContext());
LogUtils.d(TAG, "constructor: 外部依赖初始化完成");
// 执行核心初始化流程(按顺序执行,避免依赖空指针)
bindViews(rootView);
initBatteryDrawables();
bindViewListeners();
LogUtils.d(TAG, "constructor: 整体初始化完成");
}
// ======================== 私有初始化方法(封装内部逻辑,仅暴露入口)========================
/**
* 绑定视图控件(显式强转适配 Java7适配 API30 视图加载机制)
*/
private void bindViews(View rootView) {
LogUtils.d(TAG, "bindViews: 开始绑定视图");
// 基础布局绑定
mainLayout = (RelativeLayout) rootView.findViewById(R.id.activitymainRelativeLayout1);
backgroundView = (BackgroundView) rootView.findViewById(R.id.fragmentmainviewBackgroundView1);
// 容器布局绑定
llLeftSeekBar = (LinearLayout) rootView.findViewById(R.id.fragmentmainviewLinearLayout1);
llRightSeekBar = (LinearLayout) rootView.findViewById(R.id.fragmentmainviewLinearLayout2);
// 开关控件绑定
cbEnableChargeReminder = (CheckBox) rootView.findViewById(R.id.fragmentmainviewCheckBox1);
cbEnableUsageReminder = (CheckBox) rootView.findViewById(R.id.fragmentmainviewCheckBox2);
swEnableService = (Switch) rootView.findViewById(R.id.fragmentandroidviewSwitch1);
// 文本控件绑定
tvTips = (TextView) rootView.findViewById(R.id.fragmentandroidviewTextView1);
tvChargeReminderValue = (TextView) rootView.findViewById(R.id.fragmentandroidviewTextView2);
tvUsageReminderValue = (TextView) rootView.findViewById(R.id.fragmentandroidviewTextView3);
tvCurrentBatteryValue = (TextView) rootView.findViewById(R.id.fragmentandroidviewTextView4);
// 进度条控件绑定
sbChargeReminder = (VerticalSeekBar) rootView.findViewById(R.id.fragmentandroidviewVerticalSeekBar1);
sbUsageReminder = (VerticalSeekBar) rootView.findViewById(R.id.fragmentandroidviewVerticalSeekBar2);
// 图标控件绑定
ivCurrentBattery = (ImageView) rootView.findViewById(R.id.fragmentandroidviewImageView1);
ivChargeReminderBattery = (ImageView) rootView.findViewById(R.id.fragmentandroidviewImageView3);
ivUsageReminderBattery = (ImageView) rootView.findViewById(R.id.fragmentandroidviewImageView2);
// 关键视图绑定校验(仅保留核心控件错误日志,精简冗余)
if (mainLayout == null) LogUtils.e(TAG, "bindViews: mainLayout 绑定失败");
if (backgroundView == null) LogUtils.e(TAG, "bindViews: backgroundView 绑定失败");
LogUtils.d(TAG, "bindViews: 视图绑定完成");
}
/**
* 初始化电池 Drawable集成 BatteryDrawable默认能量风格适配小米机型渲染
*/
private void initBatteryDrawables() {
LogUtils.d(TAG, "initBatteryDrawables: 开始初始化电池 Drawable");
// 当前电量 Drawable颜色从资源读取适配 API30 主题)
int colorCurrent = getResourceColor(R.color.colorCurrent);
mCurrentBatteryDrawable = new BatteryDrawable(colorCurrent);
// 充电提醒 Drawable
int colorCharge = getResourceColor(R.color.colorCharge);
mChargeReminderBatteryDrawable = new BatteryDrawable(colorCharge);
// 耗电提醒 Drawable
int colorUsage = getResourceColor(R.color.colorUsege);
mUsageReminderBatteryDrawable = new BatteryDrawable(colorUsage);
LogUtils.d(TAG, "initBatteryDrawables: 电池 Drawable 初始化完成");
}
/**
* 绑定视图事件监听Java7 显式实现接口,避免 Lambda适配 API30 事件分发)
*/
private void bindViewListeners() {
LogUtils.d(TAG, "bindViewListeners: 开始绑定事件监听");
// 依赖校验,避免空指针
if (mAppConfigUtils == null || mActionListener == null) {
LogUtils.e(TAG, "bindViewListeners: 依赖实例为空,跳过监听绑定");
return;
}
// 充电提醒进度条监听
if (sbChargeReminder != null) {
sbChargeReminder.setOnSeekBarChangeListener(new ChargeReminderSeekBarListener());
LogUtils.d(TAG, "bindViewListeners: 充电提醒进度条监听绑定完成");
}
// 充电提醒开关监听
if (cbEnableChargeReminder != null) {
cbEnableChargeReminder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChecked = cbEnableChargeReminder.isChecked();
mAppConfigUtils.setChargeReminderEnabled((Activity) mContext, isChecked);
mActionListener.onChargeReminderSwitchChanged(isChecked);
LogUtils.d(TAG, "cbEnableChargeReminder: 状态切换为 " + isChecked);
}
});
LogUtils.d(TAG, "bindViewListeners: 充电提醒开关监听绑定完成");
}
// 耗电提醒进度条监听
if (sbUsageReminder != null) {
sbUsageReminder.setOnSeekBarChangeListener(new UsageReminderSeekBarListener());
LogUtils.d(TAG, "bindViewListeners: 耗电提醒进度条监听绑定完成");
}
// 耗电提醒开关监听
if (cbEnableUsageReminder != null) {
cbEnableUsageReminder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChecked = cbEnableUsageReminder.isChecked();
mAppConfigUtils.setUsageReminderEnabled((Activity) mContext, isChecked);
mActionListener.onUsageReminderSwitchChanged(isChecked);
LogUtils.d(TAG, "cbEnableUsageReminder: 状态切换为 " + isChecked);
}
});
LogUtils.d(TAG, "bindViewListeners: 耗电提醒开关监听绑定完成");
}
// 服务总开关监听
if (swEnableService != null) {
swEnableService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChecked = ((Switch) v).isChecked();
mAppConfigUtils.setServiceEnabled((Activity) mContext, isChecked);
mActionListener.onServiceSwitchChanged(isChecked);
LogUtils.d(TAG, "swEnableService: 状态切换为 " + isChecked);
}
});
LogUtils.d(TAG, "bindViewListeners: 服务总开关监听绑定完成");
}
LogUtils.d(TAG, "bindViewListeners: 所有事件监听绑定完成");
}
// ======================== 对外暴露核心方法(业务入口,精简参数,明确职责)========================
/**
* 更新所有视图数据(从配置读取数据,统一刷新 UI适配 API30 视图更新规范)
* @param frameDrawable 进度条背景 Drawable外部传入适配主题切换
*/
public void updateViewData(Drawable frameDrawable) {
LogUtils.d(TAG, "updateViewData: 开始更新视图数据");
if (mAppConfigUtils == null) {
LogUtils.e(TAG, "updateViewData: AppConfigUtils 为空,跳过更新");
return;
}
// 一次读取所有配置参数,减少工具类调用,提升性能
int chargeVal = mAppConfigUtils.getChargeReminderValue();
int usageVal = mAppConfigUtils.getUsageReminderValue();
int currentVal = mAppConfigUtils.getCurrentBatteryValue();
boolean chargeEnable = mAppConfigUtils.isChargeReminderEnabled();
boolean usageEnable = mAppConfigUtils.isUsageReminderEnabled();
boolean serviceEnable = mAppConfigUtils.isServiceEnabled();
LogUtils.d(TAG, "updateViewData: 配置数据读取完成charge=" + chargeVal + ", usage=" + usageVal + ", current=" + currentVal);
// 进度条背景更新
if (frameDrawable != null) {
if (llLeftSeekBar != null) llLeftSeekBar.setBackground(frameDrawable);
if (llRightSeekBar != null) llRightSeekBar.setBackground(frameDrawable);
LogUtils.d(TAG, "updateViewData: 进度条背景更新完成");
}
// 当前电量更新(联动 BatteryDrawable实时刷新图标
if (ivCurrentBattery != null && mCurrentBatteryDrawable != null) {
mCurrentBatteryDrawable.setBatteryValue(currentVal);
ivCurrentBattery.setImageDrawable(mCurrentBatteryDrawable);
}
if (tvCurrentBatteryValue != null) {
tvCurrentBatteryValue.setTextColor(getResourceColor(R.color.colorCurrent));
tvCurrentBatteryValue.setText(currentVal + "%");
}
LogUtils.d(TAG, "updateViewData: 当前电量视图更新完成");
// 充电提醒视图更新
if (ivChargeReminderBattery != null && mChargeReminderBatteryDrawable != null) {
mChargeReminderBatteryDrawable.setBatteryValue(chargeVal);
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
}
if (tvChargeReminderValue != null) {
tvChargeReminderValue.setTextColor(getResourceColor(R.color.colorCharge));
tvChargeReminderValue.setText(chargeVal + "%");
}
if (sbChargeReminder != null) sbChargeReminder.setProgress(chargeVal);
if (cbEnableChargeReminder != null) cbEnableChargeReminder.setChecked(chargeEnable);
LogUtils.d(TAG, "updateViewData: 充电提醒视图更新完成");
// 耗电提醒视图更新
if (ivUsageReminderBattery != null && mUsageReminderBatteryDrawable != null) {
mUsageReminderBatteryDrawable.setBatteryValue(usageVal);
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
}
if (tvUsageReminderValue != null) {
tvUsageReminderValue.setTextColor(getResourceColor(R.color.colorUsege));
tvUsageReminderValue.setText(usageVal + "%");
}
if (sbUsageReminder != null) sbUsageReminder.setProgress(usageVal);
if (cbEnableUsageReminder != null) cbEnableUsageReminder.setChecked(usageEnable);
LogUtils.d(TAG, "updateViewData: 耗电提醒视图更新完成");
// 服务开关+提示文本更新
if (swEnableService != null) {
swEnableService.setChecked(serviceEnable);
swEnableService.setText(mContext.getString(R.string.txt_aboveswitch));
}
if (tvTips != null) tvTips.setText(mContext.getString(R.string.txt_aboveswitchtips));
LogUtils.d(TAG, "updateViewData: 服务开关及提示文本更新完成");
LogUtils.d(TAG, "updateViewData: 所有视图数据更新完成");
}
/**
* 实时更新当前电量(单独抽离,适配电池实时监控场景,优化 API30 UI 响应速度)
* @param value 电量值(自动校准 0-100避免异常值
*/
public void updateCurrentBattery(int value) {
LogUtils.d(TAG, "updateCurrentBattery: 开始更新,原始值=" + value);
// 核心依赖校验
if (tvCurrentBatteryValue == null || mCurrentBatteryDrawable == null || ivCurrentBattery == null) {
LogUtils.e(TAG, "updateCurrentBattery: 视图/Drawable 为空,跳过更新");
return;
}
// 校准电量范围(强制 0-100防止 API30 视图显示异常)
int validValue = Math.max(0, Math.min(value, 100));
// 联动 BatteryDrawable 更新图标,同步文本显示
mCurrentBatteryDrawable.setBatteryValue(validValue);
ivCurrentBattery.setImageDrawable(mCurrentBatteryDrawable);
tvCurrentBatteryValue.setText(validValue + "%");
LogUtils.d(TAG, "updateCurrentBattery: 更新完成,校准后值=" + validValue);
}
/**
* 释放资源(主动回收,适配 API30 资源管控机制,优化小米手机内存占用)
*/
public void releaseResources() {
LogUtils.d(TAG, "releaseResources: 开始释放资源");
// 释放 BatteryDrawable 资源(重点回收绘制资源,避免 OOM
mCurrentBatteryDrawable = null;
mChargeReminderBatteryDrawable = null;
mUsageReminderBatteryDrawable = null;
// 置空视图实例(断开视图引用,辅助 GC 回收)
mainLayout = null;
backgroundView = null;
llLeftSeekBar = null;
llRightSeekBar = null;
cbEnableChargeReminder = null;
cbEnableUsageReminder = null;
swEnableService = null;
tvTips = null;
tvChargeReminderValue = null;
tvUsageReminderValue = null;
tvCurrentBatteryValue = null;
sbChargeReminder = null;
sbUsageReminder = null;
ivCurrentBattery = null;
ivChargeReminderBattery = null;
ivUsageReminderBattery = null;
// 置空外部依赖(断开生命周期关联,杜绝内存泄漏)
mContext = null;
mAppConfigUtils = null;
mActionListener = null;
LogUtils.d(TAG, "releaseResources: 所有资源释放完成");
}
// ======================== 内部工具方法(封装重复逻辑,提升复用性)========================
/**
* 获取资源颜色(适配 API30 主题颜色读取机制,兼容低版本,优化小米机型颜色显示)
* @param colorResId 颜色资源 ID
* @return 校准后的颜色值
*/
private int getResourceColor(int colorResId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// API23+ 支持主题颜色,适配 API30 主题机制
return mContext.getResources().getColor(colorResId, mContext.getTheme());
} else {
// 低版本兼容
return mContext.getResources().getColor(colorResId);
}
}
// ======================== 内部事件监听类(私有封装,职责单一,避免外部依赖)========================
/**
* 充电提醒进度条监听(仅处理充电提醒进度相关逻辑)
*/
private class ChargeReminderSeekBarListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 实时更新视图(联动 BatteryDrawable适配 API30 实时渲染)
if (tvChargeReminderValue != null && mChargeReminderBatteryDrawable != null && ivChargeReminderBattery != null) {
mChargeReminderBatteryDrawable.setBatteryValue(progress);
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
tvChargeReminderValue.setText(progress + "%");
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 触摸开始,无额外逻辑,留空保持接口完整
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 触摸结束,保存配置并回调
if (mAppConfigUtils == null || mActionListener == null) return;
int progress = seekBar.getProgress();
mAppConfigUtils.setChargeReminderValue((Activity) mContext, progress);
mActionListener.onChargeReminderProgressChanged(progress);
LogUtils.d(TAG, "ChargeReminderSeekBar: 进度确认,保存值=" + progress);
}
}
/**
* 耗电提醒进度条监听(仅处理耗电提醒进度相关逻辑)
*/
private class UsageReminderSeekBarListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 实时更新视图(联动 BatteryDrawable适配 API30 实时渲染)
if (tvUsageReminderValue != null && mUsageReminderBatteryDrawable != null && ivUsageReminderBattery != null) {
mUsageReminderBatteryDrawable.setBatteryValue(progress);
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
tvUsageReminderValue.setText(progress + "%");
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 触摸开始,无额外逻辑,留空保持接口完整
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 触摸结束,保存配置并回调
if (mAppConfigUtils == null || mActionListener == null) return;
int progress = seekBar.getProgress();
mAppConfigUtils.setUsageReminderValue((Activity) mContext, progress);
mActionListener.onUsageReminderProgressChanged(progress);
LogUtils.d(TAG, "UsageReminderSeekBar: 进度确认,保存值=" + progress);
}
}
// ======================== 事件回调接口(解耦视图与业务,提升扩展性)========================
public interface OnViewActionListener {
void onChargeReminderSwitchChanged(boolean isChecked);
void onUsageReminderSwitchChanged(boolean isChecked);
void onServiceSwitchChanged(boolean isChecked);
void onChargeReminderProgressChanged(int progress);
void onUsageReminderProgressChanged(int progress);
}
}