源码整理
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, "通知工具类资源释放完成");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user