Compare commits

...

55 Commits

Author SHA1 Message Date
bf051dcc9f <powerbell>APK 15.14.13 release Publish. 2025-12-21 09:22:26 +08:00
b38a8df462 添加应用内存整理后重新缓存位图的功能。 2025-12-21 09:20:15 +08:00
a8dbe43d4b 源码整理 2025-12-21 02:05:38 +08:00
05a8dc5205 <powerbell>APK 15.14.12 release Publish. 2025-12-20 22:59:40 +08:00
280fc1abd6 <powerbell>Start New Stage Version. 2025-12-20 22:58:26 +08:00
fe2084f5ff 优化RemindThread线程,节约电量。 2025-12-20 22:57:42 +08:00
2a75aa140e 修改背景控件默认背景颜色。 2025-12-20 21:23:52 +08:00
04b597cbe7 <powerbell>APK 15.14.11 release Publish. 2025-12-20 19:28:00 +08:00
35d210c378 添加滚动条拉动时,实时预览数值的功能。 2025-12-20 19:26:32 +08:00
79ae6de6fc <powerbell>APK 15.14.10 release Publish. 2025-12-20 18:45:13 +08:00
328f559d2e 改进背景图片位图缓存方法。 2025-12-20 18:40:29 +08:00
28d340a772 电量提醒线程测试完成。 2025-12-20 17:40:06 +08:00
65acbfcd04 调整电量消息频率以及消息内容。 2025-12-20 16:30:34 +08:00
6af2096b30 修复广播消息发送方法 2025-12-20 15:55:40 +08:00
e539922478 广播消息分开为,电量消息与应用配置更新消息。 2025-12-20 15:44:12 +08:00
1d9a03554c 添加广播接收器的注册与释放。 2025-12-20 13:49:02 +08:00
23beabe99b 源码整理 2025-12-20 13:23:07 +08:00
9c1e9dc75b 函数简化重构。 2025-12-20 03:07:08 +08:00
76c1dee625 MainActivity本地消息发送整理中。。。 2025-12-19 21:05:42 +08:00
e967ce5511 修复服务启动逻辑 2025-12-19 20:42:40 +08:00
bea77409a5 重置通知架构为旧式方法,用线程启动法。过程顺便整理代码。 2025-12-19 20:31:43 +08:00
e584e824c0 恢复2a74fd2c304b571ab5ae349ffc3b7f06c5b4daf7提交点,旧版电量计算方法。 2025-12-19 20:11:10 +08:00
bc6a82af41 电量提醒消息,通路架构基本完成。 2025-12-19 18:48:47 +08:00
9bc71bb3f6 正在修复电量提醒线程。。。 2025-12-19 00:13:03 +08:00
b0dee5e98e 服务线程调试中,目标就是线程放入服务类使用handler与服务通信。 2025-12-18 21:15:24 +08:00
7d796b5c3f 更新基础类库,更新调试工具。 2025-12-18 17:52:47 +08:00
d43ba4bff2 剔除服务锁,正在调试RemindThread调用。。。 2025-12-18 15:20:32 +08:00
796d826331 源码整理。 2025-12-18 13:14:04 +08:00
b3f4571b57 移除提醒线程前台标志 2025-12-18 13:11:00 +08:00
5e6de91430 服务启停调试中。。。 2025-12-17 20:40:04 +08:00
d61d1da5d1 服务自启动修复中。。。 2025-12-17 16:50:38 +08:00
5cf47172f4 服务自启配置持久化 2025-12-17 16:24:49 +08:00
683dc6791e 修复应用初始安装时电量提醒无声音的问题。 2025-12-17 15:17:45 +08:00
1e9a6adc88 修复电量进度条拉动时无响应问题 2025-12-17 14:50:21 +08:00
dbcb5259d9 源码整理 2025-12-17 14:14:07 +08:00
740ab932a4 源码整理 2025-12-17 14:01:57 +08:00
29b9f3c82b 源码整理 2025-12-17 13:50:33 +08:00
47601ef542 编译参数修复 2025-12-17 12:04:08 +08:00
7b17fae798 豆包完美想象版,未调试。 2025-12-17 07:48:32 +08:00
405b914f02 20251217_043730_107 2025-12-17 04:37:34 +08:00
1c7ceebb78 添加软著申请文档生成脚本 2025-12-17 04:03:43 +08:00
493b7e433c <powerbell>APK 15.14.9 release Publish. 2025-12-16 21:18:25 +08:00
d2ddfedc96 调整当前电量初始化时的默认值。处理bean类序列化问题,未调试。 2025-12-16 21:16:34 +08:00
bba48a4458 <powerbell>APK 15.14.8 release Publish. 2025-12-16 20:17:03 +08:00
b7ae6ce190 编译参数修复 2025-12-16 20:15:58 +08:00
ba5470ebcb 更新像素清空按钮的重置颜色值。重置后为黑色,透明度OriginalAlpha值为FF。 2025-12-16 20:14:23 +08:00
78c7763212 优化颜色拾取流程 2025-12-16 20:06:12 +08:00
d051b1f737 <powerbell>APK 15.14.7 release Publish. 2025-12-16 17:52:54 +08:00
d6323bc1ed <powerbell>Start New Stage Version. 2025-12-16 17:50:40 +08:00
dffcc0f8a0 渐变像素对话框调试完成。 2025-12-16 17:48:59 +08:00
9426618b59 20251216_171108_907 2025-12-16 17:11:20 +08:00
68d98d4be3 调色板基本调试完成 2025-12-16 16:24:09 +08:00
4db458dda8 20251216_154557_434 2025-12-16 15:46:01 +08:00
83a8f5dada 初步完成调色板调用流程调试。 2025-12-16 14:12:41 +08:00
8e1d6ba197 添加调色板对话框,未调试。 2025-12-16 12:11:32 +08:00
47 changed files with 5723 additions and 1893 deletions

View File

@@ -88,7 +88,7 @@ dependencies {
// WinBoLL备用库 jitpack.io 地址
api 'com.github.ZhanGSKen:AES:aes-v15.12.3'
api 'com.github.ZhanGSKen:APPBase:appbase-v15.12.2'
api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
//api fileTree(dir: 'libs', include: ['*.aar'])
api fileTree(dir: 'libs', include: ['*.jar'])

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Dec 14 19:58:14 HKT 2025
stageCount=7
#Sun Dec 21 09:22:26 HKT 2025
stageCount=14
libraryProject=
baseVersion=15.14
publishVersion=15.14.6
publishVersion=15.14.13
buildCount=0
baseBetaVersion=15.14.7
baseBetaVersion=15.14.14

View File

@@ -0,0 +1,279 @@
#!/bin/bash
# PowerBell软著版本号快速修改+生成脚本
# 无需手动改主脚本,输入版本号直接运行
# 颜色输出函数
red_echo() { echo -e "\033[31m$1\033[0m"; }
green_echo() { echo -e "\033[32m$1\033[0m"; }
blue_echo() { echo -e "\033[34m$1\033[0m"; }
# 1. 提示用户输入新版本号
blue_echo "==== 请输入软著版本号格式示例V15、V15.0.1 ===="
read -p "输入版本号:" NEW_VERSION
# 校验版本号格式(避免特殊符号)
if [[ ! $NEW_VERSION =~ ^V[0-9]+(\.[0-9]+)*$ ]]; then
red_echo "错误版本号格式无效请遵循「V+数字」格式如V15、V15.0.1),不含特殊符号"
exit 1
fi
# 2. 定义固定配置(仅需修改这里的著作权人,其他无需动)
SOFTWARE_NAME="PowerBell"
COPYRIGHT_OWNER="张绍建陆丰东海镇云宝软件开发工作室"
LINES_PER_PAGE=55
# 3. 生成主脚本(自动替换新版本号)
blue_echo -e "\n==== 生成${NEW_VERSION}版本主脚本 ===="
cat > build_copyright_pdf_temp.sh << EOF
#!/bin/bash
# PowerBell软著PDF生成脚本版本$NEW_VERSION
red_echo() { echo -e "\033[31m\$1\033[0m"; }
green_echo() { echo -e "\033[32m\$1\033[0m"; }
blue_echo() { echo -e "\033[34m\$1\033[0m"; }
# 配置项(已自动替换为${NEW_VERSION}
SOFTWARE_NAME="$SOFTWARE_NAME"
SOFTWARE_VERSION="$NEW_VERSION"
COPYRIGHT_OWNER="$COPYRIGHT_OWNER"
LINES_PER_PAGE=$LINES_PER_PAGE
# 步骤1检查依赖
blue_echo "==== 1/7 检查并安装依赖 ===="
sudo apt update > /dev/null 2>&1
REQUIRED_PKGS=("python3" "wkhtmltopdf" "fonts-wqy-microhei" "pdftk" "poppler-utils")
for pkg in "\${REQUIRED_PKGS[@]}"; do
if ! dpkg -s "\$pkg" > /dev/null 2>&1; then
green_echo "安装依赖:\$pkg"
sudo apt install -y "\$pkg" > /dev/null 2>&1
fi
done
# 步骤2生成纯文本源码
blue_echo -e "\n==== 2/7 生成纯文本核心源码 ===="
cat > generate_source.py << GEN_EOF
import os
PROJECT_PATH = "./"
OUTPUT_TXT = "PowerBell_Core_Source.txt"
INCLUDE_EXT = [".java", ".kt"]
EXCLUDE_DIRS = ["build", "libs", "test", "androidTest", ".git", ".idea", "gradle", "unittest"]
MIN_LINE_COUNT = 3
SOFTWARE_NAME = "$SOFTWARE_NAME"
SOFTWARE_VERSION = "$NEW_VERSION"
COPYRIGHT_OWNER = "$COPYRIGHT_OWNER"
def clean_text(text):
return ''.join(c for c in text if c.isprintable() or c in "\\n\\r\\t")
def generate_source_txt():
valid_files = []
main_dir = os.path.join(PROJECT_PATH, "src", "main")
if not os.path.exists(main_dir):
print("Error: src/main directory not found!")
return
for root, dirs, files in os.walk(main_dir):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
for file in files:
if os.path.splitext(file)[1] in INCLUDE_EXT:
file_path = os.path.join(root, file)
try:
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
lines = f.readlines()
code_lines = [l for l in lines if l.strip() and not l.strip().startswith("//")]
if len(code_lines) >= MIN_LINE_COUNT:
valid_files.append(file_path)
except:
continue
valid_files.sort(key=lambda x: os.path.getsize(x), reverse=True)
with open(OUTPUT_TXT, "w", encoding="utf-8-sig") as f:
f.write(f"\{SOFTWARE_NAME} \{SOFTWARE_VERSION} 核心源码 - 著作权人:\{COPYRIGHT_OWNER}\\n\\n")
for idx, file_path in enumerate(valid_files, 1):
f.write(f"\\n{'='*60}\\n")
f.write(f"文件 \{idx}\{file_path.replace(PROJECT_PATH, '')}\\n")
f.write(f"{'='*60}\\n\\n")
try:
try:
with open(file_path, "r", encoding="utf-8") as src_f:
content = clean_text(src_f.read())
except UnicodeDecodeError:
with open(file_path, "r", encoding="gbk") as src_f:
content = clean_text(src_f.read())
f.write(content)
f.write("\\n\\n")
except Exception as e:
f.write(f"文件读取失败:\{str(e)}\\n\\n")
continue
print(f"有效源码文件数:\{len(valid_files)}")
print(f"纯文本文件路径:\{os.path.abspath(OUTPUT_TXT)}")
if __name__ == "__main__":
generate_source_txt()
GEN_EOF
python3 generate_source.py
if [ ! -f "PowerBell_Core_Source.txt" ]; then
red_echo "纯文本源码生成失败!"
exit 1
fi
# 步骤3生成带版本号页眉的HTML
blue_echo -e "\n==== 3/7 生成带${NEW_VERSION}页眉的HTML ===="
cat > txt2html.py << TXT_EOF
import os
TXT_FILE = "PowerBell_Core_Source.txt"
HTML_FILE = "PowerBell_Source.html"
SOFTWARE_NAME = "$SOFTWARE_NAME"
SOFTWARE_VERSION = "$NEW_VERSION"
COPYRIGHT_OWNER = "$COPYRIGHT_OWNER"
LINES_PER_PAGE = $LINES_PER_PAGE
CSS_STYLE = """
<style>
@page {{
size: A4;
margin: 10mm 5mm;
@top-center {{
content: "{} {} - 源代码(著作权人:{}";
font-family: 'WenQuanYi Micro Hei', monospace;
font-size: 9pt;
font-weight: bold;
}}
@bottom-center {{
content: "页码 " counter(page) " / " counter(pages);
font-family: 'WenQuanYi Micro Hei', monospace;
font-size: 9pt;
}}
}}
body {{
font-family: 'WenQuanYi Micro Hei', monospace;
font-size: 9pt;
line-height: 1.1;
margin: 0;
padding: 5mm 0 0 0;
counter-reset: code-line;
}}
.file-header {{
background: #f0f0f0;
padding: 3px;
margin: 6px 0;
font-weight: bold;
font-size: 10pt;
}}
.code-block {{
white-space: pre;
margin-left: 8px;
line-height: 1.1;
counter-increment: code-line;
}}
.code-block:before {{
content: counter(code-line) " ";
color: #888;
display: inline-block;
width: 30px;
text-align: right;
margin-right: 5px;
}}
.page-break {{ page-break-after: always; counter-reset: code-line; }}
</style>
""".format(SOFTWARE_NAME, SOFTWARE_VERSION, COPYRIGHT_OWNER)
def txt_to_html():
with open(TXT_FILE, "r", encoding="utf-8") as f:
content = f.read()
html_content = "<!DOCTYPE html><html><head><meta charset='utf-8'>" + CSS_STYLE + "</head><body>"
content_lines = content.split("\\n")[2:]
content_clean = "\\n".join(content_lines)
blocks = content_clean.split("====")
line_count = 0
for block in blocks:
if not block.strip():
continue
if "文件 " in block and ":" in block:
file_header = block.split("\\n")[0].strip() if "\\n" in block else block.strip()
html_content += f"<div class='file-header'>\{file_header}</div>"
code_part = block.split("\\n")[1:] if "\\n" in block else []
block = "\\n".join(code_part)
code_lines = block.split("\\n")
for line in code_lines:
if line.strip() or line_count > 0:
line_count += 1
html_content += f"<div class='code-block'>\{line}</div>"
if line_count >= LINES_PER_PAGE:
html_content += "<div class='page-break'></div>"
line_count = 0
html_content += "</body></html>"
with open(HTML_FILE, "w", encoding="utf-8") as f:
f.write(html_content)
print(f"HTML文件路径\{os.path.abspath(HTML_FILE)}")
if __name__ == "__main__":
txt_to_html()
TXT_EOF
python3 txt2html.py
if [ ! -f "PowerBell_Source.html" ]; then
red_echo "HTML文件生成失败"
exit 1
fi
# 步骤4生成完整PDF
blue_echo -e "\n==== 4/7 生成完整PDF版本${NEW_VERSION} ===="
wkhtmltopdf --page-size A4 \
--margin-top 15mm --margin-bottom 15mm --margin-left 5mm --margin-right 5mm \
--encoding utf-8 \
--no-images --disable-javascript \
--enable-local-file-access \
--no-stop-slow-scripts \
PowerBell_Source.html PowerBell_soft_full.pdf
if [ ! -f "PowerBell_soft_full.pdf" ]; then
red_echo "完整PDF生成失败"
exit 1
fi
# 步骤5截取60页
blue_echo -e "\n==== 5/7 截取前30+后30页 ===="
TOTAL_PAGES=\$(pdfinfo PowerBell_soft_full.pdf | grep "Pages" | awk '{print \$2}')
green_echo "源码完整PDF总页数\$TOTAL_PAGES 页"
if [ "\$TOTAL_PAGES" -le 60 ]; then
cp PowerBell_soft_full.pdf PowerBell_软著源码_${NEW_VERSION}_60页.pdf
green_echo "源码不足60页直接使用完整PDF"
else
pdftk PowerBell_soft_full.pdf cat 1-30 output PowerBell_前30页.pdf
START_PAGE=\$((TOTAL_PAGES - 29))
pdftk PowerBell_soft_full.pdf cat \$START_PAGE-\$TOTAL_PAGES output PowerBell_后30页.pdf
pdftk PowerBell_前30页.pdf PowerBell_后30页.pdf cat output PowerBell_软著源码_${NEW_VERSION}_60页.pdf
rm -f PowerBell_前30页.pdf PowerBell_后30页.pdf
green_echo "源码超过60页已截取前30页+后30页合并为60页"
fi
# 步骤6验证规范
blue_echo -e "\n==== 6/7 验证${NEW_VERSION}版本PDF规范 ===="
FINAL_PAGES=\$(pdfinfo PowerBell_软著源码_${NEW_VERSION}_60页.pdf | grep "Pages" | awk '{print \$2}')
green_echo "最终PDF页数\$FINAL_PAGES 页"
green_echo "每页代码行数:\$LINES_PER_PAGE 行≥50行"
green_echo "页眉信息:$SOFTWARE_NAME $NEW_VERSION - 源代码(著作权人:$COPYRIGHT_OWNER"
# 步骤7清理临时文件
blue_echo -e "\n==== 7/7 清理临时文件 ===="
rm -f generate_source.py txt2html.py PowerBell_Core_Source.txt PowerBell_Source.html PowerBell_soft_full.pdf
green_echo "临时文件清理完成!"
# 输出结果
green_echo -e "\n====================================="
green_echo "$SOFTWARE_NAME $NEW_VERSION 软著PDF生成成功🎉"
green_echo "📄 最终文件:\$(pwd)/PowerBell_软著源码_${NEW_VERSION}_60页.pdf"
green_echo "💡 可直接提交软著登记,无需手动修改!"
green_echo "====================================="
EOF
# 4. 赋予执行权限并运行
chmod +x build_copyright_pdf_temp.sh
blue_echo -e "\n==== 开始生成${NEW_VERSION}版本PDF ===="
./build_copyright_pdf_temp.sh
# 5. 删除临时主脚本(可选,保留则注释此行)
rm -f build_copyright_pdf_temp.sh
green_echo -e "\n==== 操作完成!${NEW_VERSION}版本PDF已生成 ===="

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

View File

@@ -1,5 +1,6 @@
package cc.winboll.studio.powerbell;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
@@ -9,11 +10,13 @@ import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.models.NotificationMessage;
import cc.winboll.studio.powerbell.receivers.GlobalApplicationReceiver;
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.BitmapCacheUtils;
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
import java.io.File;
public class App extends GlobalApplication {
@@ -26,14 +29,20 @@ public class App extends GlobalApplication {
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1";
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2";
// 内存紧张通知常量
private static final String TRIM_MEMORY_NOTIFY_TITLE = "应用使用时内存紧张提醒";
private static final String TRIM_MEMORY_NOTIFY_CONTENT = "由于本应用使用时,系统通知内存紧张程度级别较高,图片缓存功能暂时不启用。";
// 数据配置存储工具
static AppConfigUtils _mAppConfigUtils;
static AppCacheUtils _mAppCacheUtils;
// 新增:全局 Bitmap 缓存工具(常驻内存)
// 全局 Bitmap 缓存工具(常驻内存)
public static BitmapCacheUtils _mBitmapCacheUtils;
GlobalApplicationReceiver mReceiver;
static String szTempDir = "";
// 通知工具类实例(用于发送内存紧张通知)
private NotificationManagerUtils mNotificationManager;
public static String getTempDirPath() {
return szTempDir;
@@ -60,13 +69,17 @@ public class App extends GlobalApplication {
// 设置数据配置存储工具
_mAppConfigUtils = getAppConfigUtils(this);
_mAppCacheUtils = getAppCacheUtils(this);
// 初始化全局 Bitmap 缓存工具关键App 启动时初始化,常驻内存)
// 初始化全局 Bitmap 缓存工具
_mBitmapCacheUtils = BitmapCacheUtils.getInstance();
// 初始化通知工具类(使用整理后的 NotificationManagerUtils
mNotificationManager = new NotificationManagerUtils(this);
LogUtils.d(TAG, "onCreate: 通知工具类初始化完成");
mReceiver = new GlobalApplicationReceiver(this);
mReceiver.registerAction();
// ======================== 新增:异步预加载背景图 ========================
// 异步预加载背景图(保持原有逻辑)
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
@@ -74,23 +87,19 @@ public class App extends GlobalApplication {
@Override
public void run() {
try {
// 1. 获取背景源工具类实例
BackgroundSourceUtils bgSourceUtils = BackgroundSourceUtils.getInstance(App.this);
if (bgSourceUtils == null) {
LogUtils.e(TAG, "preloadBitmap: BackgroundSourceUtils 实例为空");
return;
}
// 2. 获取当前背景Bean
BackgroundBean bgBean = bgSourceUtils.getCurrentBackgroundBean();
if (bgBean == null || !bgBean.isUseBackgroundFile()) {
LogUtils.d(TAG, "preloadBitmap: 无有效背景文件,跳过预加载");
return;
}
// 3. 获取背景图路径(优先取压缩图路径)
String bgPath = bgBean.isUseBackgroundScaledCompressFile()
? bgBean.getBackgroundScaledCompressFilePath()
: bgBean.getBackgroundFilePath();
// 4. 预加载到全局缓存
if (_mBitmapCacheUtils != null) {
_mBitmapCacheUtils.cacheBitmap(bgPath);
LogUtils.d(TAG, "preloadBitmap: 应用启动时预加载成功 - " + bgPath);
@@ -103,8 +112,7 @@ public class App extends GlobalApplication {
}
}).start();
}
}, 1000); // 延迟1秒执行避免阻塞应用初始化
// ======================== 预加载逻辑结束 ========================
}, 1000);
}
// 保持原有方法不变
@@ -130,10 +138,91 @@ public class App extends GlobalApplication {
public void onTerminate() {
super.onTerminate();
ToastUtils.release();
// 可选App 终止时清空 Bitmap 缓存,释放内存
if (_mBitmapCacheUtils != null) {
_mBitmapCacheUtils.clearAllCache();
// 释放通知工具类资源,避免内存泄漏
if (mNotificationManager != null) {
mNotificationManager.release();
mNotificationManager = null;
LogUtils.d(TAG, "onTerminate: 通知工具类资源已释放");
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
LogUtils.d(TAG, "onTrimMemory: 内存等级变化 | level=" + getTrimMemoryLevelDesc(level));
// 仅在中等及以上内存紧张等级发送通知,避免频繁打扰
if (mNotificationManager == null) {
mNotificationManager = new NotificationManagerUtils(this);
}
if (level > ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
sendTrimMemoryNotification(level);
} else {
// 再次缓存 Bitmap 缓存工具
_mBitmapCacheUtils = BitmapCacheUtils.getInstance();
LogUtils.d(TAG, "Bitmap 缓存启用中。");
}
}
/**
* 发送内存紧张通知(完全复用 NotificationManagerUtils 的 showRemindNotification 方法)
*/
private void sendTrimMemoryNotification(int level) {
LogUtils.d(TAG, "sendTrimMemoryNotification: 准备发送内存紧张通知");
// 构建通知消息体
NotificationMessage message = new NotificationMessage();
message.setTitle(TRIM_MEMORY_NOTIFY_TITLE);
message.setContent(String.format("%s [ 缓存紧张级别描述: Level %d | %s ]",TRIM_MEMORY_NOTIFY_CONTENT, level, getTrimMemoryLevelDesc(level)));
// 使用整理后的 NotificationManagerUtils 发送通知(复用提醒渠道配置)
mNotificationManager.showConfigNotification(this, message);
LogUtils.d(TAG, "sendTrimMemoryNotification: 通知已通过 NotificationManagerUtils 发送");
}
/**
* 转换内存等级为可读描述,便于日志调试
* 排序规则:按 ComponentCallbacks2 枚举数值从高到低排列(数值越高,内存越紧张)
*/
/**
* 转换内存等级为可读描述,便于日志调试
* 排序规则:按 ComponentCallbacks2 枚举实际数值10进制从高到低排列
* 数值来源:接口中定义的 16进制注释10进制数值
*/
private String getTrimMemoryLevelDesc(int level) {
switch (level) {
// 数值 800x50应用内存完全紧张补充接口中实际存在的枚举项
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
return "TRIM_MEMORY_COMPLETE应用内存完全紧张";
// 数值 600x3c中等内存紧张
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
return "MODERATE中等内存紧张";
// 数值 400x28应用进入后台
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
return "BACKGROUND应用进入后台";
// 数值 200x14应用UI隐藏
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
return "BACKGROUND应用UI隐藏";
// 数值 150xf应用运行时关键级紧张
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
return "RUNNING_CRITICAL应用运行关键级紧张";
// 数值 100xa应用运行时低内存
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
return "RUNNING_LOW应用运行低内存";
// 数值 50x5应用运行时中等紧张
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
return "RUNNING_MODERATE应用运行中等内存紧张";
// 以下为注释备用项(接口中未提供,按你的原有注释保留)
// 数值 100内存极度紧张系统可能强制杀死应用
// case ComponentCallbacks2.TRIM_MEMORY_URGENT:
// return "URGENT内存极度紧张";
// 数值 20用户正在离开应用如按Home键
// case ComponentCallbacks2.TRIM_MEMORY_USER_LEAVING:
// return "USER_LEAVING用户正在离开应用";
// 未知等级
default:
return "UNKNOWN(" + level + ")";
}
}
}

View File

@@ -1,107 +1,78 @@
package cc.winboll.studio.powerbell;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewStub;
import android.widget.CheckBox;
import android.widget.CompoundButton;
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 androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.activitys.AboutActivity;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.DevelopUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
import cc.winboll.studio.powerbell.activities.BatteryReportActivity;
import cc.winboll.studio.powerbell.activities.ClearRecordActivity;
import cc.winboll.studio.powerbell.activities.SettingsActivity;
import cc.winboll.studio.powerbell.activities.WinBoLLActivity;
import cc.winboll.studio.powerbell.fragments.MainViewFragment;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import cc.winboll.studio.powerbell.unittest.MainUnitTestActivity;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.PermissionUtils;
import cc.winboll.studio.powerbell.utils.ServiceUtils;
import cc.winboll.studio.powerbell.views.BackgroundView;
import cc.winboll.studio.powerbell.views.BatteryDrawable;
import cc.winboll.studio.powerbell.views.VerticalSeekBar;
import cc.winboll.studio.powerbell.views.MainContentView;
/**
* 主活动Java 7 兼容 | 移除窗体缓存)
* 核心优化点:
* 1. ViewHolder 缓存控件 + ViewStub 延迟加载
* 2. 资源异步加载 + 非关键任务异步化
* 应用核心主活动
* 功能:管理电池监控、背景设置、服务启停、权限申请等核心功能
* 适配Java7 | API30 | 内存泄漏防护 | UI与服务状态实时同步
*/
public class MainActivity extends WinBoLLActivity {
public class MainActivity extends WinBoLLActivity implements MainContentView.OnViewActionListener {
// ======================== 静态常量(移除缓存相关键========================
// ======================== 静态常量(置顶统一,抽离魔法值========================
public static final String TAG = "MainActivity";
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
private static final long DELAY_LOAD_NON_CRITICAL = 500L;
public static final int MSG_RELOAD_APPCONFIG = 0;
public static final int MSG_CURRENTVALUEBATTERY = 1;
public static final int MSG_LOAD_BACKGROUND = 2;
private static final int DELAY_LOAD_NON_CRITICAL = 500;
private static final int MSG_UPDATE_SERVICE_SWITCH = 3;
// ======================== 成员属性(移除缓存标识========================
public static MainActivity _mMainActivity;
static MainViewFragment _mMainViewFragment;
static Handler _mHandler;
PermissionUtils permissionUtils = PermissionUtils.getInstance();
// ======================== 静态成员(全局共享,严格管控生命周期========================
private static MainActivity sMainActivity;
private static Handler sGlobalHandler;
private App mApplication;
// ======================== 工具类实例(单例,避免重复初始化)========================
private PermissionUtils mPermissionUtils;
private AppConfigUtils mAppConfigUtils;
private BackgroundSourceUtils mBgSourceUtils;
// ======================== 应用核心实例 =========================
private App mApplication;
private MainContentView mMainContentView;
private ControlCenterServiceBean mServiceControlBean;
// ======================== 基础视图组件 =========================
private Toolbar mToolbar;
private ViewStub mAdsViewStub;
private ADsBannerView mADsBannerView;
private ViewHolder mViewHolder;
private Drawable mFrameDrawable;
private Menu mMenu;
private Fragment mCurrentShowFragment;
private MainViewFragment mMainViewFragment;
private Drawable mDrawableFrame;
private BatteryDrawable mCurrentValueBatteryDrawable;
private BatteryDrawable mChargeReminderValueBatteryDrawable;
private BatteryDrawable mUsegeReminderValueBatteryDrawable;
// ======================== 视图缓存容器ViewHolder 不变========================
private static class ViewHolder {
BackgroundView backgroundView;
RelativeLayout mainLayout;
LinearLayout llLeftSeekBar, llRightSeekBar;
CheckBox cbIsEnableChargeReminder, cbIsEnableUsegeReminder;
Switch swIsEnableService;
TextView tvTips, tvChargeReminderValue, tvUsegeReminderValue, tvCurrentValue;
VerticalSeekBar chargeReminderSeekBar, usegeReminderSeekBar;
ImageView ivCurrentBattery, ivChargeReminderBattery, ivUsegeReminderBattery;
}
// ======================== 重写生命周期(移除缓存恢复/保存逻辑)========================
// ======================== 生命周期方法(按系统调用顺序排列========================
@Override
public Activity getActivity() {
return this;
@@ -114,57 +85,101 @@ public class MainActivity extends WinBoLLActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreate(...) - 移除窗体缓存");
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate() | savedInstanceState=" + savedInstanceState);
initGlobalHandler();
// 恢复原始初始化流程:直接加载布局+初始化
setContentView(R.layout.activity_main);
initCoreUtilsAsync();
initViewHolder();
initPermissionUtils();
initMainContentView();
initCriticalView();
initCoreUtilsAsync();
loadNonCriticalViewDelayed();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
permissionUtils.startPermissionRequest(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
}
_mMainActivity = null;
_mMainViewFragment = null;
if (_mHandler != null) {
_mHandler.removeCallbacksAndMessages(null);
}
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
LogUtils.d(TAG, "onPostCreate() | savedInstanceState=" + savedInstanceState);
mPermissionUtils.startPermissionRequest(this);
LogUtils.d(TAG, "onPostCreate: 发起权限申请");
}
@Override
protected void onResume() {
super.onResume();
// 移除缓存恢复后的刷新逻辑,直接发送加载背景消息
if (_mHandler != null) {
_mHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
LogUtils.d(TAG, "onResume()");
if (sGlobalHandler != null) {
sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
}
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
mADsBannerView.resumeADs(this);
}
}
// 其他生命周期方法onCreateOptionsMenu、onOptionsItemSelected等保持原有不变
@Override
protected void onPause() {
super.onPause();
LogUtils.d(TAG, "onPause()");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy()");
// 释放广告资源
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
mADsBannerView = null;
}
// 释放核心视图
if (mMainContentView != null) {
mMainContentView.releaseResources();
mMainContentView = null;
}
// 销毁Handler防止内存泄漏
if (sGlobalHandler != null) {
sGlobalHandler.removeCallbacksAndMessages(null);
sGlobalHandler = null;
}
// 释放Drawable
if (mFrameDrawable != null) {
mFrameDrawable.setCallback(null);
mFrameDrawable = null;
}
// 置空所有引用
sMainActivity = null;
mPermissionUtils = null;
mAppConfigUtils = null;
mBgSourceUtils = null;
mServiceControlBean = null;
mMenu = null;
mApplication = null;
mToolbar = null;
mAdsViewStub = null;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
LogUtils.d(TAG, "onActivityResult() | requestCode=" + requestCode + " | resultCode=" + resultCode + " | data=" + data);
mPermissionUtils.handlePermissionRequest(this, requestCode, resultCode, data);
if (requestCode == REQUEST_READ_MEDIA_IMAGES && sGlobalHandler != null) {
sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
}
}
// ======================== 菜单与导航方法 ========================
@Override
public boolean onCreateOptionsMenu(Menu menu) {
LogUtils.d(TAG, "onCreateOptionsMenu() | menu=" + menu);
mMenu = menu;
AESThemeUtil.inflateMenu(this, menu);
if (App.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
getMenuInflater().inflate(R.menu.toolbar_unittest, mMenu);
getMenuInflater().inflate(R.menu.toolbar_unittest, mMenu);
}
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
return true;
@@ -172,16 +187,15 @@ public class MainActivity extends WinBoLLActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int menuItemId = item.getItemId();
LogUtils.d(TAG, "onOptionsItemSelected() | itemId=" + item.getItemId());
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
recreate();
return true;
}
if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
return true;
}
switch (menuItemId) {
switch (item.getItemId()) {
case R.id.action_settings:
startActivity(new Intent(this, SettingsActivity.class));
break;
@@ -198,10 +212,7 @@ public class MainActivity extends WinBoLLActivity {
startActivity(new Intent(this, MainUnitTestActivity.class));
break;
case R.id.action_about:
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAPPInfo();
intent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
startAboutActivity();
break;
default:
return super.onOptionsItemSelected(item);
@@ -209,435 +220,324 @@ public class MainActivity extends WinBoLLActivity {
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
permissionUtils.handlePermissionRequest(this, requestCode, resultCode, data);
if (requestCode == REQUEST_READ_MEDIA_IMAGES) {
if (_mHandler != null) {
_mHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
}
}
}
@Override
public void onBackPressed() {
moveTaskToBack(true);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
@Override
public void setupToolbar() {
super.setupToolbar();
LogUtils.d(TAG, "setupToolbar()");
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
}
// ======================== 移除缓存相关核心方法restoreFromCache/refreshViewFromCache========================
@Override
public void onBackPressed() {
LogUtils.d(TAG, "onBackPressed()");
moveTaskToBack(true);
}
/**
* 初始化电量图标(复用逻辑)
*/
private void initBatteryDrawables() {
if (mCurrentValueBatteryDrawable == null) {
mCurrentValueBatteryDrawable = new BatteryDrawable(getActivity().getColor(R.color.colorCurrent));
}
if (mChargeReminderValueBatteryDrawable == null) {
mChargeReminderValueBatteryDrawable = new BatteryDrawable(getActivity().getColor(R.color.colorCharge));
}
if (mUsegeReminderValueBatteryDrawable == null) {
mUsegeReminderValueBatteryDrawable = new BatteryDrawable(getActivity().getColor(R.color.colorUsege));
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
LogUtils.d(TAG, "dispatchKeyEvent() | event=" + event);
return super.dispatchKeyEvent(event);
}
// ======================== 核心初始化方法 ========================
private void initPermissionUtils() {
LogUtils.d(TAG, "initPermissionUtils()");
mPermissionUtils = PermissionUtils.getInstance();
}
private void initGlobalHandler() {
LogUtils.d(TAG, "initGlobalHandler()");
if (sGlobalHandler == null) {
sGlobalHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (sMainActivity == null || sMainActivity.isFinishing() || sMainActivity.isDestroyed()) {
LogUtils.w(TAG, "handleMessage: Activity已销毁跳过消息 | what=" + msg.what);
return;
}
LogUtils.d(TAG, "handleMessage() | what=" + msg.what);
switch (msg.what) {
case MSG_RELOAD_APPCONFIG:
sMainActivity.updateViewData();
break;
case MSG_CURRENTVALUEBATTERY:
if (sMainActivity.mMainContentView != null) {
sMainActivity.mMainContentView.updateCurrentBattery(msg.arg1);
LogUtils.d(TAG, "handleMessage: 更新当前电量 | value=" + msg.arg1);
}
break;
case MSG_LOAD_BACKGROUND:
sMainActivity.reloadBackground();
sMainActivity.setMainLayoutBackgroundColor();
break;
case MSG_UPDATE_SERVICE_SWITCH:
sMainActivity.updateServiceSwitchUI();
break;
}
}
};
}
}
// ======================== 原有核心方法(移除缓存判断逻辑)========================
/**
* 刷新背景(移除缓存判断)
*/
public void reloadBackground() {
if (mViewHolder == null || mBgSourceUtils == null || mViewHolder.backgroundView == null) {
LogUtils.e(TAG, "reloadBackground: 背景加载失败(控件/工具类未初始化)");
return;
}
BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean();
if (bean != null) {
mViewHolder.backgroundView.loadBackgroundBean(bean);
} else {
LogUtils.e(TAG, "reloadBackground: 背景Bean为空图片路径异常");
mViewHolder.backgroundView.setBackgroundResource(R.drawable.default_background);
}
private void initMainContentView() {
LogUtils.d(TAG, "initMainContentView()");
View rootView = findViewById(android.R.id.content);
mMainContentView = new MainContentView(this, rootView, this);
}
/**
* 设置主页面背景颜色(移除缓存判断)
*/
void setBackgroundColor() {
if (isFinishing() || isDestroyed() || mViewHolder == null || mBgSourceUtils == null || mViewHolder.mainLayout == null) {
return;
}
BackgroundBean bean = mBgSourceUtils.getCurrentBackgroundBean();
if (bean != null) {
int nPixelColor = bean.getPixelColor();
mViewHolder.mainLayout.setBackgroundColor(nPixelColor);
private void initCriticalView() {
LogUtils.d(TAG, "initCriticalView()");
sMainActivity = this;
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
if (mToolbar != null) {
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
}
mAdsViewStub = findViewById(R.id.stub_ads_banner);
}
/**
* 设置视图数据(移除缓存判断)
*/
void setViewData() {
if (mViewHolder == null || mAppConfigUtils == null) return;
int nChargeReminderValue = mAppConfigUtils.getChargeReminderValue();
int nUsegeReminderValue = mAppConfigUtils.getUsegeReminderValue();
int nCurrentValue = mAppConfigUtils.getCurrentValue();
if (mViewHolder.llLeftSeekBar != null && mViewHolder.llRightSeekBar != null && mDrawableFrame != null) {
mViewHolder.llLeftSeekBar.setBackground(mDrawableFrame);
mViewHolder.llRightSeekBar.setBackground(mDrawableFrame);
}
initBatteryDrawables();
if (mViewHolder.ivCurrentBattery != null) {
mCurrentValueBatteryDrawable.setValue(nCurrentValue);
mViewHolder.ivCurrentBattery.setImageDrawable(mCurrentValueBatteryDrawable);
}
if (mViewHolder.ivChargeReminderBattery != null) {
mChargeReminderValueBatteryDrawable.setValue(nChargeReminderValue);
mViewHolder.ivChargeReminderBattery.setImageDrawable(mChargeReminderValueBatteryDrawable);
}
if (mViewHolder.ivUsegeReminderBattery != null) {
mUsegeReminderValueBatteryDrawable.setValue(nUsegeReminderValue);
mViewHolder.ivUsegeReminderBattery.setImageDrawable(mUsegeReminderValueBatteryDrawable);
}
if (mViewHolder.tvChargeReminderValue != null) {
mViewHolder.tvChargeReminderValue.setTextColor(getActivity().getColor(R.color.colorCharge));
mViewHolder.tvChargeReminderValue.setText(String.valueOf(nChargeReminderValue) + "%");
}
if (mViewHolder.chargeReminderSeekBar != null) {
mViewHolder.chargeReminderSeekBar.setProgress(nChargeReminderValue);
}
if (mViewHolder.cbIsEnableChargeReminder != null) {
mViewHolder.cbIsEnableChargeReminder.setChecked(mAppConfigUtils.getIsEnableChargeReminder());
}
if (mViewHolder.tvUsegeReminderValue != null) {
mViewHolder.tvUsegeReminderValue.setTextColor(getActivity().getColor(R.color.colorUsege));
mViewHolder.tvUsegeReminderValue.setText(String.valueOf(nUsegeReminderValue) + "%");
}
if (mViewHolder.usegeReminderSeekBar != null) {
mViewHolder.usegeReminderSeekBar.setProgress(nUsegeReminderValue);
}
if (mViewHolder.cbIsEnableUsegeReminder != null) {
mViewHolder.cbIsEnableUsegeReminder.setChecked(mAppConfigUtils.getIsEnableUsegeReminder());
}
if (mViewHolder.tvCurrentValue != null) {
mViewHolder.tvCurrentValue.setTextColor(getActivity().getColor(R.color.colorCurrent));
mViewHolder.tvCurrentValue.setText(String.valueOf(nCurrentValue) + "%");
}
if (mViewHolder.swIsEnableService != null) {
mViewHolder.swIsEnableService.setChecked(mAppConfigUtils.getIsEnableService());
mViewHolder.swIsEnableService.setText(getString(R.string.txt_aboveswitch));
}
if (mViewHolder.tvTips != null) {
mViewHolder.tvTips.setText(getString(R.string.txt_aboveswitchtips));
}
}
/**
* 设置视图监听(保持原有)
*/
void setViewListener() {
if (mViewHolder == null || mAppConfigUtils == null) return;
if (mViewHolder.chargeReminderSeekBar != null) {
mViewHolder.chargeReminderSeekBar.setOnSeekBarChangeListener(new ChargeReminderSeekBarChangeListener());
}
if (mViewHolder.cbIsEnableChargeReminder != null) {
mViewHolder.cbIsEnableChargeReminder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAppConfigUtils.setIsEnableChargeReminder(mViewHolder.cbIsEnableChargeReminder.isChecked());
}
});
}
if (mViewHolder.usegeReminderSeekBar != null) {
mViewHolder.usegeReminderSeekBar.setOnSeekBarChangeListener(new UsegeReminderSeekBarChangeListener());
}
if (mViewHolder.cbIsEnableUsegeReminder != null) {
mViewHolder.cbIsEnableUsegeReminder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAppConfigUtils.setIsEnableUsegeReminder(mViewHolder.cbIsEnableUsegeReminder.isChecked());
}
});
}
if (mViewHolder.swIsEnableService != null) {
mViewHolder.swIsEnableService.setOnClickListener(new CompoundButton.OnClickListener() {
@Override
public void onClick(View view) {
mAppConfigUtils.setIsEnableService(getActivity(), mViewHolder.swIsEnableService.isChecked());
}
});
}
}
/**
* 更新当前电量显示(保持原有)
*/
void setCurrentValueBattery(int value) {
if (mViewHolder == null || mCurrentValueBatteryDrawable == null || mViewHolder.tvCurrentValue == null) return;
mViewHolder.tvCurrentValue.setText(String.valueOf(value) + "%");
mCurrentValueBatteryDrawable.setValue(value);
mCurrentValueBatteryDrawable.invalidateSelf();
}
/**
* 初始化ViewHolder保持原有
*/
private void initViewHolder() {
mViewHolder = new ViewHolder();
mViewHolder.mainLayout = (RelativeLayout) findViewById(R.id.activitymainRelativeLayout1);
if (mViewHolder.mainLayout == null) LogUtils.e(TAG, "initViewHolder: mainLayout绑定失败ID不匹配");
mViewHolder.backgroundView = (BackgroundView) findViewById(R.id.fragmentmainviewBackgroundView1);
if (mViewHolder.backgroundView == null) LogUtils.e(TAG, "initViewHolder: backgroundView绑定失败ID不匹配");
mViewHolder.llLeftSeekBar = (LinearLayout) findViewById(R.id.fragmentmainviewLinearLayout1);
mViewHolder.llRightSeekBar = (LinearLayout) findViewById(R.id.fragmentmainviewLinearLayout2);
mViewHolder.cbIsEnableChargeReminder = (CheckBox) findViewById(R.id.fragmentmainviewCheckBox1);
mViewHolder.cbIsEnableUsegeReminder = (CheckBox) findViewById(R.id.fragmentmainviewCheckBox2);
mViewHolder.swIsEnableService = (Switch) findViewById(R.id.fragmentandroidviewSwitch1);
mViewHolder.tvTips = (TextView) findViewById(R.id.fragmentandroidviewTextView1);
mViewHolder.tvChargeReminderValue = (TextView) findViewById(R.id.fragmentandroidviewTextView2);
mViewHolder.tvUsegeReminderValue = (TextView) findViewById(R.id.fragmentandroidviewTextView3);
mViewHolder.tvCurrentValue = (TextView) findViewById(R.id.fragmentandroidviewTextView4);
mViewHolder.chargeReminderSeekBar = (VerticalSeekBar) findViewById(R.id.fragmentandroidviewVerticalSeekBar1);
mViewHolder.usegeReminderSeekBar = (VerticalSeekBar) findViewById(R.id.fragmentandroidviewVerticalSeekBar2);
mViewHolder.ivCurrentBattery = (ImageView) findViewById(R.id.fragmentandroidviewImageView1);
mViewHolder.ivChargeReminderBattery = (ImageView) findViewById(R.id.fragmentandroidviewImageView3);
mViewHolder.ivUsegeReminderBattery = (ImageView) findViewById(R.id.fragmentandroidviewImageView2);
mAdsViewStub = (ViewStub) findViewById(R.id.stub_ads_banner);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
}
/**
* 初始化核心工具类(保持原有)
*/
private void initCoreUtilsAsync() {
LogUtils.d(TAG, "initCoreUtilsAsync()");
new Thread(new Runnable() {
@Override
public void run() {
LogUtils.d(TAG, "initCoreUtilsAsync: 异步线程启动 | threadId=" + Thread.currentThread().getId());
mApplication = (App) getApplication();
mAppConfigUtils = App.getAppConfigUtils(getActivity());
mAppConfigUtils = AppConfigUtils.getInstance(getApplicationContext());
mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity());
// 初始化服务控制配置
mServiceControlBean = ControlCenterServiceBean.loadBean(getApplicationContext(), ControlCenterServiceBean.class);
if (mServiceControlBean == null) {
mServiceControlBean = new ControlCenterServiceBean(false);
ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean);
}
// 根据配置启停服务
final boolean isServiceEnable = mServiceControlBean.isEnableService();
final boolean isServiceAlive = ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName());
LogUtils.d(TAG, "initCoreUtilsAsync: 服务配置状态 | isServiceEnable=" + isServiceEnable + " | isServiceAlive=" + isServiceAlive);
if (isServiceEnable && !isServiceAlive) {
runOnUiThread(new Runnable() {
@Override
public void run() {
ControlCenterService.startControlCenterService(getApplicationContext());
}
});
} else if (!isServiceEnable && isServiceAlive) {
runOnUiThread(new Runnable() {
@Override
public void run() {
ControlCenterService.stopControlCenterService(getApplicationContext());
}
});
}
// 主线程更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getActivity() != null) {
mDrawableFrame = getActivity().getDrawable(R.drawable.bg_frame);
if (isFinishing() || isDestroyed()) {
LogUtils.w(TAG, "initCoreUtilsAsync: Activity已销毁跳过UI更新");
return;
}
setViewData();
setViewListener();
checkServiceAsync();
if (_mHandler != null) {
_mHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
// 加载框架背景适配API23+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mFrameDrawable = getResources().getDrawable(R.drawable.bg_frame, getTheme());
} else {
mFrameDrawable = getResources().getDrawable(R.drawable.bg_frame);
}
updateViewData();
sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
}
});
}
}).start();
}
/**
* 初始化首屏核心视图(保持原有)
*/
private void initCriticalView() {
_mMainActivity = this;
setSupportActionBar(mToolbar);
if (mToolbar != null) {
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
}
}
/**
* 延迟加载非首屏核心视图(保持原有)
*/
private void loadNonCriticalViewDelayed() {
LogUtils.d(TAG, "loadNonCriticalViewDelayed() | 延迟时长=" + DELAY_LOAD_NON_CRITICAL + "ms");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (isFinishing() || isDestroyed()) return;
if (isFinishing() || isDestroyed()) {
LogUtils.w(TAG, "loadNonCriticalViewDelayed: Activity已销毁跳过广告加载");
return;
}
loadAdsView();
}
}, DELAY_LOAD_NON_CRITICAL);
}
/**
* 加载广告视图(保持原有)
*/
// ======================== 视图操作方法 ========================
private void loadAdsView() {
if (mAdsViewStub == null) return;
LogUtils.d(TAG, "loadAdsView()");
if (mAdsViewStub == null) {
LogUtils.e(TAG, "loadAdsView: 广告ViewStub为空加载失败");
return;
}
if (mADsBannerView == null) {
View adsView = mAdsViewStub.inflate();
mADsBannerView = (ADsBannerView) adsView.findViewById(R.id.adsbanner);
mADsBannerView = adsView.findViewById(R.id.adsbanner);
}
}
/**
* 检查服务状态(保持原有)
*/
private void checkServiceAsync() {
new Thread(new Runnable() {
@Override
public void run() {
if (mAppConfigUtils == null || isFinishing() || isDestroyed()) return;
if (mAppConfigUtils.getIsEnableService()
&& !ServiceUtils.isServiceAlive(getActivity(), ControlCenterService.class.getName())) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(getActivity(), ControlCenterService.class);
getActivity().startForegroundService(intent);
}
});
}
}
}).start();
private void updateViewData() {
LogUtils.d(TAG, "updateViewData()");
if (mMainContentView == null || mFrameDrawable == null) {
LogUtils.e(TAG, "updateViewData: 核心视图或框架背景为空,更新失败");
return;
}
mMainContentView.updateViewData(mFrameDrawable);
}
/**
* 初始化全局Handler保持原有
*/
private void initGlobalHandler() {
if (_mHandler == null) {
_mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RELOAD_APPCONFIG:
if (_mMainActivity != null && _mMainActivity.mViewHolder != null) {
_mMainActivity.setViewData();
}
break;
case MSG_CURRENTVALUEBATTERY:
if (_mMainActivity != null && _mMainActivity.mViewHolder != null) {
_mMainActivity.setCurrentValueBattery(msg.arg1);
}
break;
case MSG_LOAD_BACKGROUND:
if (_mMainActivity != null) {
_mMainActivity.reloadBackground();
_mMainActivity.setBackgroundColor();
}
break;
}
super.handleMessage(msg);
}
};
private void reloadBackground() {
LogUtils.d(TAG, "reloadBackground()");
if (mMainContentView == null || mBgSourceUtils == null) {
LogUtils.e(TAG, "reloadBackground: 核心视图或背景工具类为空,加载失败");
return;
}
BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean();
if (currentBgBean != null) {
mMainContentView.backgroundView.loadBackgroundBean(currentBgBean);
} else {
mMainContentView.backgroundView.setBackgroundResource(R.drawable.default_background);
}
}
// 内部监听类(保持原有)
class ChargeReminderSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mViewHolder == null || mChargeReminderValueBatteryDrawable == null || mViewHolder.tvChargeReminderValue == null) return;
mViewHolder.tvChargeReminderValue.setText(String.valueOf(progress) + "%");
mChargeReminderValueBatteryDrawable.setValue(progress);
mChargeReminderValueBatteryDrawable.invalidateSelf();
private void setMainLayoutBackgroundColor() {
LogUtils.d(TAG, "setMainLayoutBackgroundColor()");
if (isFinishing() || isDestroyed() || mMainContentView == null || mBgSourceUtils == null) {
LogUtils.e(TAG, "setMainLayoutBackgroundColor: 上下文无效,设置失败");
return;
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (mAppConfigUtils == null || mViewHolder == null || mViewHolder.tvChargeReminderValue == null) return;
int nChargeReminderValue = ((VerticalSeekBar) seekBar)._mnProgress;
mAppConfigUtils.setChargeReminderValue(nChargeReminderValue);
mViewHolder.tvChargeReminderValue.setText(String.valueOf(nChargeReminderValue) + "%");
BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean();
if (currentBgBean != null) {
mMainContentView.mainLayout.setBackgroundColor(currentBgBean.getPixelColor());
LogUtils.d(TAG, "setMainLayoutBackgroundColor: 主布局背景色设置完成 | color=" + currentBgBean.getPixelColor());
}
}
class UsegeReminderSeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mViewHolder == null || mUsegeReminderValueBatteryDrawable == null || mViewHolder.tvUsegeReminderValue == null) return;
mViewHolder.tvUsegeReminderValue.setText(String.valueOf(progress) + "%");
mUsegeReminderValueBatteryDrawable.setValue(progress);
mUsegeReminderValueBatteryDrawable.invalidateSelf();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (mAppConfigUtils == null || mViewHolder == null || mViewHolder.tvUsegeReminderValue == null) return;
int nUsegeReminderValue = ((VerticalSeekBar) seekBar)._mnProgress;
mAppConfigUtils.setUsegeReminderValue(nUsegeReminderValue);
mViewHolder.tvUsegeReminderValue.setText(String.valueOf(nUsegeReminderValue) + "%");
private void updateServiceSwitchUI() {
LogUtils.d(TAG, "updateServiceSwitchUI()");
if (mMainContentView == null || mServiceControlBean == null) {
LogUtils.e(TAG, "updateServiceSwitchUI: 核心视图或服务配置为空,更新失败");
return;
}
boolean configEnabled = mServiceControlBean.isEnableService();
mMainContentView.setServiceSwitchEnabled(false);
mMainContentView.setServiceSwitchChecked(configEnabled);
mMainContentView.setServiceSwitchEnabled(true);
}
// 静态工具方法(保持原有)
public static void relaodAppConfigs() {
if (_mHandler != null) {
_mHandler.sendMessage(_mHandler.obtainMessage(MSG_RELOAD_APPCONFIG));
// ======================== 服务与线程管理方法 ========================
private void toggleServiceEnableState(boolean isEnable) {
LogUtils.d(TAG, "toggleServiceEnableState() | 目标状态=" + isEnable);
if (mServiceControlBean == null) {
LogUtils.e(TAG, "toggleServiceEnableState: 服务配置为空,切换失败");
return;
}
}
mServiceControlBean.setIsEnableService(isEnable);
ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean);
public static void sendMsgCurrentValueBattery(int value) {
if (_mHandler != null) {
Message msg = _mHandler.obtainMessage(MSG_CURRENTVALUEBATTERY);
msg.arg1 = value;
_mHandler.sendMessage(msg);
}
}
private String getRealPathFromURI(Uri contentUri) {
String[] proj = {MediaStore.MediaColumns.DATA};
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
if (cursor != null && cursor.moveToNext()) {
int nColumnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
if (nColumnIndex > -1) {
String path = cursor.getString(nColumnIndex);
cursor.close();
return path;
// UI开关联动服务启停
if (isEnable) {
if (!ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName())) {
ControlCenterService.startControlCenterService(getApplicationContext());
}
cursor.close();
} else {
ControlCenterService.stopControlCenterService(getApplicationContext());
}
return null;
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
}
APPInfo genDefaultAPPInfo() {
String szBranchName = "powerbell";
// ======================== 页面跳转方法 ========================
private void startAboutActivity() {
LogUtils.d(TAG, "startAboutActivity()");
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAppInfo();
aboutIntent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), aboutIntent, AboutActivity.class);
}
// ======================== 消息发送方法 ========================
private void notifyServiceAppConfigChange() {
LogUtils.d(TAG, "notifyServiceAppConfigChange()");
ControlCenterService.sendAppConfigStatusUpdateMessage(this);
reloadAppConfig();
}
public static void reloadAppConfig() {
LogUtils.d(TAG, "reloadAppConfig()");
if (sGlobalHandler != null) {
sGlobalHandler.sendEmptyMessage(MSG_RELOAD_APPCONFIG);
} else {
LogUtils.w(TAG, "reloadAppConfig: 全局Handler为空消息发送失败");
}
}
public static void sendCurrentBatteryValueMessage(int value) {
LogUtils.d(TAG, "sendCurrentBatteryValueMessage() | 电量=" + value);
if (sGlobalHandler != null) {
Message msg = sGlobalHandler.obtainMessage(MSG_CURRENTVALUEBATTERY);
msg.arg1 = value;
sGlobalHandler.sendMessage(msg);
} else {
LogUtils.w(TAG, "sendCurrentBatteryValueMessage: 全局Handler为空消息发送失败");
}
}
// ======================== 辅助工具方法 ========================
private APPInfo genDefaultAppInfo() {
LogUtils.d(TAG, "genDefaultAppInfo()");
String branchName = "powerbell";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppIcon(R.drawable.ic_launcher);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(szBranchName);
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=PowerBell");
appInfo.setAppAPKName("PowerBell");
appInfo.setAppAPKFolderName("PowerBell");
return appInfo;
}
// ======================== MainContentView 事件回调 ========================
@Override
public void onChargeReminderSwitchChanged(boolean isChecked) {
LogUtils.d(TAG, "onChargeReminderSwitchChanged() | isChecked=" + isChecked);
notifyServiceAppConfigChange();
}
@Override
public void onUsageReminderSwitchChanged(boolean isChecked) {
LogUtils.d(TAG, "onUsageReminderSwitchChanged() | isChecked=" + isChecked);
notifyServiceAppConfigChange();
}
@Override
public void onServiceSwitchChanged(boolean isChecked) {
LogUtils.d(TAG, "onServiceSwitchChanged() | isChecked=" + isChecked);
toggleServiceEnableState(isChecked);
}
@Override
public void onChargeReminderProgressChanged(int progress) {
LogUtils.d(TAG, "onChargeReminderProgressChanged() | progress=" + progress);
notifyServiceAppConfigChange();
}
@Override
public void onUsageReminderProgressChanged(int progress) {
LogUtils.d(TAG, "onUsageReminderProgressChanged() | progress=" + progress);
notifyServiceAppConfigChange();
}
}

View File

@@ -15,7 +15,6 @@ import android.os.Looper;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.View;
import android.widget.LinearLayout;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;
@@ -24,6 +23,7 @@ import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
import cc.winboll.studio.powerbell.dialogs.ColorPaletteDialog;
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
@@ -32,6 +32,7 @@ import cc.winboll.studio.powerbell.utils.FileUtils;
import cc.winboll.studio.powerbell.utils.ImageCropUtils;
import cc.winboll.studio.powerbell.utils.UriUtils;
import cc.winboll.studio.powerbell.views.BackgroundView;
import com.a4455jkjh.colorpicker.ColorPickerDialog;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -207,6 +208,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener);
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
findViewById(R.id.activitybackgroundsettingsAButton1).setOnClickListener(onColorPaletteClickListener);
}
// ====================== 按钮点击事件 ======================
@@ -321,7 +323,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
LogUtils.d(TAG, "【按钮点击】清空像素颜色");
BackgroundBean bean = mBgSourceUtils.getPreviewBackgroundBean();
int oldColor = bean.getPixelColor();
bean.setPixelColor(0);
bean.setPixelColor(0xFF000000);
mBgSourceUtils.saveSettings();
doubleRefreshPreview();
isPreviewBackgroundChanged = true;
@@ -330,6 +332,32 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
}
};
private View.OnClickListener onColorPaletteClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "【按钮点击】调色板按钮");
// 初始颜色(白色,含透明度)
//int initialColor = 0xFFFFFFFF;
int initialColor = mBgSourceUtils.getPreviewBackgroundBean().getPixelColor();
// 显示对话框
ColorPaletteDialog dialog = new ColorPaletteDialog(BackgroundSettingsActivity.this, initialColor, new ColorPaletteDialog.OnColorSelectedListener() {
@Override
public void onColorSelected(int color) {
// 回调返回 0xAARRGGBB 格式颜色,直接使用
mBgSourceUtils.getPreviewBackgroundBean().setPixelColor(color);
mBgSourceUtils.saveSettings();
doubleRefreshPreview();
isPreviewBackgroundChanged = true;
LogUtils.d("选择颜色", String.format("#%08X", color));
}
});
dialog.show();
LogUtils.d(TAG, "调色板按钮响应完成。");
}
};
// ====================== 工具方法 ======================
/**
* 生成 FileProvider Uri适配 Android 7.0+

View File

@@ -70,7 +70,7 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
@Override
public void onOHPCommit() {
mApplication.clearBatteryHistory();
sendBroadcast(new Intent(ControlCenterServiceReceiver.ACTION_UPDATE_SERVICENOTIFICATION));
sendBroadcast(new Intent(ControlCenterServiceReceiver.ACTION_UPDATE_FOREGROUND_NOTIFICATION));
initRecordText();
String szMSG = "The APP battery record is cleaned.";
LogUtils.d(TAG, szMSG);

View File

@@ -0,0 +1,747 @@
package cc.winboll.studio.powerbell.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import com.a4455jkjh.colorpicker.ColorPickerDialog;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/12/16 11:47
* @Describe 调色板对话框支持颜色拾取、RGB输入、透明度/亮度调节,兼容 API29-30+ 小米机型)
*/
public class ColorPaletteDialog extends Dialog implements View.OnClickListener, SeekBar.OnSeekBarChangeListener {
// ====================== 常量定义(首屏可见,统一管理) ======================
public static final String TAG = "ColorPaletteDialog";
private static final int MAX_RGB_VALUE = 255; // RGB分量最大值0-255
private static final int DEFAULT_BRIGHTNESS = 100; // 默认亮度百分比100%,无调节)
private static final int BRIGHTNESS_STEP = 5; // 亮度调节步长每次±5%,精准流畅)
private static final int MIN_BRIGHTNESS = 10; // 亮度最小值10%,避免全黑看不见)
private static final int MAX_BRIGHTNESS = 200; // 亮度最大值200%,避免过曝失真)
private static final int MAX_ALPHA_PERCENT = 100; // 透明度最大值100%=不透明)
private static final int MIN_ALPHA_PERCENT = 0; // 透明度最小值0%=完全透明)
// ====================== 回调接口(紧跟常量,逻辑关联) ======================
public interface OnColorSelectedListener {
void onColorSelected(int color); // 返回0xAARRGGBB格式颜色含透明度
}
// ====================== 成员变量(按优先级排序:核心数据→控件引用) ======================
// 核心数据:原始基准值(用户输入/选择颜色时更新)+ 实时调节值(亮度/透明度变化时更新)
private OnColorSelectedListener mListener; // 颜色选择回调(非空校验)
private int mInitialColor; // 初始颜色(传入的默认颜色)
private int mCurrentColor; // 当前最终颜色(含亮度+透明度调节)
private int mCurrentBrightnessPercent; // 当前亮度百分比10%-200%
// 透明度百分比0-100%,用户直观操作)+ 原始/实时值0-255颜色计算用
private int mOriginalAlphaPercent; // 原始透明度百分比(基准值,用户输入/选色时更新)
private int mCurrentAlphaPercent; // 实时透明度百分比(调节进度条时更新)
private int mOriginalAlpha; // 原始透明度0-255基准值
private int mCurrentAlpha; // 实时透明度0-255计算用
// RGB原始基准值+实时调节值
private int mOriginalR; // 原始R分量基准值用户输入/选色时更新)
private int mOriginalG; // 原始G分量基准值用户输入/选色时更新)
private int mOriginalB; // 原始B分量基准值用户输入/选色时更新)
private int mCurrentR; // 实时R分量亮度调节后同步输入框显示
private int mCurrentG; // 实时G分量亮度调节后同步输入框显示
private int mCurrentB; // 实时B分量亮度调节后同步输入框显示
// 并发控制标记:是否是应用程序自身在更新颜色(避免循环回调/重复触发)
private static volatile boolean isAppSelfUpdatingColor = false;
// 控件引用(新增透明度进度条+文本)
private ImageView ivColorPicker; // 颜色预览拾取框
private ImageView ivColorScaler; // 颜色渐变拾取框
private EditText etR; // R分量输入框显示实时调节值
private EditText etG; // G分量输入框显示实时调节值
private EditText etB; // B分量输入框显示实时调节值
private EditText etColorValue; // 颜色值输入框(#AARRGGBB显示最终值
private SeekBar sbAlpha; // 透明度调节进度条0-100%
private TextView tvAlphaValue; // 透明度数值显示X%
private TextView tvBrightnessMinus;// 亮度减少按钮(-
private TextView tvBrightnessValue;// 亮度数值显示X%,直观易懂)
private TextView tvBrightnessPlus; // 亮度增加按钮(+
private TextView tvConfirm; // 确认按钮
private TextView tvCancel; // 取消按钮
// ====================== 构造方法(初始化核心数据,严格校验) ======================
public ColorPaletteDialog(Context context, int initialColor, OnColorSelectedListener listener) {
super(context, R.style.CustomDialogStyle);
this.mInitialColor = initialColor;
this.mListener = listener;
// 1. 强制回调非空,避免后续空指针(容错)
if (mListener == null) {
throw new IllegalArgumentException("OnColorSelectedListener can not be null!");
}
// 2. 解析初始颜色:原始基准值 = 实时值(初始无调节)
// 透明度初始颜色的alpha0-255转百分比0-100%
this.mOriginalAlpha = Color.alpha(initialColor);
this.mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
this.mCurrentAlpha = mOriginalAlpha;
this.mCurrentAlphaPercent = mOriginalAlphaPercent;
// RGB初始颜色的RGB分量
this.mOriginalR = Color.red(initialColor);
this.mOriginalG = Color.green(initialColor);
this.mOriginalB = Color.blue(initialColor);
this.mCurrentR = mOriginalR;
this.mCurrentG = mOriginalG;
this.mCurrentB = mOriginalB;
// 3. 初始化当前状态默认亮度100%,当前颜色=初始颜色)
this.mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
this.mCurrentColor = initialColor;
LogUtils.d(TAG, "init dialog success | 初始颜色:" + String.format("#%08X", initialColor)
+ " | 原始RGB" + mOriginalR + "," + mOriginalG + "," + mOriginalB
+ " | 原始透明度:" + mOriginalAlphaPercent + "%"
+ " | 初始亮度:" + mCurrentBrightnessPercent + "%");
}
// ====================== 生命周期方法(按执行顺序排列,逻辑清晰) ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 隐藏标题栏
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_color_palette, null);
setContentView(view);
// 初始化流程:控件绑定→数据赋值→监听设置→尺寸适配(小米机型优先适配)
initViewBind(view);
initData();
initListener();
adjustDialogSize();
LogUtils.d(TAG, "dialog create complete | 适配小米API29-30机型");
}
@Override
public void dismiss() {
super.dismiss();
// 释放资源,避免内存泄漏(回调引用置空)
mListener = null;
LogUtils.d(TAG, "dialog dismiss | 释放资源完成");
}
// ====================== 初始化核心方法(职责单一,便于维护) ======================
/**
* 控件绑定(新增透明度进度条+文本绑定)
*/
private void initViewBind(View view) {
ivColorPicker = view.findViewById(R.id.iv_color_picker);
ivColorScaler = view.findViewById(R.id.iv_color_scaler);
etR = view.findViewById(R.id.et_r);
etG = view.findViewById(R.id.et_g);
etB = view.findViewById(R.id.et_b);
etColorValue = view.findViewById(R.id.et_color_value);
sbAlpha = view.findViewById(R.id.sb_alpha);
tvAlphaValue = view.findViewById(R.id.tv_alpha_value);
tvBrightnessMinus = view.findViewById(R.id.tv_brightness_minus);
tvBrightnessValue = view.findViewById(R.id.tv_brightness_value);
tvBrightnessPlus = view.findViewById(R.id.tv_brightness_plus);
tvConfirm = view.findViewById(R.id.tv_confirm);
tvCancel = view.findViewById(R.id.tv_cancel);
// 控件非空校验(小米低版本容错,绑定失败直接关闭对话框)
if (ivColorPicker == null || ivColorScaler == null || etR == null || etG == null || etB == null || etColorValue == null
|| sbAlpha == null || tvAlphaValue == null
|| tvBrightnessMinus == null || tvBrightnessValue == null || tvBrightnessPlus == null
|| tvConfirm == null || tvCancel == null) {
LogUtils.e(TAG, "view bind failed | 请检查布局ID是否正确");
dismiss();
return;
}
LogUtils.d(TAG, "view bind complete | 所有控件绑定成功");
}
/**
* 数据初始化(无监听状态下赋值,避免循环回调)
*/
private void initData() {
// 1. 颜色预览(显示当前最终颜色,初始=原始颜色)
ivColorPicker.setBackgroundColor(mCurrentColor);
// 2. RGB输入框显示「实时分量」初始=原始值)
etR.setText(String.valueOf(mCurrentR));
etG.setText(String.valueOf(mCurrentG));
etB.setText(String.valueOf(mCurrentB));
// 3. 颜色值输入框(显示当前最终颜色,格式#AARRGGBB大写更规范
etColorValue.setText(String.format("#%08X", mCurrentColor));
// 4. 透明度控件(进度条+文本,初始=原始透明度)
sbAlpha.setProgress(mCurrentAlphaPercent);
tvAlphaValue.setText(mCurrentAlphaPercent + "%");
// 5. 亮度控件显示默认100%,初始化按钮状态)
tvBrightnessValue.setText(mCurrentBrightnessPercent + "%");
updateBrightnessBtnStatus(); // 禁用边界值按钮初始100%,都可用)
LogUtils.d(TAG, "init data complete | 原始透明度:" + mOriginalAlphaPercent + "%");
}
/**
* 监听初始化(新增透明度进度条监听)
*/
private void initListener() {
// 点击监听(按钮+颜色拾取框)
ivColorPicker.setOnClickListener(this);
ivColorScaler.setOnClickListener(this);
tvConfirm.setOnClickListener(this);
tvCancel.setOnClickListener(this);
tvBrightnessMinus.setOnClickListener(this);
tvBrightnessPlus.setOnClickListener(this);
// 透明度进度条监听
sbAlpha.setOnSeekBarChangeListener(this);
// 输入框监听RGB+颜色值,避免循环同步)
initTextWatcherListener();
LogUtils.d(TAG, "all listener init complete | 监听绑定成功");
}
/**
* 对话框尺寸适配(小米全面屏+软键盘优化,避免输入框被遮挡)
*/
private void adjustDialogSize() {
Window window = getWindow();
if (window != null) {
WindowManager.LayoutParams lp = window.getAttributes();
// 宽度占屏幕80%,高度自适应(适配不同屏幕尺寸)
lp.width = (int) (getContext().getResources().getDisplayMetrics().widthPixels * 0.8);
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 软键盘适配:小米虚拟导航栏兼容,避免输入框被遮挡
window.setAttributes(lp);
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
| WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
LogUtils.d(TAG, "dialog size adjust complete | 适配全面屏+软键盘");
}
}
// ====================== 监听子方法(细分类型,逻辑清晰) ======================
/**
* 输入框文本监听RGB+颜色值传入触发ID避免循环同步
*/
private void initTextWatcherListener() {
// RGB输入框监听复用方法减少冗余
setEditTextWatcher(etR, R.id.et_r);
setEditTextWatcher(etG, R.id.et_g);
setEditTextWatcher(etB, R.id.et_b);
// 颜色值输入框监听(支持#RRGGBB/#AARRGGBB格式
etColorValue.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
// 关键:判断非应用自身更新,才执行解析(避免循环回调)
if (!isAppSelfUpdatingColor) {
parseColorFromStr(s.toString().trim(), R.id.et_color_value);
}
}
});
}
// ====================== 透明度进度条监听实现(核心新增) ======================
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// 仅处理用户手动拖动进度条(避免应用自身更新时触发)
if (fromUser && !isAppSelfUpdatingColor) {
updateAlphaBySeekBar(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
/**
* 拖动透明度进度条更新颜色(核心新增逻辑)
*/
private synchronized void updateAlphaBySeekBar(int alphaPercent) {
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
try {
// 更新实时透明度(百分比+0-255值
mCurrentAlphaPercent = alphaPercent;
mCurrentAlpha = percent2Alpha(alphaPercent);
// 重新计算最终颜色(基于当前亮度+新透明度)
calculateBrightnessAndUpdate();
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, "update alpha by seekbar | 透明度:" + mCurrentAlphaPercent + "%");
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
}
// ====================== 颜色核心逻辑(新增透明度参数,全功能兼容) ======================
/**
* 核心计算基于原始RGB+当前亮度+当前透明度计算实时RGB+最终颜色
* 逻辑亮度百分比→调节系数→原始RGB×系数→限制0-255→拼接透明度→最终颜色
*/
private void calculateBrightnessAndUpdate() {
// 亮度百分比转调节系数10%→0.1100%→1.0200%→2.0
float brightnessFactor = mCurrentBrightnessPercent / 100.0f;
// RGB三个分量同时调节基于原始基准值避免叠加失真限制0-255
mCurrentR = Math.min(Math.max(Math.round(mOriginalR * brightnessFactor), 0), MAX_RGB_VALUE);
mCurrentG = Math.min(Math.max(Math.round(mOriginalG * brightnessFactor), 0), MAX_RGB_VALUE);
mCurrentB = Math.min(Math.max(Math.round(mOriginalB * brightnessFactor), 0), MAX_RGB_VALUE);
// 拼接「实时透明度」+「实时RGB」得到最终颜色0xAARRGGBB
mCurrentColor = Color.argb(mCurrentAlpha, mCurrentR, mCurrentG, mCurrentB);
}
/**
* 亮度减少每次减5%最低10%,防止过暗)
*/
private void decreaseBrightness() {
changeBrightness(false);
}
/**
* 亮度增加每次加5%最高200%,防止过曝)
*/
private void increaseBrightness() {
changeBrightness(true);
}
/**
* 亮度调节核心方法(统一逻辑,加并发控制,同步所有控件)
*/
private synchronized void changeBrightness(boolean isIncrease) {
// 关键:判断非应用自身更新,才执行调节(避免重复触发/循环)
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
try {
if (isIncrease) {
if (mCurrentBrightnessPercent >= MAX_BRIGHTNESS) return; // 达到最大值,不处理
mCurrentBrightnessPercent += BRIGHTNESS_STEP; // 增加步长
} else {
if (mCurrentBrightnessPercent <= MIN_BRIGHTNESS) return; // 达到最小值,不处理
mCurrentBrightnessPercent -= BRIGHTNESS_STEP; // 减少步长
}
// 计算亮度调节后的实时RGB+最终颜色(含当前透明度)
calculateBrightnessAndUpdate();
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, (isIncrease ? "increase" : "decrease") + " brightness | "
+ "亮度:" + mCurrentBrightnessPercent + "% | 实时RGB" + mCurrentR + "," + mCurrentG + "," + mCurrentB);
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
}
/**
* 解析颜色字符串(支持#RRGGBB/#AARRGGBB容错处理更新原始基准值+实时值)
* 新增:解析颜色的透明度,同步更新透明度进度条
*/
private void parseColorFromStr(String colorStr, int triggerViewId) {
// 关键:判断非应用自身更新,才执行解析(避免循环回调)
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
try {
if (TextUtils.isEmpty(colorStr)) return;
// 补全#前缀兼容用户输入习惯如直接输AARRGGBB
if (!colorStr.startsWith("#")) {
colorStr = "#" + colorStr;
}
// 格式校验仅支持6位RRGGBB/8位AARRGGBB避免非法格式
if (colorStr.length() != 7 && colorStr.length() != 9) {
LogUtils.e(TAG, "parse color failed | 格式错误(需#RRGGBB/#AARRGGBB输入" + colorStr);
return;
}
// 解析颜色系统API安全可靠
int parsedColor = Color.parseColor(colorStr);
// 更新原始基准值(用户输入颜色,重置基准)
// 透明度解析颜色的alpha0-255转百分比0-100%
mOriginalAlpha = Color.alpha(parsedColor);
mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
// RGB解析颜色的RGB分量
mOriginalR = Color.red(parsedColor);
mOriginalG = Color.green(parsedColor);
mOriginalB = Color.blue(parsedColor);
// 更新实时值(原始值=实时值,无调节)
mCurrentAlpha = mOriginalAlpha;
mCurrentAlphaPercent = mOriginalAlphaPercent;
mCurrentR = mOriginalR;
mCurrentG = mOriginalG;
mCurrentB = mOriginalB;
// 重置亮度为100%
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
mCurrentColor = parsedColor;
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, "parse color success | 解析颜色:" + String.format("#%08X", parsedColor)
+ " | 透明度:" + mCurrentAlphaPercent + "% | 重置亮度:" + DEFAULT_BRIGHTNESS + "%");
} catch (IllegalArgumentException e) {
LogUtils.e(TAG, "parse color failed | 非法颜色格式,输入:" + colorStr, e);
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
}
/**
* 通过RGB输入框更新颜色用户输入后更新原始基准值+实时值重置亮度为100%
* 新增透明度基准值保持不变仅更新RGB
*/
private synchronized void updateColorByRGB(int triggerViewId) {
// 关键:判断非应用自身更新,才执行更新(避免循环回调)
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true; // 标记为应用自身更新
try {
// 解析用户输入的RGB值限制0-255非法输入设为0
int inputR = parseInputValue(etR.getText().toString());
int inputG = parseInputValue(etG.getText().toString());
int inputB = parseInputValue(etB.getText().toString());
// 更新原始基准值(用户手动输入,作为新的调节基准)
mOriginalR = inputR;
mOriginalG = inputG;
mOriginalB = inputB;
// 更新实时值(输入值=实时值,无亮度调节)
mCurrentR = inputR;
mCurrentG = inputG;
mCurrentB = inputB;
// 重置亮度为100%(透明度保持当前值不变)
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
// 计算最终颜色(无亮度调节,拼接当前透明度)
mCurrentColor = Color.argb(mCurrentAlpha, mCurrentR, mCurrentG, mCurrentB);
// 同步所有控件
updateAllViews();
LogUtils.d(TAG, "update color by RGB | 新原始RGB" + mOriginalR + "," + mOriginalG + "," + mOriginalB
+ " | 透明度:" + mCurrentAlphaPercent + "% | 重置亮度:" + DEFAULT_BRIGHTNESS + "%");
} catch (Exception e) {
LogUtils.e(TAG, "update color by RGB failed", e);
} finally {
// 直接释放标记,避免卡顿
isAppSelfUpdatingColor = false;
}
}
}
/**
* 核心同步:更新所有控件显示(新增透明度控件同步,统一方法)
*/
private void updateAllViews() {
// 1. 同步颜色预览(显示最终颜色,含透明度+亮度)
ivColorPicker.setBackgroundColor(mCurrentColor);
// 2. 同步RGB输入框显示实时调节值
etR.setText(String.valueOf(mCurrentR));
etG.setText(String.valueOf(mCurrentG));
etB.setText(String.valueOf(mCurrentB));
// 3. 同步颜色值输入框(显示最终颜色,含透明度,格式#AARRGGBB
etColorValue.setText(String.format("#%08X", mCurrentColor));
// 4. 同步透明度控件(进度条+文本,显示实时透明度)
sbAlpha.setProgress(mCurrentAlphaPercent);
tvAlphaValue.setText(mCurrentAlphaPercent + "%");
// 5. 同步亮度控件(数值+按钮状态)
tvBrightnessValue.setText(mCurrentBrightnessPercent + "%");
updateBrightnessBtnStatus();
LogUtils.d(TAG, "sync all views complete | 最终颜色:" + String.format("#%08X", mCurrentColor)
+ " | 实时RGB" + mCurrentR + "," + mCurrentG + "," + mCurrentB
+ " | 透明度:" + mCurrentAlphaPercent + "% | 亮度:" + mCurrentBrightnessPercent + "%");
}
/**
* 更新亮度按钮状态(边界值禁用,提升交互体验)
*/
private void updateBrightnessBtnStatus() {
// 亮度≤10%禁用减号文字变浅灰≥200%:禁用加号(文字变浅灰)
boolean canMinus = mCurrentBrightnessPercent > MIN_BRIGHTNESS;
boolean canPlus = mCurrentBrightnessPercent < MAX_BRIGHTNESS;
tvBrightnessMinus.setEnabled(canMinus);
tvBrightnessPlus.setEnabled(canPlus);
tvBrightnessMinus.setTextColor(canMinus ? Color.BLACK : Color.parseColor("#CCCCCC"));
tvBrightnessPlus.setTextColor(canPlus ? Color.BLACK : Color.parseColor("#CCCCCC"));
}
// ====================== 工具方法(新增透明度转换工具,通用复用) ======================
/**
* 透明度0-255 → 0-100%(颜色计算值转用户直观百分比)
*/
private int alpha2Percent(int alpha) {
return Math.round((float) alpha / MAX_RGB_VALUE * MAX_ALPHA_PERCENT);
}
/**
* 透明度0-100% → 0-255用户操作百分比转颜色计算值
*/
private int percent2Alpha(int percent) {
return Math.round((float) percent / MAX_ALPHA_PERCENT * MAX_RGB_VALUE);
}
/**
* 解析输入值限制0-255非法输入返回0容错处理
*/
private int parseInputValue(String input) {
if (TextUtils.isEmpty(input)) return 0;
try {
int value = Integer.parseInt(input);
return Math.min(Math.max(value, 0), MAX_RGB_VALUE); // 限制范围,避免溢出
} catch (NumberFormatException e) {
LogUtils.e(TAG, "parse input failed | 非法数字,输入:" + input, e);
return 0;
}
}
/**
* RGB输入框监听复用减少冗余代码统一逻辑
*/
private void setEditTextWatcher(EditText editText, final int viewId) {
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
// 关键:判断非应用自身更新,才执行更新(避免循环回调)
if (!isAppSelfUpdatingColor) {
updateColorByRGB(viewId); // 输入变化后更新颜色
}
}
});
}
/**
* dp转px适配小米不同分辨率避免尺寸错乱通用工具
*/
private int dp2px(float dp) {
return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f);
}
/**
* 显示系统颜色选择器兼容API29-30无高版本依赖小米机型适配
* 核心调整:新增「水平滚动容器+颜色排列容器」二级结构内置圆形按钮无额外drawable依赖
*/
private void showSystemColorPicker() {
LogUtils.d(TAG, "show system color picker | 兼容小米API29-30支持横向滚动");
final android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(getContext());
builder.setTitle("选择基础颜色");
// 50种常用颜色按「红→橙→黄→绿→青→蓝→紫→粉→棕→灰→黑白」彩虹光谱顺序排列
final int[] systemColors = {
// 红色系6种深红→大红→浅红→玫红→暗红→橘红
0xFFCC0000, 0xFFFF0000, 0xFFFF6666, 0xFFFF1493, 0xFF8B0000, 0xFFFF4500,
// 橙色系5种深橙→橙→浅橙→橙黄→橘橙
0xFFCC6600, 0xFFFF8800, 0xFFFFAA33, 0xFFFFBB00, 0xFFF5A623,
// 黄色系5种深黄→黄→浅黄→鹅黄→金黄
0xFFCCCC00, 0xFFFFFF00, 0xFFFFEE99, 0xFFFFFACD, 0xFFFFD700,
// 绿色系7种深绿→绿→浅绿→草绿→薄荷绿→翠绿→墨绿
0xFF006600, 0xFF00FF00, 0xFF99FF99, 0xFF66CC66, 0xFF98FB98, 0xFF00FF99, 0xFF003300,
// 青色系5种深青→青→浅青→蓝绿→青绿
0xFF006666, 0xFF00FFFF, 0xFF99FFFF, 0xFF00CCCC, 0xFF40E0D0,
// 蓝色系8种深蓝→藏蓝→蓝→浅蓝→天蓝→宝蓝→湖蓝→靛蓝
0xFF0000CC, 0xFF00008B, 0xFF0000FF, 0xFF6666FF, 0xFF87CEEB, 0xFF0066FF, 0xFF0099FF, 0xFF4B0082,
// 紫色系6种深紫→紫→浅紫→紫罗兰→紫红→蓝紫
0xFF660099, 0xFF8800FF, 0xFFAA99FF, 0xFF9370DB, 0xFFCBC3E3, 0xFF8A2BE2,
// 粉色系5种深粉→粉→浅粉→嫩粉→桃粉
0xFFFF00FF, 0xFFFF99CC, 0xFFFFCCDD, 0xFFFFB6C1, 0xFFFFA5A5,
// 棕色系4种深棕→棕→浅棕→棕黄
0xFF8B4513, 0xFFA0522D, 0xFFD2B48C, 0xFFCD853F,
// 灰色系6种深灰→灰→浅灰→银灰→淡灰→浅银灰
0xFF333333, 0xFF666666, 0xFF888888, 0xFFAAAAAA, 0xFFCCCCCC, 0xFFE6E6E6,
// 黑白系3种黑→白→米白
0xFF000000, 0xFFFFFFFF, 0xFFFFFAFA
};
// 1. 第一级:水平滚动容器
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(getContext());
horizontalScrollView.setHorizontalScrollBarEnabled(true);
horizontalScrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
horizontalScrollView.setPadding(dp2px(5), dp2px(5), dp2px(5), dp2px(5));
// 2. 第二级:颜色排列容器(横向)
LinearLayout colorLayout = new LinearLayout(getContext());
colorLayout.setOrientation(LinearLayout.HORIZONTAL);
colorLayout.setGravity(Gravity.CENTER_VERTICAL);
colorLayout.setPadding(dp2px(10), dp2px(10), dp2px(10), dp2px(10));
// 3. 循环添加颜色按钮(内置圆形效果,无额外依赖)
for (int i = 0; i < systemColors.length; i++) {
final int color = systemColors[i];
ImageView colorBtn = new ImageView(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(dp2px(40), dp2px(40));
if (i != systemColors.length - 1) {
lp.setMargins(0, 0, dp2px(10), 0); // 按钮间距
}
colorBtn.setLayoutParams(lp);
// 核心:内置圆形背景(白色边框+圆形形状无需drawable文件
GradientDrawable circleBg = new GradientDrawable();
circleBg.setShape(GradientDrawable.OVAL); // 圆形
circleBg.setColor(color); // 按钮颜色
circleBg.setStroke(dp2px(2), Color.WHITE); // 白色边框2dp宽区分颜色
colorBtn.setBackground(circleBg); // 设置圆形背景
colorBtn.setClickable(true);
colorBtn.setFocusable(true);
// 点击事件(逻辑不变)
colorBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true;
try {
mOriginalAlpha = Color.alpha(color);
mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
mOriginalR = Color.red(color);
mOriginalG = Color.green(color);
mOriginalB = Color.blue(color);
mCurrentAlpha = mOriginalAlpha;
mCurrentAlphaPercent = mOriginalAlphaPercent;
mCurrentR = mOriginalR;
mCurrentG = mOriginalG;
mCurrentB = mOriginalB;
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
mCurrentColor = color;
updateAllViews();
builder.create().dismiss();
LogUtils.d(TAG, "select system color | 选择颜色:" + String.format("#%08X", color)
+ " | 透明度:" + mCurrentAlphaPercent + "%");
} finally {
isAppSelfUpdatingColor = false;
}
}
}
});
colorLayout.addView(colorBtn);
}
// 层级嵌套(滚动容器→颜色容器)
horizontalScrollView.addView(colorLayout);
builder.setView(horizontalScrollView).setNegativeButton("关闭", null).show();
}
// ====================== 点击事件实现(统一处理,逻辑清晰) ======================
@Override
public void onClick(View v) {
//ToastUtils.show("onClick");
int id = v.getId();
// 关键:所有点击事件均加判断(避免并发冲突/重复触发)
if (!isAppSelfUpdatingColor) {
if (id == R.id.iv_color_picker) {
showSystemColorPicker(); // 打开系统颜色选择器
} if (id == R.id.iv_color_scaler) {
//ToastUtils.show("iv_color_scale");
openColorScalerDialog(mCurrentColor); // 打开系统颜色选择器
} else if (id == R.id.tv_confirm) {
mListener.onColorSelected(mCurrentColor); // 确认选择,回调颜色
LogUtils.d(TAG, "confirm color | 回调颜色:" + String.format("#%08X", mCurrentColor));
dismiss();
} else if (id == R.id.tv_cancel) {
dismiss(); // 取消,关闭对话框
LogUtils.d(TAG, "cancel color | 取消选择,关闭对话框");
} else if (id == R.id.tv_brightness_minus) {
decreaseBrightness(); // 减少亮度
} else if (id == R.id.tv_brightness_plus) {
increaseBrightness(); // 增加亮度
}
}
}
void openColorScalerDialog(int nColor) {
//ToastUtils.show("openColorPickerDialog");
final ColorScalerDialog dlg = new ColorScalerDialog(getContext(), nColor);
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
@Override
public void beforeColorChanged() {
}
@Override
public void onColorChanged(int color) {
dlg.currentColorScalerDialogColor = color;
}
@Override
public void afterColorChanged() {
}
});
dlg.show();
}
class ColorScalerDialog extends ColorPickerDialog {
public int currentColorScalerDialogColor = 0;
public ColorScalerDialog(Context context, int p) {
super(context, p);
}
@Override
public void dismiss() {
super.dismiss();
int color = currentColorScalerDialogColor;
ToastUtils.show(String.format("dismiss color %d", color));
if (!isAppSelfUpdatingColor) {
isAppSelfUpdatingColor = true;
try {
mOriginalAlpha = Color.alpha(color);
mOriginalAlphaPercent = alpha2Percent(mOriginalAlpha);
mOriginalR = Color.red(color);
mOriginalG = Color.green(color);
mOriginalB = Color.blue(color);
mCurrentAlpha = mOriginalAlpha;
mCurrentAlphaPercent = mOriginalAlphaPercent;
mCurrentR = mOriginalR;
mCurrentG = mOriginalG;
mCurrentB = mOriginalB;
mCurrentBrightnessPercent = DEFAULT_BRIGHTNESS;
mCurrentColor = color;
updateAllViews();
LogUtils.d(TAG, "select system color | 选择颜色:" + String.format("#%08X", color)
+ " | 透明度:" + mCurrentAlphaPercent + "%");
} finally {
isAppSelfUpdatingColor = false;
}
}
}
}
}

View File

@@ -1,42 +0,0 @@
package cc.winboll.studio.powerbell.fragments;
import android.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.activities.PixelPickerActivity;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.powerbell.utils.ServiceUtils;
import cc.winboll.studio.powerbell.views.BackgroundView;
import cc.winboll.studio.powerbell.views.BatteryDrawable;
import cc.winboll.studio.powerbell.views.VerticalSeekBar;
public class MainViewFragment extends Fragment {
public static final String TAG = "MainViewFragment";
}

View File

@@ -2,35 +2,119 @@ package cc.winboll.studio.powerbell.handlers;
import android.os.Handler;
import android.os.Message;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.models.NotificationMessage;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import java.lang.ref.WeakReference;
/**
* 服务通信Handler
* 功能:处理电量提醒消息,构建并发送标准化通知
* 特性:弱引用防泄漏、参数严格校验、通知格式统一
* 适配Java7 | API30 | 小米手机
*/
public class ControlCenterServiceHandler extends Handler {
public static final String TAG = ControlCenterServiceHandler.class.getSimpleName();
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
public static final String TAG = "ControlCenterServiceHandler";
public static final int MSG_REMIND_TEXT = 1001; // 电量提醒消息标识
public static final int MSG_REMIND_TEXT = 0;
// 提醒类型常量
private static final String REMIND_TYPE_CHARGE = "+";
private static final String REMIND_TYPE_USAGE = "-";
WeakReference<ControlCenterService> serviceWeakReference;
// 电量范围常量
private static final int BATTERY_LEVEL_MIN = 0;
private static final int BATTERY_LEVEL_MAX = 100;
// 通知文案常量(抽离魔法值,便于统一修改)
private static final String CHARGE_REMIND_TITLE = "充电提醒";
private static final String USAGE_REMIND_TITLE = "耗电提醒";
private static final String CHARGE_REMIND_CONTENT_FORMAT = "(+)电量已达额定值。当前电量%d%%%s。";
private static final String USAGE_REMIND_CONTENT_FORMAT = "(-)电量低于指定值。当前电量%d%%%s。";
private static final String CHARGE_STATE_CHARGING = "充电中";
private static final String CHARGE_STATE_NOT_CHARGING = "未充电";
// ================================== 成员变量区弱引用防泄漏final保证不可变=================================
private final WeakReference<ControlCenterService> mwrControlCenterService;
// ================================== 构造方法(强制传入服务,初始化弱引用)=================================
public ControlCenterServiceHandler(ControlCenterService service) {
serviceWeakReference = new WeakReference<ControlCenterService>(service);
LogUtils.d(TAG, "构造方法执行 | service=" + (service != null ? service.getClass().getSimpleName() : "null"));
this.mwrControlCenterService = new WeakReference<>(service);
}
// ================================== 核心消息处理重写handleMessage解析多参数消息=================================
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 解析消息参数obj=提醒类型(+/-)arg1=当前电量arg2=充电状态(1=充电/0=未充电)
String remindType = (msg.obj != null) ? (String) msg.obj : "";
int currentBattery = msg.arg1;
boolean isCharging = msg.arg2 == 1;
LogUtils.d(TAG, "handleMessage: 接收消息 | what=" + msg.what + " | type=" + remindType + " | battery=" + currentBattery + " | isCharging=" + isCharging);
// 弱引用获取服务,避免内存泄漏
ControlCenterService service = mwrControlCenterService.get();
if (service == null) {
LogUtils.e(TAG, "handleMessage: 服务实例已被GC回收终止消息处理");
return;
}
// 按消息类型分发处理
switch (msg.what) {
case MSG_REMIND_TEXT: // 处理下载完成消息更新UI
{
// 显示提醒消息
//
//LogUtils.d(TAG, "显示提醒消息");
ControlCenterService controlCenterService = serviceWeakReference.get();
if (controlCenterService != null) {
//LogUtils.d(TAG, ((NotificationMessage)msg.obj).getTitle());
//LogUtils.d(TAG, ((NotificationMessage)msg.obj).getContent());
controlCenterService.appenRemindMSG((String)msg.obj);
}
break;
}
case MSG_REMIND_TEXT:
handleRemindMessage(service, remindType, currentBattery, isCharging);
break;
default:
LogUtils.w(TAG, "handleMessage: 未知消息类型,忽略处理 | what=" + msg.what);
break;
}
}
// ================================== 业务辅助方法(构建通知并发送,全链路参数校验)=================================
/**
* 处理电量提醒消息,构建带电量+充电状态的通知并发送
* @param service 控制中心服务实例(已校验非空)
* @param remindType 提醒类型(+充电/-耗电)
* @param currentBattery 当前电量0-100
* @param isCharging 充电状态
*/
private void handleRemindMessage(ControlCenterService service, String remindType, int currentBattery, boolean isCharging) {
LogUtils.d(TAG, "handleRemindMessage: 开始处理提醒消息 | type=" + remindType + " | battery=" + currentBattery + " | isCharging=" + isCharging);
// 1. 前置校验:通知工具类+参数有效性
if (service.getNotificationManager() == null) {
LogUtils.e(TAG, "handleRemindMessage: 通知管理工具类未初始化,无法发送提醒");
return;
}
if (!REMIND_TYPE_CHARGE.equals(remindType) && !REMIND_TYPE_USAGE.equals(remindType)) {
LogUtils.w(TAG, "handleRemindMessage: 提醒类型无效,忽略 | type=" + remindType + " | 允许值:" + REMIND_TYPE_CHARGE + "/" + REMIND_TYPE_USAGE);
return;
}
if (currentBattery < BATTERY_LEVEL_MIN || currentBattery > BATTERY_LEVEL_MAX) {
LogUtils.w(TAG, "handleRemindMessage: 电量值超出范围,忽略 | battery=" + currentBattery + " | 允许范围:" + BATTERY_LEVEL_MIN + "-" + BATTERY_LEVEL_MAX);
return;
}
// 2. 构建通知模型,使用统一格式
NotificationMessage remindMsg = new NotificationMessage();
String chargeStateDesc = isCharging ? CHARGE_STATE_CHARGING : CHARGE_STATE_NOT_CHARGING;
if (REMIND_TYPE_CHARGE.equals(remindType)) {
remindMsg.setTitle(CHARGE_REMIND_TITLE);
remindMsg.setContent(String.format(CHARGE_REMIND_CONTENT_FORMAT, currentBattery, chargeStateDesc));
remindMsg.setRemindMSG("charge_remind");
} else {
remindMsg.setTitle(USAGE_REMIND_TITLE);
remindMsg.setContent(String.format(USAGE_REMIND_CONTENT_FORMAT, currentBattery, chargeStateDesc));
remindMsg.setRemindMSG("usage_remind");
}
LogUtils.d(TAG, "handleRemindMessage: 通知模型构建完成 | title=" + remindMsg.getTitle() + " | content=" + remindMsg.getContent());
// 3. 调用通知工具类发送提醒
LogUtils.d(TAG, "handleRemindMessage: 调用通知工具类发送提醒 | remindMSG=" + remindMsg.getRemindMSG());
service.getNotificationManager().showRemindNotification(service, remindMsg);
LogUtils.d(TAG, "handleRemindMessage: 提醒通知发送流程执行完毕");
}
}

View File

@@ -5,44 +5,71 @@ package cc.winboll.studio.powerbell.models;
* @Date 2024/04/29 17:24:53
* @Describe 应用运行参数类
*/
import android.os.Parcel;
import android.os.Parcelable;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
import java.io.Serializable;
public class AppConfigBean extends BaseBean implements Serializable {
// 核心修正:新增 Parcelable 接口实现API30 持久化/Intent 传递必备)
public class AppConfigBean extends BaseBean implements Serializable, Parcelable {
// 序列化版本号Serializable 必备,避免反序列化失败)
private static final long serialVersionUID = 1L;
transient public static final String TAG = "AppConfigBean";
boolean isEnableUsegeReminder = false;
int usegeReminderValue = 45;
boolean isEnableChargeReminder = false;
int chargeReminderValue = 100;
// 铃声提醒间隔时间。.
int reminderIntervalTime = 5000;
// 电池是否正在充电。
boolean isCharging = false;
// 电池当前电量。.
int currentValue = -1;
// 核心配置字段(保留原有字段,统一状态字段命名)
boolean isEnableUsageReminder = false; // 耗电提醒开关
int usageReminderValue = 45; // 耗电提醒阈值0-100
boolean isEnableChargeReminder = false;// 充电提醒开关
int chargeReminderValue = 100; // 充电提醒阈值0-100
int reminderIntervalTime = 5000; // 铃声提醒间隔ms原有
boolean isCharging = false; // 是否充电(状态字段,原有)
int currentBatteryValue = -1; // 修正统一命名为「currentBatteryValue」原 currentValue
int batteryDetectInterval = 2000; // 新增电量检测间隔ms适配 RemindThread
// 构造方法:初始化默认配置(同步修正字段名,统一默认值)
public AppConfigBean() {
setChargeReminderValue(100);
setIsEnableChargeReminder(false);
setUsegeReminderValue(10);
setIsEnableUsegeReminder(false);
setEnableChargeReminder(false);
setUsageReminderValue(10);
setEnableUsageReminder(false);
setReminderIntervalTime(5000);
setBatteryDetectInterval(1000); // 新增默认检测间隔1秒
setCurrentBatteryValue(-1); // 修正:初始化当前电量字段
}
// ====================== 核心修复:补全缺失方法(适配 RemindThread/Receiver 调用) ======================
/**
* 设置当前电池电量Receiver 监听电池变化时调用,与 RemindThread 字段对齐)
*/
public void setCurrentBatteryValue(int currentBatteryValue) {
// 强化校验:电量范围限制在 0-100异常值置为 -1标识无效
this.currentBatteryValue = (currentBatteryValue >= 0 && currentBatteryValue <= 100)
? currentBatteryValue : -1;
}
/**
* 获取当前电池电量RemindThread 同步配置时调用,与 set 方法对应)
*/
public int getCurrentBatteryValue() {
return currentBatteryValue;
}
// ====================== 原有字段 Setter/Getter修正命名强化校验 ======================
public void setReminderIntervalTime(int reminderIntervalTime) {
this.reminderIntervalTime = reminderIntervalTime;
// 校验:提醒间隔不小于 1000ms避免频繁提醒
this.reminderIntervalTime = Math.max(reminderIntervalTime, 1000);
}
public int getReminderIntervalTime() {
return reminderIntervalTime;
}
public void setIsCharging(boolean isCharging) {
public void setIsCharging(boolean isCharging) { // 修正:方法名与字段名统一(原 setCharging
this.isCharging = isCharging;
}
@@ -50,31 +77,24 @@ public class AppConfigBean extends BaseBean implements Serializable {
return isCharging;
}
public void setCurrentValue(int currentValue) {
this.currentValue = currentValue;
public void setEnableUsageReminder(boolean isEnableUsageReminder) {
this.isEnableUsageReminder = isEnableUsageReminder;
}
public int getCurrentValue() {
return currentValue;
public boolean isEnableUsageReminder() {
return isEnableUsageReminder;
}
public void setIsEnableUsegeReminder(boolean isEnableUsegeReminder) {
this.isEnableUsegeReminder = isEnableUsegeReminder;
public void setUsageReminderValue(int usageReminderValue) {
// 校验:阈值范围 0-100
this.usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100);
}
public boolean isEnableUsegeReminder() {
return isEnableUsegeReminder;
public int getUsageReminderValue() {
return usageReminderValue;
}
public void setUsegeReminderValue(int usegeReminderValue) {
this.usegeReminderValue = usegeReminderValue;
}
public int getUsegeReminderValue() {
return usegeReminderValue;
}
public void setIsEnableChargeReminder(boolean isEnableChargeReminder) {
public void setEnableChargeReminder(boolean isEnableChargeReminder) {
this.isEnableChargeReminder = isEnableChargeReminder;
}
@@ -83,13 +103,25 @@ public class AppConfigBean extends BaseBean implements Serializable {
}
public void setChargeReminderValue(int chargeReminderValue) {
this.chargeReminderValue = chargeReminderValue;
// 校验:阈值范围 0-100
this.chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100);
}
public int getChargeReminderValue() {
return chargeReminderValue;
}
// ====================== 电量检测间隔 Setter/Getter适配 RemindThread ======================
public int getBatteryDetectInterval() {
return batteryDetectInterval;
}
// 强化校验检测间隔不小于500ms避免 CPU 高占用,与 RemindThread 最小休眠一致)
public void setBatteryDetectInterval(int batteryDetectInterval) {
this.batteryDetectInterval = Math.max(batteryDetectInterval, 500);
}
// ====================== JSON 序列化/反序列化(兼容旧配置,同步修正字段) ======================
@Override
public String getName() {
return AppConfigBean.class.getName();
@@ -99,10 +131,18 @@ public class AppConfigBean extends BaseBean implements Serializable {
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
AppConfigBean bean = this;
jsonWriter.name("isEnableUsegeReminder").value(bean.isEnableUsegeReminder());
jsonWriter.name("usegeReminderValue").value(bean.getUsegeReminderValue());
// 原有字段序列化(保留拼写兼容,同步修正字段名)
jsonWriter.name("isEnableUsageReminder").value(bean.isEnableUsageReminder());
jsonWriter.name("usageReminderValue").value(bean.getUsageReminderValue());
jsonWriter.name("isEnableChargeReminder").value(bean.isEnableChargeReminder());
jsonWriter.name("chargeReminderValue").value(bean.getChargeReminderValue());
jsonWriter.name("reminderIntervalTime").value(bean.getReminderIntervalTime());
jsonWriter.name("isCharging").value(bean.isCharging());
// 修正:序列化新字段名 currentBatteryValue兼容旧字段 currentValue
jsonWriter.name("currentBatteryValue").value(bean.getCurrentBatteryValue());
jsonWriter.name("currentValue").value(bean.getCurrentBatteryValue()); // 兼容旧配置,避免数据丢失
// 新增字段序列化:电量检测间隔
jsonWriter.name("batteryDetectInterval").value(bean.getBatteryDetectInterval());
}
@Override
@@ -111,20 +151,77 @@ public class AppConfigBean extends BaseBean implements Serializable {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("isEnableUsegeReminder")) {
bean.setIsEnableUsegeReminder(jsonReader.nextBoolean());
} else if (name.equals("usegeReminderValue")) {
bean.setUsegeReminderValue(jsonReader.nextInt());
// 原有字段反序列化(兼容旧 Key 拼写,同步修正字段)
if (name.equals("isEnableUsageReminder") || name.equals("isEnableUsegeReminder")) {
bean.setEnableUsageReminder(jsonReader.nextBoolean());
} else if (name.equals("usageReminderValue") || name.equals("usegeReminderValue")) {
bean.setUsageReminderValue(jsonReader.nextInt());
} else if (name.equals("isEnableChargeReminder")) {
bean.setIsEnableChargeReminder(jsonReader.nextBoolean());
bean.setEnableChargeReminder(jsonReader.nextBoolean());
} else if (name.equals("chargeReminderValue")) {
bean.setChargeReminderValue(jsonReader.nextInt());
} else if (name.equals("reminderIntervalTime")) {
bean.setReminderIntervalTime(jsonReader.nextInt());
} else if (name.equals("isCharging")) {
bean.setIsCharging(jsonReader.nextBoolean()); // 修正:调用新方法名
}
// 核心兼容:优先读取旧字段 currentValue再读取新字段 currentBatteryValue新字段覆盖旧字段
else if (name.equals("currentValue")) {
bean.setCurrentBatteryValue(jsonReader.nextInt());
} else if (name.equals("currentBatteryValue")) {
bean.setCurrentBatteryValue(jsonReader.nextInt());
}
// 新增字段反序列化兼容无此字段的旧配置用默认值1000ms
else if (name.equals("batteryDetectInterval")) {
bean.setBatteryDetectInterval(jsonReader.nextInt());
} else {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return bean;
}
// ====================== Parcelable 接口实现(同步修正字段,确保 Intent 传递正常) ======================
@Override
public int describeContents() {
return 0; // 无特殊内容描述固定返回0
}
// 序列化:将所有字段写入 Parcel同步修正字段名Java7 适配)
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (isEnableUsageReminder ? 1 : 0)); // boolean → byte
dest.writeInt(usageReminderValue);
dest.writeByte((byte) (isEnableChargeReminder ? 1 : 0)); // boolean → byte
dest.writeInt(chargeReminderValue);
dest.writeInt(reminderIntervalTime);
dest.writeByte((byte) (isCharging ? 1 : 0)); // boolean → byte
dest.writeInt(currentBatteryValue); // 修正:序列化新字段名
dest.writeInt(batteryDetectInterval);
}
// 反序列化:从 Parcel 读取字段,创建对象(必须 public static final 修饰)
public static final Parcelable.Creator<AppConfigBean> CREATOR = new Parcelable.Creator<AppConfigBean>() {
@Override
public AppConfigBean createFromParcel(Parcel source) {
AppConfigBean bean = new AppConfigBean();
// 按 writeToParcel 顺序读取,同步修正字段
bean.isEnableUsageReminder = source.readByte() != 0;
bean.usageReminderValue = source.readInt();
bean.isEnableChargeReminder = source.readByte() != 0;
bean.chargeReminderValue = source.readInt();
bean.reminderIntervalTime = source.readInt();
bean.isCharging = source.readByte() != 0;
bean.currentBatteryValue = source.readInt(); // 修正:读取新字段名
bean.batteryDetectInterval = source.readInt();
return bean;
}
@Override
public AppConfigBean[] newArray(int size) {
return new AppConfigBean[size];
}
};
}

View File

@@ -1,63 +1,142 @@
package cc.winboll.studio.powerbell.models;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 07:06:07
* @Describe 服务控制参数
*/
import android.os.Parcel;
import android.os.Parcelable;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
import java.io.Serializable;
public class ControlCenterServiceBean extends BaseBean {
import cc.winboll.studio.libappbase.BaseBean;
import cc.winboll.studio.libappbase.LogUtils;
public static final String TAG = "ControlCenterServiceBean";
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 15:55
* @Describe 服务控制参数模型管理服务启用状态支持序列化、Parcel传递、JSON解析
*/
public class ControlCenterServiceBean extends BaseBean implements Parcelable, Serializable {
// ================================== 静态常量(置顶统一管理,避免魔法值)=================================
private static final long serialVersionUID = 1L; // Serializable 必备,保障反序列化兼容
private static final String TAG = "ControlCenterServiceBean";
// JSON 字段常量,避免硬编码,减少拼写错误
private static final String JSON_FIELD_IS_ENABLE_SERVICE = "isEnableService";
boolean isEnableService = false;
// ================================== 核心成员变量(私有封装,规范命名)=================================
private boolean isEnableService = false; // 服务启用状态true=启用false=禁用
// ================================== Parcelable 静态创建器(必须 public static final适配 API30 传递)=================================
public static final Parcelable.Creator<ControlCenterServiceBean> CREATOR = new Parcelable.Creator<ControlCenterServiceBean>() {
@Override
public ControlCenterServiceBean createFromParcel(Parcel source) {
LogUtils.d(TAG, "Parcelable createFromParcel: 从Parcel反序列化对象");
// Java7 + API30 适配Parcel 无直接 writeBoolean用 byte 存储/读取
boolean isEnable = source.readByte() != 0;
ControlCenterServiceBean bean = new ControlCenterServiceBean(isEnable);
LogUtils.d(TAG, "Parcelable createFromParcel: 反序列化完成isEnableService=" + isEnable);
return bean;
}
@Override
public ControlCenterServiceBean[] newArray(int size) {
LogUtils.d(TAG, "Parcelable newArray: 创建数组,长度=" + size);
return new ControlCenterServiceBean[size];
}
};
// ================================== 构造方法(无参+有参,满足不同初始化场景)=================================
/**
* 无参构造JSON解析、反射创建必备
*/
public ControlCenterServiceBean() {
this.isEnableService = false;
LogUtils.d(TAG, "无参构造初始化服务状态为禁用false");
}
/**
* 有参构造(指定服务启用状态)
* @param isEnableService 服务启用状态
*/
public ControlCenterServiceBean(boolean isEnableService) {
this.isEnableService = isEnableService;
LogUtils.d(TAG, "有参构造初始化服务状态isEnableService=" + isEnableService);
}
public void setIsEnableService(boolean isEnableService) {
this.isEnableService = isEnableService;
}
// ================================== Getter/Setter 方法(封装成员变量,控制访问)=================================
public boolean isEnableService() {
LogUtils.d(TAG, "get isEnableService: 当前状态=" + isEnableService);
return isEnableService;
}
public void setIsEnableService(boolean isEnableService) {
LogUtils.d(TAG, "set isEnableService: 旧状态=" + this.isEnableService + ",新状态=" + isEnableService);
this.isEnableService = isEnableService;
}
// ================================== 父类 BaseBean 方法重写(核心业务逻辑)=================================
@Override
public String getName() {
return ControlCenterServiceBean.class.getName();
String className = ControlCenterServiceBean.class.getName();
LogUtils.d(TAG, "getName: 返回类名=" + className);
return className;
}
/**
* 序列化对象到 JSON适配数据持久化/网络传输)
*/
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
LogUtils.d(TAG, "writeThisToJsonWriter: 开始将对象序列化到JSON");
super.writeThisToJsonWriter(jsonWriter);
ControlCenterServiceBean bean = this;
jsonWriter.name("isEnableService").value(bean.isEnableService());
// 写入服务启用状态字段
jsonWriter.name(JSON_FIELD_IS_ENABLE_SERVICE).value(this.isEnableService);
LogUtils.d(TAG, "writeThisToJsonWriter: JSON序列化完成字段=" + JSON_FIELD_IS_ENABLE_SERVICE + ",值=" + this.isEnableService);
}
/**
* 从 JSON 反序列化创建对象(适配数据恢复)
*/
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
LogUtils.d(TAG, "readBeanFromJsonReader: 开始从JSON反序列化对象");
ControlCenterServiceBean bean = new ControlCenterServiceBean();
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("isEnableService")) {
bean.setIsEnableService(jsonReader.nextBoolean());
String fieldName = jsonReader.nextName();
if (JSON_FIELD_IS_ENABLE_SERVICE.equals(fieldName)) {
// 读取并设置服务启用状态
boolean isEnable = jsonReader.nextBoolean();
bean.setIsEnableService(isEnable);
LogUtils.d(TAG, "readBeanFromJsonReader: 读取JSON字段" + fieldName + "=" + isEnable);
} else {
// 跳过未知字段,避免解析异常
jsonReader.skipValue();
LogUtils.w(TAG, "readBeanFromJsonReader: 跳过未知JSON字段=" + fieldName);
}
}
// 结束 JSON 对象
jsonReader.endObject();
LogUtils.d(TAG, "readBeanFromJsonReader: JSON反序列化完成");
return bean;
}
// ================================== Parcelable 接口方法实现(适配 Intent 组件间传递)=================================
@Override
public int describeContents() {
// 无特殊内容如文件描述符返回0即可API30 标准实现)
LogUtils.d(TAG, "describeContents: 返回内容描述符=0");
return 0;
}
/**
* 序列化对象到 ParcelIntent 传递必备Java7 适配)
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
LogUtils.d(TAG, "writeToParcel: 开始将对象序列化到Parcelflags=" + flags);
// Java7 + API30 适配Parcel 无 writeBoolean 方法,用 byte 存储1=true0=false
dest.writeByte((byte) (this.isEnableService ? 1 : 0));
LogUtils.d(TAG, "writeToParcel: Parcel序列化完成isEnableService=" + this.isEnableService + "存储为byte=" + (this.isEnableService ? 1 : 0) + "");
}
}

View File

@@ -1,40 +1,36 @@
package cc.winboll.studio.powerbell.models;
// 应用消息结构
//
/**
* 通知数据模型:统一存储通知标题、内容等信息,适配各组件数据传递
*/
public class NotificationMessage {
String Title;
String Content;
String RemindMSG;
private String title; // 通知标题
private String content; // 通知内容
private String remindMSG; // 通知标识(区分服务运行/充电/耗电)
public NotificationMessage(String title, String content) {
Title = title;
Content = content;
}
public void setRemindMSG(String remindMSG) {
RemindMSG = remindMSG;
}
public String getRemindMSG() {
return RemindMSG;
// ====================== Setter/Getter 方法 ======================
public String getTitle() {
return title;
}
public void setTitle(String title) {
Title = title;
}
public String getTitle() {
return Title;
}
public void setContent(String content) {
Content = content;
this.title = title;
}
public String getContent() {
return Content;
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRemindMSG() {
return remindMSG;
}
public void setRemindMSG(String remindMSG) {
this.remindMSG = remindMSG;
}
}

View File

@@ -6,82 +6,250 @@ import android.content.Intent;
import android.content.IntentFilter;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.models.AppConfigBean;
import cc.winboll.studio.powerbell.models.NotificationMessage;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.BatteryUtils;
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
import java.lang.ref.WeakReference;
/**
* 控制中心广播接收器
* 功能:监听电池状态变化、前台通知更新、配置变更指令
* 适配Java7 | API30 | 内存泄漏防护 | 多线程状态同步
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/19 20:23
*/
public class ControlCenterServiceReceiver extends BroadcastReceiver {
public static final String TAG = ControlCenterServiceReceiver.class.getSimpleName();
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
public static final String TAG = "ControlCenterServiceReceiver";
public static final String ACTION_UPDATE_SERVICENOTIFICATION = ControlCenterServiceReceiver.class.getName() + ".ACTION_UPDATE_NOTIFICATION";
public static final String ACTION_START_REMINDTHREAD = ControlCenterServiceReceiver.class.getName() + ".ACTION_UPDATE_REMINDTHREAD";
// 广播Action常量带包名前缀防冲突
public static final String ACTION_UPDATE_FOREGROUND_NOTIFICATION = "cc.winboll.studio.powerbell.action.ACTION_UPDATE_FOREGROUND_NOTIFICATION";
public static final String ACTION_APPCONFIG_CHANGED = "cc.winboll.studio.powerbell.action.ACTION_APPCONFIG_CHANGED";
public static final String EXTRA_APP_CONFIG_BEAN = "extra_app_config_bean";
WeakReference<ControlCenterService> mwrService;
// 存储电量指示值,
// 用于校验电量消息时的电量变化
static volatile int _mnTheQuantityOfElectricityOld = -1;
static volatile boolean _mIsCharging = false;
// 广播优先级与电量范围常量
private static final int BROADCAST_PRIORITY = IntentFilter.SYSTEM_HIGH_PRIORITY - 10;
private static final int BATTERY_LEVEL_MIN = 0;
private static final int BATTERY_LEVEL_MAX = 100;
// ================================== 静态状态标记volatile保证多线程可见性=================================
private static volatile int sLastBatteryLevel = -1; // 上次电量(多线程可见)
private static volatile boolean sIsCharging = false; // 上次充电状态(多线程可见)
// ================================== 成员变量区(弱引用防泄漏,按功能分层)=================================
private WeakReference<ControlCenterService> mwrControlCenterService;
private boolean isRegistered = false; // 新增:标记广播注册状态,避免冗余操作
// ================================== 构造方法(初始化弱引用,避免服务强引用泄漏)=================================
public ControlCenterServiceReceiver(ControlCenterService service) {
mwrService = new WeakReference<ControlCenterService>(service);
LogUtils.d(TAG, "构造接收器 | service=" + (service != null ? service.getClass().getSimpleName() : "null"));
this.mwrControlCenterService = new WeakReference<>(service);
}
// ================================== 广播核心接收逻辑入口方法分Action分发处理=================================
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_UPDATE_SERVICENOTIFICATION)) {
mwrService.get().updateServiceNotification();
} else if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
boolean isCharging = BatteryUtils.isCharging(intent);
int nTheQuantityOfElectricity = BatteryUtils.getTheQuantityOfElectricity(intent);
if (mwrService.get().getRemindThread() != null) {
// 先设置提醒进程电池状态标志
if (_mIsCharging != isCharging) {
mwrService.get().getRemindThread().setIsCharging(isCharging);
}
if (_mnTheQuantityOfElectricityOld != nTheQuantityOfElectricity) {
mwrService.get().getRemindThread().setQuantityOfElectricity(nTheQuantityOfElectricity);
}
LogUtils.d(TAG, "onReceive: 接收广播 | action=" + (intent != null ? intent.getAction() : "null"));
// 基础参数校验
if (context == null || intent == null || intent.getAction() == null) {
LogUtils.e(TAG, "onReceive: 参数无效context=" + context + " | intent=" + intent + "),终止处理");
return;
}
// 弱引用获取服务,双重校验服务有效性
ControlCenterService service = mwrControlCenterService != null ? mwrControlCenterService.get() : null;
if (service == null || service.isDestroyed()) {
LogUtils.e(TAG, "onReceive: 服务已销毁或为空service=" + service + "),注销广播");
unregisterAction(context);
return;
}
// 分Action处理业务逻辑
String action = intent.getAction();
switch (action) {
case Intent.ACTION_BATTERY_CHANGED:
handleBatteryStateChanged(service, intent);
break;
case ACTION_UPDATE_FOREGROUND_NOTIFICATION:
handleUpdateForegroundNotification(service);
break;
case ACTION_APPCONFIG_CHANGED:
LogUtils.d(TAG, "onReceive: 开始处理配置更新广播"); // 新增:标记配置广播处理起点
handleNotifyAppConfigUpdate(service);
break;
default:
LogUtils.w(TAG, "onReceive: 未知Action=" + action);
}
LogUtils.d(TAG, "onReceive: 广播处理完成");
}
// ================================== 业务处理方法(按功能拆分,强化容错与日志)=================================
/**
* 处理电池状态变化广播
* @param service 控制中心服务实例
* @param intent 电池状态广播意图
*/
private void handleBatteryStateChanged(ControlCenterService service, Intent intent) {
LogUtils.d(TAG, "handleBatteryStateChanged: 解析电池状态 | service=" + service + " | intent=" + intent);
try {
// 1. 解析并校验当前电池状态
boolean currentCharging = BatteryUtils.isCharging(intent);
int currentBatteryLevel = BatteryUtils.getCurrentBatteryLevel(intent);
currentBatteryLevel = Math.min(Math.max(currentBatteryLevel, BATTERY_LEVEL_MIN), BATTERY_LEVEL_MAX);
LogUtils.d(TAG, "handleBatteryStateChanged: 当前状态 | 充电=" + currentCharging + " | 电量=" + currentBatteryLevel + "%");
// 2. 状态无变化则跳过,减少无效运算
if (currentCharging == sIsCharging && currentBatteryLevel == sLastBatteryLevel) {
LogUtils.d(TAG, "handleBatteryStateChanged: 电池状态无变化,跳过处理");
return;
}
// 新电池状态标志某一个有变化就更新显示信息
if (_mIsCharging != isCharging || _mnTheQuantityOfElectricityOld != nTheQuantityOfElectricity) {
mwrService.get().updateServiceNotification();
AppConfigUtils appConfigUtils = AppConfigUtils.getInstance(context);
appConfigUtils.loadAppConfigBean();
AppConfigBean appConfigBean = appConfigUtils.mAppConfigBean;
appConfigBean.setCurrentValue(nTheQuantityOfElectricity);
appConfigBean.setIsCharging(isCharging);
mwrService.get().startRemindThread(appConfigBean);
// 保存电池报告
// 示例数据更新逻辑
// List<BatteryData> newData = new ArrayList<>(adapter.getDataList());
// newData.add(0, new BatteryData(percentage, "00:00:00", "00:00:00"));
// adapter.updateData(newData);
// 保存好新的电池状态标志
_mIsCharging = isCharging;
_mnTheQuantityOfElectricityOld = nTheQuantityOfElectricity;
}
} else if (intent.getAction().equals(ACTION_START_REMINDTHREAD)) {
LogUtils.d(TAG, "ACTION_START_REMINDTHREAD");
//AppConfigUtils appConfigUtils = AppConfigUtils.getInstance(context);
//appConfigUtils.loadAppConfigBean();
AppConfigBean appConfigBean = (AppConfigBean)intent.getSerializableExtra("appConfigBean");
mwrService.get().startRemindThread(appConfigBean);
// 4. 更新静态缓存状态,保证多线程可见
sIsCharging = currentCharging;
sLastBatteryLevel = currentBatteryLevel;
handleNotifyAppConfigUpdate(service);
LogUtils.d(TAG, "handleBatteryStateChanged: 电池状态处理成功 | 缓存电量=" + sLastBatteryLevel + "% | 缓存充电状态=" + sIsCharging);
} catch (Exception e) {
LogUtils.e(TAG, "handleBatteryStateChanged: 处理失败", e);
}
}
// 注册 Receiver
//
/**
* 处理配置变更通知,同步缓存状态到配置
* @param service 控制中心服务实例
*/
private void handleNotifyAppConfigUpdate(ControlCenterService service) {
LogUtils.d(TAG, "handleNotifyAppConfigUpdate: 同步缓存状态到配置 | service=" + service);
try {
// 加载最新配置
AppConfigBean latestConfig = AppConfigUtils.getInstance(service).loadAppConfig();
if (latestConfig == null) { // 新增:配置空指针防护
LogUtils.e(TAG, "handleNotifyAppConfigUpdate: 最新配置为空,终止处理");
return;
}
LogUtils.d(TAG, "handleNotifyAppConfigUpdate: 加载最新配置 | 充电阈值=" + latestConfig.getChargeReminderValue() + " | 耗电阈值=" + latestConfig.getUsageReminderValue());
// 同步缓存的电池状态到配置
latestConfig.setCurrentBatteryValue(sLastBatteryLevel);
latestConfig.setIsCharging(sIsCharging);
service.notifyAppConfigUpdate(latestConfig);
LogUtils.d(TAG, "handleNotifyAppConfigUpdate: 配置同步成功 | 缓存电量=" + sLastBatteryLevel + "% | 充电状态=" + sIsCharging);
LogUtils.d(TAG, "handleNotifyAppConfigUpdate: 配置更新广播处理完成"); // 新增:标记配置广播处理终点
} catch (Exception e) {
LogUtils.e(TAG, "handleNotifyAppConfigUpdate: 处理失败", e);
}
}
/**
* 处理前台服务通知更新
* @param service 控制中心服务实例
*/
private void handleUpdateForegroundNotification(ControlCenterService service) {
LogUtils.d(TAG, "handleUpdateForegroundNotification: 更新前台通知 | service=" + service);
try {
NotificationManagerUtils notifyUtils = service.getNotificationManager();
NotificationMessage notifyMsg = service.getForegroundNotifyMsg();
// 非空校验,避免空指针
if (notifyUtils == null || notifyMsg == null) {
LogUtils.e(TAG, "handleUpdateForegroundNotification: 通知工具类或消息为空notifyUtils=" + notifyUtils + " | notifyMsg=" + notifyMsg + "");
return;
}
notifyUtils.updateForegroundServiceNotify(notifyMsg);
LogUtils.d(TAG, "handleUpdateForegroundNotification: 前台通知更新成功 | 通知标题=" + notifyMsg.getTitle());
} catch (Exception e) {
LogUtils.e(TAG, "handleUpdateForegroundNotification: 处理失败", e);
}
}
// ================================== 广播注册/注销(强化容错,避免重复操作)=================================
/**
* 注册广播接收器
* @param context 上下文
*/
public void registerAction(Context context) {
IntentFilter filter=new IntentFilter();
filter.addAction(ACTION_UPDATE_SERVICENOTIFICATION);
filter.addAction(ACTION_START_REMINDTHREAD);
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
context.registerReceiver(this, filter);
LogUtils.d(TAG, "registerAction: 注册广播接收器 | context=" + context);
if (context == null || isRegistered) { // 新增:已注册则跳过
LogUtils.e(TAG, "registerAction: 上下文为空或已注册,注册失败");
return;
}
try {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(ACTION_UPDATE_FOREGROUND_NOTIFICATION);
filter.addAction(ACTION_APPCONFIG_CHANGED);
filter.setPriority(BROADCAST_PRIORITY);
context.registerReceiver(this, filter);
isRegistered = true; // 标记为已注册
LogUtils.d(TAG, "registerAction: 广播注册成功 | 优先级=" + BROADCAST_PRIORITY);
} catch (Exception e) {
LogUtils.e(TAG, "registerAction: 注册失败", e);
}
}
/**
* 注销广播接收器
* @param context 上下文
*/
public void unregisterAction(Context context) {
LogUtils.d(TAG, "unregisterAction: 注销广播接收器 | context=" + context);
if (context == null || !isRegistered) { // 新增:未注册则跳过
LogUtils.e(TAG, "unregisterAction: 上下文为空或未注册,注销失败");
return;
}
try {
context.unregisterReceiver(this);
isRegistered = false; // 标记为未注册
LogUtils.d(TAG, "unregisterAction: 广播注销成功");
} catch (IllegalArgumentException e) {
LogUtils.w(TAG, "unregisterAction: 广播未注册,跳过注销");
} catch (Exception e) {
LogUtils.e(TAG, "unregisterAction: 注销失败", e);
}
}
// ================================== 资源释放与Getter方法按需开放防泄漏=================================
/**
* 主动释放资源,避免内存泄漏
*/
public void release() {
LogUtils.d(TAG, "release: 释放广播接收器资源");
// 清空弱引用帮助GC回收
if (mwrControlCenterService != null) {
mwrControlCenterService.clear();
mwrControlCenterService = null;
LogUtils.d(TAG, "release: 弱引用已清空");
}
// 重置静态状态缓存
sLastBatteryLevel = -1;
sIsCharging = false;
LogUtils.d(TAG, "release: 静态状态缓存已重置");
}
/**
* 获取上次记录的电池电量
* @return 电量值0-100未初始化返回-1
*/
public static int getLastBatteryLevel() {
return sLastBatteryLevel;
}
/**
* 获取上次记录的充电状态
* @return true=充电中false=未充电
*/
public static boolean isLastCharging() {
return sIsCharging;
}
}

View File

@@ -4,62 +4,175 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.BatteryUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/19 20:13
* @Describe 全局应用广播接收器
* 功能:监听系统电池状态变化,同步状态到配置工具类,通知页面更新
* 适配Java7 | API30 | 内存泄漏防护
*/
public class GlobalApplicationReceiver extends BroadcastReceiver {
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
public static final String TAG = "GlobalApplicationReceiver";
private static final int BATTERY_LEVEL_MIN = 0;
private static final int BATTERY_LEVEL_MAX = 100;
AppConfigUtils mAppConfigUtils;
App mGlobalApplication;
// 存储电量指示值,
// 用于校验电量消息时的电量变化
static volatile int _mnTheQuantityOfElectricityOld = -1;
static volatile boolean _mIsCharging = false;
// 保存当前实例,
// 便利封装 registerAction() 函数
GlobalApplicationReceiver mReceiver;
// ================================== 静态成员变量线程安全volatile保证多线程可见性=================================
private static volatile int sLastBatteryLevel = -1; // 历史电量0-100
private static volatile boolean sLastIsCharging = false; // 历史充电状态
// ================================== 成员变量区(按功能分层)=================================
private App mGlobalApplication;
private AppConfigUtils mAppConfigUtils;
private GlobalApplicationReceiver mCurrentReceiver;
// ================================== 构造方法(强化参数校验,初始化核心依赖)=================================
public GlobalApplicationReceiver(App globalApplication) {
mReceiver = this;
mGlobalApplication = globalApplication;
mAppConfigUtils = App.getAppConfigUtils(mGlobalApplication);
LogUtils.d(TAG, "构造接收器 | App=" + globalApplication);
if (globalApplication == null) {
LogUtils.e(TAG, "构造失败App实例为空");
throw new IllegalArgumentException("App cannot be null");
}
this.mCurrentReceiver = this;
this.mGlobalApplication = globalApplication;
this.mAppConfigUtils = App.getAppConfigUtils(mGlobalApplication);
LogUtils.d(TAG, "构造完成AppConfigUtils=" + mAppConfigUtils);
}
// ================================== 广播核心接收逻辑(入口方法,过滤电池状态广播)=================================
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
// 先设置好新电池状态标志
boolean isCharging = BatteryUtils.isCharging(intent);
if (_mIsCharging != isCharging) {
mAppConfigUtils.setIsCharging(isCharging);
LogUtils.d(TAG, "onReceive: 接收广播 | context=" + context + " | intent=" + intent + " | action=" + (intent != null ? intent.getAction() : "null"));
// 基础参数校验
if (context == null || intent == null || intent.getAction() == null) {
LogUtils.e(TAG, "onReceive: 参数无效,终止处理");
return;
}
// 仅处理电池状态变化广播
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
handleBatteryStateChanged(context, intent);
}
LogUtils.d(TAG, "onReceive: 广播处理完成");
}
// ================================== 业务逻辑方法(处理电池状态变化,同步配置+通知页面)=================================
/**
* 处理电池状态变化广播
* @param context 上下文
* @param intent 电池状态广播意图
*/
private void handleBatteryStateChanged(Context context, Intent intent) {
LogUtils.d(TAG, "handleBatteryStateChanged: 解析电池状态 | intent=" + intent);
// 1. 解析当前电池状态(复用工具类,二次校验电量范围)
boolean currentIsCharging = BatteryUtils.isCharging(intent);
int currentBatteryLevel = BatteryUtils.getCurrentBatteryLevel(intent);
currentBatteryLevel = Math.min(Math.max(currentBatteryLevel, BATTERY_LEVEL_MIN), BATTERY_LEVEL_MAX);
LogUtils.d(TAG, "handleBatteryStateChanged: 当前状态 | 充电=" + currentIsCharging + " | 电量=" + currentBatteryLevel + "%");
// 2. 状态无变化则跳过,减少无效运算
if (currentIsCharging == sLastIsCharging && currentBatteryLevel == sLastBatteryLevel) {
LogUtils.d(TAG, "handleBatteryStateChanged: 状态无变化,跳过处理");
return;
}
// 3. 同步最新状态到配置工具类
if (mAppConfigUtils != null) {
if (currentIsCharging != sLastIsCharging) {
mAppConfigUtils.setCharging(currentIsCharging);
LogUtils.d(TAG, "handleBatteryStateChanged: 同步充电状态 | " + currentIsCharging);
}
int nTheQuantityOfElectricity = BatteryUtils.getTheQuantityOfElectricity(intent);
if (_mnTheQuantityOfElectricityOld != nTheQuantityOfElectricity) {
mAppConfigUtils.setCurrentValue(nTheQuantityOfElectricity);
}
// 新电池状态标志某一个有变化就更新显示信息
if (_mIsCharging != isCharging || _mnTheQuantityOfElectricityOld != nTheQuantityOfElectricity) {
// 电池状态改变先取消旧的提醒消息
//NotificationHelper.cancelRemindNotification(context);
App.getAppCacheUtils(context).addChangingTime(nTheQuantityOfElectricity);
MainActivity.sendMsgCurrentValueBattery(nTheQuantityOfElectricity);
// 保存好新的电池状态标志
_mIsCharging = isCharging;
_mnTheQuantityOfElectricityOld = nTheQuantityOfElectricity;
if (currentBatteryLevel != sLastBatteryLevel) {
mAppConfigUtils.setCurrentBatteryValue(currentBatteryLevel);
LogUtils.d(TAG, "handleBatteryStateChanged: 同步电量 | " + currentBatteryLevel + "%");
}
} else {
LogUtils.e(TAG, "handleBatteryStateChanged: AppConfigUtils为空同步失败");
}
// 4. 执行状态变化后的业务逻辑
// 记录电量变化时间
if (App.getAppCacheUtils(context) != null) {
App.getAppCacheUtils(context).addChangingTime(currentBatteryLevel);
LogUtils.d(TAG, "handleBatteryStateChanged: 记录电量变化时间");
}
// 通知MainActivity更新电量
MainActivity.sendCurrentBatteryValueMessage(currentBatteryLevel);
LogUtils.d(TAG, "handleBatteryStateChanged: 发送电量更新消息到MainActivity");
// 5. 更新历史状态缓存
sLastIsCharging = currentIsCharging;
sLastBatteryLevel = currentBatteryLevel;
LogUtils.d(TAG, "handleBatteryStateChanged: 更新历史状态完成");
}
// ================================== 广播注册/注销(强化容错,避免重复操作)=================================
/**
* 注册广播接收器
*/
public void registerAction() {
LogUtils.d(TAG, "registerAction: 注册广播");
if (mGlobalApplication == null || mCurrentReceiver == null) {
LogUtils.e(TAG, "注册失败App或Receiver实例为空");
return;
}
try {
// 先注销再注册,避免重复注册异常
unregisterAction();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
mGlobalApplication.registerReceiver(mCurrentReceiver, filter);
LogUtils.d(TAG, "registerAction: 广播注册成功");
} catch (Exception e) {
LogUtils.e(TAG, "registerAction: 注册失败", e);
}
}
// 注册 Receiver
//
public void registerAction() {
IntentFilter filter=new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
mGlobalApplication.registerReceiver(mReceiver, filter);
/**
* 注销广播接收器
*/
public void unregisterAction() {
LogUtils.d(TAG, "unregisterAction: 注销广播");
if (mGlobalApplication == null || mCurrentReceiver == null) {
LogUtils.e(TAG, "注销失败App或Receiver实例为空");
return;
}
try {
mGlobalApplication.unregisterReceiver(mCurrentReceiver);
LogUtils.d(TAG, "unregisterAction: 广播注销成功");
} catch (IllegalArgumentException e) {
LogUtils.w(TAG, "unregisterAction: 广播未注册,跳过注销");
} catch (Exception e) {
LogUtils.e(TAG, "unregisterAction: 注销失败", e);
}
}
// ================================== 资源释放方法(主动释放,彻底避免内存泄漏)=================================
/**
* 释放接收器资源供App销毁时调用
*/
public void release() {
LogUtils.d(TAG, "release: 释放接收器资源");
// 注销广播
unregisterAction();
// 置空引用帮助GC回收
mGlobalApplication = null;
mAppConfigUtils = null;
mCurrentReceiver = null;
// 重置静态状态缓存
sLastBatteryLevel = -1;
sLastIsCharging = false;
LogUtils.d(TAG, "release: 资源释放完成");
}
}

View File

@@ -27,7 +27,7 @@ public class MainReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String szAction = intent.getAction();
if (szAction.equals(ACTION_BOOT_COMPLETED)) {
boolean isEnableService = App.getAppConfigUtils(context).getIsEnableService();
boolean isEnableService = App.getAppConfigUtils(context).isServiceEnabled();
if (isEnableService) {
if (ServiceUtils.isServiceAlive(context.getApplicationContext(), ControlCenterService.class.getName()) == false) {
LogUtils.d(TAG, "wakeupAndBindMain() Wakeup... ControlCenterService");

View File

@@ -5,101 +5,177 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.ServiceUtils;
/**
* 电池提醒核心服务进程守护类
* 功能:监听主服务 {@link ControlCenterService} 存活状态,异常断开时自动重启并绑定
* 适配Java7 | API30 | 前台服务启动规则 | 服务绑定稳定性保障
*/
public class AssistantService extends Service {
private final static String TAG = "AssistantService";
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
private static final String TAG = "AssistantService";
// 服务返回策略常量(统一定义,避免魔法值)
private static final int SERVICE_RETURN_STICKY = START_STICKY;
// 服务绑定标记常量
private static final int BIND_FLAG = Context.BIND_IMPORTANT;
//MyBinder mMyBinder;
MyServiceConnection mMyServiceConnection;
volatile boolean mIsThreadAlive;
AppConfigUtils mAppConfigUtils;
// ================================== 成员变量区按功能分层volatile保证多线程可见性=================================
private AppConfigUtils mAppConfigUtils;
private MyServiceConnection mMyServiceConnection;
private volatile boolean mIsThreadAlive;
@Override
public IBinder onBind(Intent intent) {
//return mMyBinder;
return null;
}
@Override
public void onCreate() {
//LogUtils.d(TAG, "onCreate");
super.onCreate();
mAppConfigUtils = App.getAppConfigUtils(this);
//mMyBinder = new MyBinder();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
// 设置运行参数
mIsThreadAlive = false;
run();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//LogUtils.d(TAG, "call onStartCommand(...)");
run();
return START_STICKY;
}
/*class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getServiceName() {
return AssistantService.class.getSimpleName();
}
}*/
@Override
public void onDestroy() {
//LogUtils.d(TAG, "onDestroy");
mIsThreadAlive = false;
super.onDestroy();
}
// 运行服务内容
//
void run() {
//LogUtils.d(TAG, "run");
if (mAppConfigUtils.getIsEnableService()) {
if (mIsThreadAlive == false) {
// 设置运行状态
mIsThreadAlive = true;
// 唤醒和绑定主进程
wakeupAndBindMain();
}
}
}
// 唤醒和绑定主进程
//
void wakeupAndBindMain() {
if (ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName()) == false) {
//LogUtils.d(TAG, "wakeupAndBindMain() Wakeup... ControlCenterService");
startForegroundService(new Intent(AssistantService.this, ControlCenterService.class));
}
//LogUtils.d(TAG, "wakeupAndBindMain() Bind... ControlCenterService");
bindService(new Intent(AssistantService.this, ControlCenterService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
// 主进程与守护进程连接时需要用到此类
//
class MyServiceConnection implements ServiceConnection {
// ================================== 内部类(服务连接状态监听,前置定义便于引用)=================================
/**
* 服务连接状态监听器
* 主服务连接成功时记录状态,断开时自动重连
*/
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
LogUtils.d(TAG, "onServiceConnected: 主服务连接成功 | 组件名=" + name.getClassName() + " | Binder=" + service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//LogUtils.d(TAG, "call onServiceDisconnected(...)");
if (mAppConfigUtils.getIsEnableService()) {
LogUtils.d(TAG, "onServiceDisconnected: 主服务连接断开 | 组件名=" + name.getClassName());
// 主服务断开且配置启用时,重新唤醒绑定
if (mAppConfigUtils != null && mAppConfigUtils.isServiceEnabled()) {
LogUtils.d(TAG, "onServiceDisconnected: 配置启用,尝试重新唤醒并绑定主服务");
wakeupAndBindMain();
}
}
}
// ================================== 服务生命周期方法按执行顺序排列onCreate→onStartCommand→onBind→onDestroy=================================
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate: 守护服务启动 | 进程ID=" + android.os.Process.myPid());
// 初始化配置工具类,添加空指针防护
mAppConfigUtils = App.getAppConfigUtils(this);
if (mAppConfigUtils == null) {
LogUtils.e(TAG, "onCreate: AppConfigUtils初始化失败守护服务无法工作");
stopSelf();
return;
}
// 初始化服务连接对象
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
LogUtils.d(TAG, "onCreate: ServiceConnection初始化完成");
}
// 初始化运行状态,执行核心守护逻辑
mIsThreadAlive = false;
run();
LogUtils.d(TAG, "onCreate: 守护服务初始化完成 | 服务启用状态=" + mAppConfigUtils.isServiceEnabled());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand: 守护服务触发重启 | flags=" + flags + " | startId=" + startId);
// 配置工具类为空时,直接返回非粘性策略
if (mAppConfigUtils == null) {
LogUtils.e(TAG, "onStartCommand: AppConfigUtils未初始化终止服务");
stopSelf();
return START_NOT_STICKY;
}
run();
int returnFlag = mAppConfigUtils.isServiceEnabled() ? SERVICE_RETURN_STICKY : super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand: 处理完成 | 返回策略=" + (returnFlag == SERVICE_RETURN_STICKY ? "START_STICKY" : "DEFAULT"));
return returnFlag;
}
@Override
public IBinder onBind(Intent intent) {
LogUtils.d(TAG, "onBind: 服务绑定请求 | intent=" + intent);
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 守护服务销毁流程启动");
// 重置运行状态,终止守护逻辑
mIsThreadAlive = false;
// 解绑主服务,添加异常捕获防止重复解绑崩溃
unbindMainService();
// 置空工具类引用帮助GC回收
mAppConfigUtils = null;
LogUtils.d(TAG, "onDestroy: 守护服务销毁完成");
}
// ================================== 核心业务逻辑(守护主服务存活)=================================
/**
* 执行守护逻辑:检查主服务状态,按需唤醒并绑定
* 前置条件mAppConfigUtils 必须初始化完成
*/
private void run() {
LogUtils.d(TAG, "run: 执行守护逻辑 | 配置启用=" + mAppConfigUtils.isServiceEnabled() + " | 线程存活=" + mIsThreadAlive);
if (mAppConfigUtils.isServiceEnabled()) {
if (!mIsThreadAlive) {
mIsThreadAlive = true;
wakeupAndBindMain();
}
} else {
LogUtils.d(TAG, "run: 服务未启用,跳过守护逻辑");
// 服务未启用时,重置线程状态
mIsThreadAlive = false;
}
}
/**
* 唤醒主服务并建立绑定,确保主服务持续运行
* 适配 API26+ 前台服务启动规则,避免系统限制导致启动失败
*/
private void wakeupAndBindMain() {
// 检查主服务存活状态
boolean isMainServiceAlive = ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName());
LogUtils.d(TAG, "wakeupAndBindMain: 主服务存活状态=" + isMainServiceAlive);
// 主服务未存活时按需启动区分API版本
if (!isMainServiceAlive) {
Intent mainServiceIntent = new Intent(AssistantService.this, ControlCenterService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(mainServiceIntent);
LogUtils.d(TAG, "wakeupAndBindMain: API26+ 以前台服务方式启动主服务");
} else {
startService(mainServiceIntent);
LogUtils.d(TAG, "wakeupAndBindMain: 以普通服务方式启动主服务");
}
}
// 绑定主服务,监听连接状态,添加结果日志
Intent bindIntent = new Intent(AssistantService.this, ControlCenterService.class);
boolean bindResult = bindService(bindIntent, mMyServiceConnection, BIND_FLAG);
LogUtils.d(TAG, "wakeupAndBindMain: 绑定主服务结果=" + bindResult + " | 绑定标记=BIND_IMPORTANT");
}
// ================================== 辅助工具方法(拆分独立逻辑,提高可维护性)=================================
/**
* 解绑主服务,包含异常捕获与状态日志
*/
private void unbindMainService() {
if (mMyServiceConnection != null) {
try {
unbindService(mMyServiceConnection);
LogUtils.d(TAG, "unbindMainService: 已成功解绑ControlCenterService");
} catch (IllegalArgumentException e) {
LogUtils.w(TAG, "unbindMainService: 解绑服务失败,服务未绑定 | " + e.getMessage());
}
mMyServiceConnection = null;
}
}
}

View File

@@ -1,314 +1,495 @@
package cc.winboll.studio.powerbell.services;
/*
* PowerBy : ZhanGSKen(ZhangShaojian2018@163.com)
* 参考:
* 进程保活-双进程守护的正确姿势
* https://blog.csdn.net/sinat_35159441/article/details/75267380
* Android Service之onStartCommand方法研究
* https://blog.csdn.net/cyp331203/article/details/38920491
*/
import android.app.Notification;
import android.app.ActivityManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.widget.RemoteViews;
import android.os.PowerManager;
import android.provider.Settings;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.handlers.ControlCenterServiceHandler;
import cc.winboll.studio.powerbell.models.AppConfigBean;
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
import cc.winboll.studio.powerbell.models.NotificationMessage;
import cc.winboll.studio.powerbell.receivers.ControlCenterServiceReceiver;
import cc.winboll.studio.powerbell.services.AssistantService;
import cc.winboll.studio.powerbell.threads.RemindThread;
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
import cc.winboll.studio.powerbell.utils.ServiceUtils;
import cc.winboll.studio.powerbell.utils.StringUtils;
import java.util.List;
/**
* 电池提醒核心服务
* 功能:管理前台服务生命周期、控制提醒线程启停、处理配置更新
* 适配Java7 | API30 | 前台服务超时防护 | 电池优化忽略引导
*/
public class ControlCenterService extends Service {
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
public static final String TAG = "ControlCenterService";
private static final long THREAD_STOP_TIMEOUT = 1000L;
private static final int SERVICE_RETURN_STICKY = START_STICKY;
private static final int DEFAULT_CHARGE_REMINDER_VALUE = 80;
private static final int DEFAULT_USAGE_REMINDER_VALUE = 20;
private static final int DEFAULT_BATTERY_DETECT_INTERVAL = 1000;
private static final int RUNNING_SERVICE_LIST_LIMIT = 100;
public static final int MSG_UPDATE_STATUS = 0;
// ================================== 静态状态标记volatile保证多线程可见性=================================
private static volatile boolean isServiceRunning = false;
private static volatile boolean mIsDestroyed = true;
static ControlCenterService _mControlCenterService;
volatile boolean isServiceRunning;
AppConfigUtils mAppConfigUtils;
AppCacheUtils mAppCacheUtils;
// 前台服务通知工具
NotificationManagerUtils mNotificationManagerUtils;
Notification notification;
RemindThread mRemindThread;
ControlCenterServiceHandler mControlCenterServiceHandler;
MyServiceConnection mMyServiceConnection;
ControlCenterServiceReceiver mControlCenterServiceReceiver;
ControlCenterServiceReceiver mControlCenterServiceReceiverLocalBroadcast;
@Override
public IBinder onBind(Intent intent) {
return null;
}
public RemindThread getRemindThread() {
return mRemindThread;
}
// ================================== 成员变量区(按功能分层:配置→核心组件→通知相关)=================================
// 服务控制配置
private ControlCenterServiceBean mServiceControlBean;
private AppConfigBean mCurrentConfigBean;
// 业务核心组件
private ControlCenterServiceHandler mServiceHandler;
private ControlCenterServiceReceiver mControlCenterServiceReceiver;
// 通知相关
private NotificationManagerUtils mNotificationManager;
private NotificationMessage mForegroundNotifyMsg;
// ================================== 服务生命周期方法按执行顺序onCreate→onStartCommand→onBind→onDestroy=================================
@Override
public void onCreate() {
super.onCreate();
_mControlCenterService = ControlCenterService.this;
isServiceRunning = false;
mAppConfigUtils = App.getAppConfigUtils(this);
mAppCacheUtils = App.getAppCacheUtils(this);
mNotificationManagerUtils = new NotificationManagerUtils(ControlCenterService.this);
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
mControlCenterServiceHandler = new ControlCenterServiceHandler(this);
// 运行服务内容
run();
LogUtils.d(TAG, "onCreate执行 | 线程=" + Thread.currentThread().getName() + " | 进程ID=" + android.os.Process.myPid());
runCoreServiceLogic();
LogUtils.d(TAG, "onCreate完成 | 前台状态=" + isServiceRunning + " | 服务启用=" + (mServiceControlBean != null && mServiceControlBean.isEnableService()));
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 运行服务内容
run();
return (mAppConfigUtils.getIsEnableService()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand执行 | startId=" + startId + " | action=" + (intent != null ? intent.getAction() : "null"));
loadLatestServiceControlConfig();
runCoreServiceLogic();
int returnFlag = (mServiceControlBean != null && mServiceControlBean.isEnableService())
? SERVICE_RETURN_STICKY
: super.onStartCommand(intent, flags, startId);
LogUtils.d(TAG, "onStartCommand完成 | 返回策略=" + (returnFlag == SERVICE_RETURN_STICKY ? "START_STICKY" : "DEFAULT"));
return returnFlag;
}
// 运行服务内容
//
void run() {
if (mAppConfigUtils.getIsEnableService() && isServiceRunning == false) {
LogUtils.d(TAG, "run");
isServiceRunning = true;
// 唤醒守护进程
wakeupAndBindAssistant();
// 显示前台通知栏
// 在Service中
NotificationManagerUtils notificationManagerUtils = new NotificationManagerUtils(this);
//Intent intent = new Intent(this, MainActivity.class);
notificationManagerUtils.startForegroundServiceNotify(ControlCenterService.this, new NotificationMessage(getString(R.string.app_name), "Service Running, Click to open app"));
//startForeground(NotificationHelper.FOREGROUND_NOTIFICATION_ID, notification);
// NotificationMessage notificationMessage=createNotificationMessage();
// //Toast.makeText(getApplication(), "", Toast.LENGTH_SHORT).show();
// mNotificationUtils.createForegroundNotification(this, notificationMessage);
// mNotificationUtils.createRemindNotification(this, notificationMessage);
if (mControlCenterServiceReceiver == null) {
// 注册广播接收器
mControlCenterServiceReceiver = new ControlCenterServiceReceiver(this);
mControlCenterServiceReceiver.registerAction(this);
}
new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
@Override
public void run() {
startRemindThread(mAppConfigUtils.mAppConfigBean);
ToastUtils.show("Service Is Start.");
LogUtils.i(TAG, "Service Is Start.");
}
}, 2000);
}
}
String getValuesString() {
String szReturn = "Usege: ";
szReturn += mAppConfigUtils.getIsEnableUsegeReminder() ? Integer.toString(mAppConfigUtils.getUsegeReminderValue()) : "?";
szReturn += "% Charge: ";
szReturn += mAppConfigUtils.getIsEnableChargeReminder() ? Integer.toString(mAppConfigUtils.getChargeReminderValue()) : "?";
szReturn += "%\nCurrent: " + Integer.toString(mAppConfigUtils.getCurrentValue()) + "%";
return szReturn;
}
NotificationMessage createNotificationMessage() {
String szTitle = ((App)getApplication()).getString(R.string.app_name);
String szContent = getValuesString() + " {?} " + StringUtils.formatPCMListString(mAppCacheUtils.getArrayListBatteryInfo());
return new NotificationMessage(szTitle, szContent);
}
// 更新前台通知
//
public void updateServiceNotification() {
//mNotificationUtils.updateForegroundNotification(ControlCenterService.this, createNotificationMessage());
}
// 更新前台通知
//
public void updateServiceNotification(NotificationMessage notificationMessage) {
//mNotificationUtils.updateForegroundNotification(ControlCenterService.this, notificationMessage);
}
// 更新前台通知
//
public void updateRemindNotification(NotificationMessage notificationMessage) {
//mNotificationUtils.updateRemindNotification(ControlCenterService.this, notificationMessage);
}
// 唤醒和绑定守护进程
//
void wakeupAndBindAssistant() {
if (ServiceUtils.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) {
startService(new Intent(ControlCenterService.this, AssistantService.class));
//LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService");
bindService(new Intent(ControlCenterService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
}
// 开启提醒铃声线程
//
public void startRemindThread(AppConfigBean appConfigBean) {
//LogUtils.d(TAG, "startRemindThread");
if (mRemindThread == null) {
mRemindThread = new RemindThread(this, mControlCenterServiceHandler);
} else {
if (mRemindThread.isExist() == true) {
mRemindThread = new RemindThread(this, mControlCenterServiceHandler);
} else {
// 提醒进程正在进行中就更新状态后退出
mRemindThread.setChargeReminderValue(appConfigBean.getChargeReminderValue());
mRemindThread.setUsegeReminderValue(appConfigBean.getUsegeReminderValue());
mRemindThread.setIsEnableChargeReminder(appConfigBean.isEnableChargeReminder());
mRemindThread.setIsEnableUsegeReminder(appConfigBean.isEnableUsegeReminder());
mRemindThread.setSleepTime(appConfigBean.getReminderIntervalTime());
mRemindThread.setIsCharging(appConfigBean.isCharging());
mRemindThread.setQuantityOfElectricity(appConfigBean.getCurrentValue());
//LogUtils.d(TAG, "mRemindThread update.");
return;
}
}
mRemindThread.setChargeReminderValue(appConfigBean.getChargeReminderValue());
mRemindThread.setUsegeReminderValue(appConfigBean.getUsegeReminderValue());
mRemindThread.setSleepTime(appConfigBean.getReminderIntervalTime());
mRemindThread.setIsCharging(appConfigBean.isCharging());
mRemindThread.setQuantityOfElectricity(appConfigBean.getCurrentValue());
mRemindThread.setIsEnableChargeReminder(appConfigBean.isEnableChargeReminder());
mRemindThread.setIsEnableUsegeReminder(appConfigBean.isEnableUsegeReminder());
mRemindThread.start();
//LogUtils.d(TAG, "mRemindThread.start()");
}
public void stopRemindThread() {
if (mRemindThread != null) {
mRemindThread.setIsExist(true);
mRemindThread = null;
}
@Override
public IBinder onBind(Intent intent) {
LogUtils.d(TAG, "onBind执行 | intent=" + intent);
return null;
}
@Override
public void onDestroy() {
//LogUtils.d(TAG, "onDestroy");
mAppConfigUtils.loadAppConfigBean();
if (mAppConfigUtils.getIsEnableService() == false) {
// 设置运行状态
isServiceRunning = false;
// 停止守护进程
Intent intent = new Intent(this, AssistantService.class);
stopService(intent);
// 停止Receiver
if (mControlCenterServiceReceiver != null) {
unregisterReceiver(mControlCenterServiceReceiver);
mControlCenterServiceReceiver = null;
LogUtils.d(TAG, "onDestroy执行服务销毁流程启动");
super.onDestroy();
// 资源释放顺序:前台服务 → 线程 → 广播接收器 → Handler → 通知 → 引用(避免内存泄漏)
stopForegroundService();
RemindThread.stopRemindThread();
releaseBroadcastReceiver();
destroyHandler();
releaseNotificationResource();
clearAllReferences();
// 状态重置
mCurrentConfigBean = null;
mForegroundNotifyMsg = null;
mServiceHandler = null;
isServiceRunning = false;
mIsDestroyed = true;
LogUtils.d(TAG, "onDestroy完成服务销毁完成");
}
// ================================== 核心业务逻辑(独立抽取,统一调用)=================================
/**
* 服务核心运行逻辑在onCreate/onStartCommand复用
* 避免重复初始化,保证前台服务优先启动
*/
private synchronized void runCoreServiceLogic() {
LogUtils.d(TAG, "runCoreServiceLogic执行");
loadLatestServiceControlConfig();
boolean serviceEnabled = mServiceControlBean != null && mServiceControlBean.isEnableService();
LogUtils.d(TAG, "runCoreServiceLogic服务启用=" + serviceEnabled + " | 已运行=" + isServiceRunning + " | 已销毁=" + mIsDestroyed);
if (serviceEnabled && !isServiceRunning) {
isServiceRunning = true;
mIsDestroyed = false;
if (initForegroundNotificationImmediately()) {
loadDefaultConfig();
initServiceBusinessLogic();
LogUtils.d(TAG, "runCoreServiceLogic核心组件初始化成功");
} else {
LogUtils.e(TAG, "runCoreServiceLogic前台通知初始化失败终止业务");
stopForegroundService();
isServiceRunning = false;
}
// 停止前台通知栏
} else {
LogUtils.d(TAG, "runCoreServiceLogic无需执行核心逻辑");
}
}
// ================================== 前台通知管理优先执行防止API26+前台服务5秒超时=================================
/**
* 立即初始化前台通知防止API26+前台服务超时异常
* @return true=成功 false=失败
*/
private boolean initForegroundNotificationImmediately() {
LogUtils.d(TAG, "initForegroundNotificationImmediately执行");
try {
if (mNotificationManager == null) {
mNotificationManager = new NotificationManagerUtils(this);
LogUtils.d(TAG, "initForegroundNotificationImmediately通知工具类初始化完成");
}
if (mForegroundNotifyMsg == null) {
mForegroundNotifyMsg = new NotificationMessage();
mForegroundNotifyMsg.setTitle("电池监测服务");
mForegroundNotifyMsg.setContent("后台运行中");
mForegroundNotifyMsg.setRemindMSG("service_running");
LogUtils.d(TAG, "initForegroundNotificationImmediately通知消息构建完成");
}
mNotificationManager.startForegroundServiceNotify(this, mForegroundNotifyMsg);
ToastUtils.show("电池监测服务已启动");
LogUtils.d(TAG, "initForegroundNotificationImmediately前台通知发送成功 | ID=" + NotificationManagerUtils.NOTIFY_ID_FOREGROUND_SERVICE);
return true;
} catch (Exception e) {
LogUtils.e(TAG, "initForegroundNotificationImmediately通知初始化异常", e);
return false;
}
}
/**
* 停止前台服务并取消通知
*/
private void stopForegroundService() {
LogUtils.d(TAG, "stopForegroundService执行");
try {
stopForeground(true);
// 停止消息提醒进程
stopRemindThread();
super.onDestroy();
//LogUtils.d(TAG, "onDestroy done");
LogUtils.d(TAG, "stopForegroundService前台服务已停止通知已取消");
} catch (Exception e) {
LogUtils.e(TAG, "stopForegroundService停止异常", e);
}
}
// 主进程与守护进程连接时需要用到此类
//
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
}
@Override
public void onServiceDisconnected(ComponentName name) {
//LogUtils.d(TAG, "call onServiceConnected(...)");
if (mAppConfigUtils.getIsEnableService()) {
// 唤醒守护进程
wakeupAndBindAssistant();
}
// ================================== 配置管理(本地持久化+内存同步)=================================
/**
* 加载本地最新服务控制配置
*/
private void loadLatestServiceControlConfig() {
LogUtils.d(TAG, "loadLatestServiceControlConfig执行");
ControlCenterServiceBean latestBean = ControlCenterServiceBean.loadBean(this, ControlCenterServiceBean.class);
if (latestBean != null) {
mServiceControlBean = latestBean;
LogUtils.d(TAG, "loadLatestServiceControlConfig配置读取成功 | 启用=" + mServiceControlBean.isEnableService());
} else {
LogUtils.w(TAG, "loadLatestServiceControlConfig本地无配置沿用内存配置");
}
}
public void appenRemindMSG(String szRemindMSG) {
String msg = "";
for (int i = 0; i < 20; i++) {
msg += szRemindMSG;
/**
* 加载默认业务配置(首次启动兜底)
*/
private void loadDefaultConfig() {
LogUtils.d(TAG, "loadDefaultConfig执行");
if (mCurrentConfigBean == null) {
mCurrentConfigBean = new AppConfigBean();
mCurrentConfigBean.setEnableChargeReminder(true);
mCurrentConfigBean.setChargeReminderValue(DEFAULT_CHARGE_REMINDER_VALUE);
mCurrentConfigBean.setEnableUsageReminder(true);
mCurrentConfigBean.setUsageReminderValue(DEFAULT_USAGE_REMINDER_VALUE);
mCurrentConfigBean.setBatteryDetectInterval(DEFAULT_BATTERY_DETECT_INTERVAL);
LogUtils.d(TAG, "loadDefaultConfig默认配置加载完成 | 充电阈值=" + DEFAULT_CHARGE_REMINDER_VALUE + " | 耗电阈值=" + DEFAULT_USAGE_REMINDER_VALUE + " | 检测间隔=" + DEFAULT_BATTERY_DETECT_INTERVAL + "ms");
} else {
LogUtils.d(TAG, "loadDefaultConfig内存已有配置无需加载");
}
NotificationManagerUtils notificationManagerUtils = new NotificationManagerUtils(ControlCenterService.this);
Intent intent = new Intent(ControlCenterService.this, MainActivity.class);
notificationManagerUtils.showTempAlertNotify(getString(R.string.app_name), msg);
// NotificationMessage notificationMessage = createNotificationMessage();
// notificationMessage.setRemindMSG(szRemindMSG);
// //LogUtils.d(TAG, "notificationMessage : " + notificationMessage.getRemindMSG());
// updateRemindNotification(notificationMessage);
}
// 设置颜色背景
public static RemoteViews setLinearLayoutColor(RemoteViews remoteViews, int viewId, int color) {
remoteViews.setInt(viewId, "setBackgroundColor", color);
return remoteViews;
// ================================== 业务组件初始化与销毁Handler/广播/线程等)=================================
/**
* 初始化Handler等核心业务组件
*/
private void initServiceBusinessLogic() {
LogUtils.d(TAG, "initServiceBusinessLogic执行");
// 初始化Handler
if (mServiceHandler == null) {
mServiceHandler = new ControlCenterServiceHandler(this);
LogUtils.d(TAG, "initServiceBusinessLogicHandler初始化完成");
} else {
LogUtils.d(TAG, "initServiceBusinessLogicHandler已存在");
}
// 初始化广播接收器
if (mControlCenterServiceReceiver == null) {
mControlCenterServiceReceiver = new ControlCenterServiceReceiver(this);
mControlCenterServiceReceiver.registerAction(this);
LogUtils.d(TAG, "initServiceBusinessLogic广播接收器初始化并注册完成 | 接收器=" + mControlCenterServiceReceiver);
} else {
LogUtils.d(TAG, "initServiceBusinessLogic广播接收器已存在");
}
}
// 设置Drawable背景
public static RemoteViews setLinearLayoutDrawable(RemoteViews remoteViews, int viewId, int drawableRes) {
remoteViews.setInt(viewId, "setBackgroundResource", drawableRes);
return remoteViews;
/**
* 释放广播接收器资源
*/
private void releaseBroadcastReceiver() {
LogUtils.d(TAG, "releaseBroadcastReceiver执行");
if (mControlCenterServiceReceiver != null) {
mControlCenterServiceReceiver.release();
mControlCenterServiceReceiver = null;
LogUtils.d(TAG, "releaseBroadcastReceiver广播接收器已释放");
} else {
LogUtils.w(TAG, "releaseBroadcastReceiver广播接收器实例为空");
}
}
//
// 启动服务
//
/**
* 销毁Handler移除所有消息和回调防止内存泄漏
*/
private void destroyHandler() {
LogUtils.d(TAG, "destroyHandler执行");
if (mServiceHandler != null) {
mServiceHandler.removeCallbacksAndMessages(null);
mServiceHandler = null;
LogUtils.d(TAG, "destroyHandlerHandler已销毁");
} else {
LogUtils.w(TAG, "destroyHandlerHandler实例为空");
}
}
/**
* 释放通知工具类资源
*/
private void releaseNotificationResource() {
LogUtils.d(TAG, "releaseNotificationResource执行");
if (mNotificationManager != null) {
mNotificationManager.release();
mNotificationManager = null;
LogUtils.d(TAG, "releaseNotificationResource通知资源已释放");
} else {
LogUtils.w(TAG, "releaseNotificationResource通知工具类实例为空");
}
}
/**
* 置空所有引用,防止内存泄漏
*/
private void clearAllReferences() {
LogUtils.d(TAG, "clearAllReferences执行");
mForegroundNotifyMsg = null;
mServiceControlBean = null;
LogUtils.d(TAG, "clearAllReferences引用清理完成");
}
// ================================== 外部调用接口(静态方法,提供服务启停/配置更新入口)=================================
/**
* 外部启动服务的统一入口
* @param context 上下文
*/
public static void startControlCenterService(Context context) {
LogUtils.d(TAG, "startControlCenterService执行 | context=" + context);
if (context == null) {
LogUtils.e(TAG, "startControlCenterServiceContext为空启动失败");
return;
}
// 保存启用配置
ControlCenterServiceBean controlBean = new ControlCenterServiceBean(true);
ControlCenterServiceBean.saveBean(context, controlBean);
LogUtils.d(TAG, "startControlCenterService服务启用配置已保存 | 配置=" + controlBean);
// 启动服务区分API版本
Intent intent = new Intent(context, ControlCenterService.class);
context.startForegroundService(intent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
LogUtils.d(TAG, "startControlCenterService以前台服务方式启动API26+");
} else {
context.startService(intent);
LogUtils.d(TAG, "startControlCenterService以普通服务方式启动API26-");
}
}
//
// 停止服务
//
/**
* 外部停止服务的统一入口
* @param context 上下文
*/
public static void stopControlCenterService(Context context) {
LogUtils.d(TAG, "stopControlCenterService执行 | context=" + context);
if (context == null) {
LogUtils.e(TAG, "stopControlCenterServiceContext为空停止失败");
return;
}
// 保存停用配置
ControlCenterServiceBean controlBean = new ControlCenterServiceBean(false);
ControlCenterServiceBean.saveBean(context, controlBean);
LogUtils.d(TAG, "stopControlCenterService服务停用配置已保存 | 配置=" + controlBean);
// 停止服务
Intent intent = new Intent(context, ControlCenterService.class);
context.stopService(intent);
LogUtils.d(TAG, "stopControlCenterService停止指令已发送");
}
public static void updateStatus(Context context, AppConfigBean appConfigBean) {
//LogUtils.d(TAG, "updateStatus");
// 创建一个Intent实例定义广播的内容
Intent intent = new Intent(ControlCenterServiceReceiver.ACTION_START_REMINDTHREAD);
// 设置可选的Action数据如额外信息
intent.putExtra("appConfigBean", appConfigBean);
// 发送广播
context.sendBroadcast(intent);
/**
* 外部更新配置并触发线程重启
* @param context 上下文
*/
public static void sendAppConfigStatusUpdateMessage(Context context) {
LogUtils.d(TAG, "sendAppConfigStatusUpdateMessage执行 | context=" + context);
if (context == null) {
LogUtils.e(TAG, "sendAppConfigStatusUpdateMessage参数为空更新失败");
return;
}
Intent intent = new Intent(ControlCenterServiceReceiver.ACTION_APPCONFIG_CHANGED);
intent.setPackage(context.getPackageName());
// 新增:发送广播并记录结果
context.sendBroadcast(intent);
LogUtils.d(TAG, "sendAppConfigStatusUpdateMessage配置更新广播发送 action=" + ControlCenterServiceReceiver.ACTION_APPCONFIG_CHANGED);
}
/**
* 检查并引导用户开启忽略电池优化API23+
* @param context 上下文
*/
public static void checkIgnoreBatteryOptimization(Context context) {
LogUtils.d(TAG, "checkIgnoreBatteryOptimization执行 | context=" + context);
if (context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
LogUtils.w(TAG, "checkIgnoreBatteryOptimization无需检查Context为空或API<23");
return;
}
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (powerManager == null) {
LogUtils.e(TAG, "checkIgnoreBatteryOptimizationPowerManager获取失败");
return;
}
String packageName = context.getPackageName();
boolean isIgnored = powerManager.isIgnoringBatteryOptimizations(packageName);
LogUtils.d(TAG, "checkIgnoreBatteryOptimization已忽略电池优化=" + isIgnored);
if (!isIgnored) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
LogUtils.d(TAG, "checkIgnoreBatteryOptimization已跳转至系统设置页 | package=" + packageName);
}
}
/**
* 检查服务是否运行适配API30+
* @param context 上下文
* @param serviceClass 服务类
* @return true=运行中 false=未运行
*/
private static boolean isServiceRunning(Context context, Class<?> serviceClass) {
LogUtils.d(TAG, "isServiceRunning执行 | context=" + context + " | service=" + (serviceClass != null ? serviceClass.getName() : "null"));
if (context == null || serviceClass == null) {
LogUtils.e(TAG, "isServiceRunning参数为空");
return false;
}
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) {
LogUtils.e(TAG, "isServiceRunningActivityManager获取失败");
return false;
}
boolean isRunning = false;
String packageName = context.getPackageName();
String serviceClassName = serviceClass.getName();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// API30+ 禁止获取其他应用服务,通过进程状态判断
List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
if (processes != null) {
for (ActivityManager.RunningAppProcessInfo process : processes) {
if (packageName.equals(process.processName) &&
(process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE ||
process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)) {
isRunning = true;
break;
}
}
}
LogUtils.d(TAG, "isServiceRunningAPI30+ 判断结果=" + isRunning);
} else {
// API30- 通过服务列表判断
List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(RUNNING_SERVICE_LIST_LIMIT);
if (services != null) {
for (ActivityManager.RunningServiceInfo info : services) {
if (serviceClassName.equals(info.service.getClassName())) {
isRunning = true;
break;
}
}
}
LogUtils.d(TAG, "isServiceRunningAPI30- 判断结果=" + isRunning);
}
// 兜底判断:配置启用状态
if (!isRunning) {
isRunning = isServiceStarted(context, serviceClass);
LogUtils.d(TAG, "isServiceRunning兜底判断结果=" + isRunning);
}
return isRunning;
}
/**
* 兜底判断服务是否已启动(通过配置文件)
*/
private static boolean isServiceStarted(Context context, Class<?> serviceClass) {
LogUtils.d(TAG, "isServiceStarted执行");
try {
ControlCenterServiceBean controlBean = ControlCenterServiceBean.loadBean(context, ControlCenterServiceBean.class);
return controlBean != null && controlBean.isEnableService();
} catch (Exception e) {
LogUtils.e(TAG, "isServiceStarted兜底判断异常", e);
return false;
}
}
// ================================== 业务方法(配置更新/电池状态回调)=================================
/**
* 接收外部配置更新,同步到提醒线程
* @param latestConfig 最新配置
*/
public void notifyAppConfigUpdate(AppConfigBean latestConfig) {
LogUtils.d(TAG, "notifyAppConfigUpdate执行 | 充电阈值=" + (latestConfig != null ? latestConfig.getChargeReminderValue() : null) + " | 耗电阈值=" + (latestConfig != null ? latestConfig.getUsageReminderValue() : null));
if (latestConfig != null && mServiceHandler != null) {
mCurrentConfigBean = latestConfig;
RemindThread.startRemindThreadWithAppConfig(this, mServiceHandler, latestConfig);
LogUtils.d(TAG, "notifyAppConfigUpdate配置已同步到提醒线程");
} else {
LogUtils.e(TAG, "notifyAppConfigUpdate参数为空同步失败 | latestConfig=" + latestConfig + " | mServiceHandler=" + mServiceHandler);
}
}
// ================================== Getter 方法按需开放避免冗余Setter=================================
public ControlCenterServiceBean getServiceControlBean() {
return mServiceControlBean;
}
public NotificationManagerUtils getNotificationManager() {
return mNotificationManager;
}
public NotificationMessage getForegroundNotifyMsg() {
return mForegroundNotifyMsg;
}
public AppConfigBean getCurrentConfigBean() {
return mCurrentConfigBean;
}
public boolean isDestroyed() {
return mIsDestroyed;
}
}

View File

@@ -4,36 +4,368 @@ import android.content.Context;
import android.os.Message;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.handlers.ControlCenterServiceHandler;
import cc.winboll.studio.powerbell.models.AppConfigBean;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* 提醒线程(多实例列表管理)
* 功能:管理充电/耗电提醒逻辑触发条件时向Handler发送提醒消息
* 适配Java7 | API30 | 内存泄漏防护 | 多线程状态同步
* 对外接口:{@link #startRemindThreadWithAppConfig(Context, ControlCenterServiceHandler, AppConfigBean)}、{
* @link #startRemindThreadWithBatteryInfo(Context, ControlCenterServiceHandler, boolean, int)}、{@link #stopRemindThread()}
*/
public class RemindThread extends Thread {
public static final String TAG = RemindThread.class.getSimpleName();
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
public static final String TAG = "RemindThread";
Context mContext;
// 控制线程是否退出的标志
volatile boolean isExist = false;
// 消息提醒开关
static volatile boolean isReminding = false;
// 充电提醒开关
static volatile boolean isEnableUsegeReminder = false;
// 耗电提醒开关
static volatile boolean isEnableChargeReminder = false;
// 电量比较停顿时间
static volatile int sleepTime = 1000;
// 充电提醒电量
static volatile int chargeReminderValue = -1;
// 耗电提醒电量
static volatile int usegeReminderValue = -1;
// 当前电量
static volatile int quantityOfElectricity = -1;
// 是否正在充电
static volatile boolean isCharging = false;
// 服务Handler, 用于线程发送消息使用
WeakReference<ControlCenterServiceHandler> mwrControlCenterServiceHandler;
// 时间常量 (ms)
private static final int MIN_SLEEP_TIME = 2000;
private static final long THREAD_JOIN_TIMEOUT = 1000L;
// 状态常量
private static final int INVALID_BATTERY_VALUE = -1;
private static final int BATTERY_LEVEL_MIN = 0;
private static final int BATTERY_LEVEL_MAX = 100;
// 提醒类型常量
private static final String REMIND_TYPE_CHARGE = "+";
private static final String REMIND_TYPE_USAGE = "-";
// ================================== 静态成员(多实例列表管理)=================================
private static volatile ArrayList<RemindThread> sRemindThreadList;
// ================================== 成员变量区按功能分层volatile保证多线程可见性=================================
// 并发安全锁(保护线程状态变更)
private final Object mRemindLock = new Object();
// 弱引用依赖防内存泄漏ApplicationContext 避免 Activity 引用)
private Context mContext;
private WeakReference<ControlCenterServiceHandler> mwrControlCenterServiceHandler;
// 线程状态标记volatile 确保多线程可见)
private volatile boolean isReminding;
public volatile boolean isExist;
// 业务配置参数volatile 确保配置变更实时生效)
private volatile boolean isEnableChargeReminder;
private volatile boolean isEnableUsageReminder;
private volatile long sleepTime;
private volatile int chargeReminderValue;
private volatile int usageReminderValue;
private volatile int quantityOfElectricity;
private volatile boolean isCharging;
// ================================== 私有构造器(禁止外部实例化)=================================
private RemindThread(Context context, ControlCenterServiceHandler handler) {
LogUtils.d(TAG, "构造器调用 | context=" + context + " | handler=" + handler);
this.mContext = context.getApplicationContext();
this.mwrControlCenterServiceHandler = new WeakReference<>(handler);
resetThreadStateInternal();
LogUtils.d(TAG, "构造完成 | threadId=" + getId() + " | 初始状态重置成功");
}
// ================================== 对外公开静态接口(多实例列表管理)=================================
/**
* 启动提醒线程,同步最新配置
* 逻辑:停止所有旧线程 → 创建新线程 → 加入列表管理
* @param context 上下文(非空)
* @param handler 服务处理器(非空)
* @param config 应用配置Bean非空
* @return true: 启动成功false: 入参非法
*/
public static boolean startRemindThreadWithAppConfig(Context context, ControlCenterServiceHandler handler, AppConfigBean config) {
LogUtils.d(TAG, "startRemindThreadWithAppConfig调用 | context=" + context + " | handler=" + handler + " | config=" + config);
// 入参严格校验
if (context == null || handler == null || config == null) {
LogUtils.e(TAG, "启动失败:入参为空 | context=" + context + " | handler=" + handler + " | config=" + config);
return false;
}
// 初始化线程列表
if (sRemindThreadList == null) {
synchronized (RemindThread.class) {
if (sRemindThreadList == null) {
sRemindThreadList = new ArrayList<RemindThread>();
LogUtils.d(TAG, "线程列表初始化完成");
}
}
}
// 停止所有旧线程
stopAllOldThreadsInternal();
// 创建并启动新线程
RemindThread newRemindThread = new RemindThread(context, handler);
newRemindThread.setAppConfigBean(config);
newRemindThread.isExist = false;
newRemindThread.start();
sRemindThreadList.add(newRemindThread);
LogUtils.d(TAG, "新线程启动成功 | threadId=" + newRemindThread.getId() + " | 列表大小=" + sRemindThreadList.size());
return true;
}
/**
* 启动提醒线程,同步电池状态信息
* 逻辑:停止所有旧线程 → 创建新线程 → 同步电池状态 → 加入列表管理
* @param context 上下文(非空)
* @param handler 服务处理器(非空)
* @param isCharging 充电状态
* @param batteryLevel 当前电量
* @return true: 启动成功false: 入参非法
*/
public static boolean startRemindThreadWithBatteryInfo(Context context, ControlCenterServiceHandler handler, boolean isCharging, int batteryLevel) {
LogUtils.d(TAG, "startRemindThreadWithBatteryInfo调用 | context=" + context + " | handler=" + handler + " | isCharging=" + isCharging + " | batteryLevel=" + batteryLevel);
// 入参严格校验
if (context == null || handler == null) {
LogUtils.e(TAG, "启动失败:入参为空 | context=" + context + " | handler=" + handler);
return false;
}
// 初始化线程列表
if (sRemindThreadList == null) {
synchronized (RemindThread.class) {
if (sRemindThreadList == null) {
sRemindThreadList = new ArrayList<RemindThread>();
LogUtils.d(TAG, "线程列表初始化完成");
}
}
}
// 停止所有旧线程
stopAllOldThreadsInternal();
// 创建并启动新线程
RemindThread newRemindThread = new RemindThread(context, handler);
// 同步电池状态
newRemindThread.isCharging = isCharging;
newRemindThread.quantityOfElectricity = Math.min(Math.max(batteryLevel, BATTERY_LEVEL_MIN), BATTERY_LEVEL_MAX);
newRemindThread.isExist = false;
newRemindThread.start();
sRemindThreadList.add(newRemindThread);
LogUtils.d(TAG, "新线程启动成功 | threadId=" + newRemindThread.getId() + " | 电池状态同步完成");
return true;
}
/**
* 安全停止所有线程,清空列表
*/
public static void stopRemindThread() {
LogUtils.d(TAG, "stopRemindThread调用 | 列表存在=" + (sRemindThreadList != null) + " | 列表大小=" + (sRemindThreadList != null ? sRemindThreadList.size() : 0));
if (sRemindThreadList == null || sRemindThreadList.isEmpty()) {
LogUtils.w(TAG, "停止失败:线程列表为空");
return;
}
// 标记所有线程退出
for (RemindThread remindThread : sRemindThreadList) {
remindThread.isExist = true;
LogUtils.d(TAG, "标记线程退出 | threadId=" + remindThread.getId());
}
// 清空列表
sRemindThreadList.clear();
LogUtils.d(TAG, "所有线程已标记退出,列表已清空");
}
// ================================== 私有静态辅助方法(多实例管理)=================================
/**
* 停止所有旧线程并清空列表
*/
private static void stopAllOldThreadsInternal() {
if (sRemindThreadList == null || sRemindThreadList.isEmpty()) {
return;
}
// 标记所有旧线程退出
for (RemindThread remindThread : sRemindThreadList) {
remindThread.isExist = true;
LogUtils.d(TAG, "标记旧线程退出 | threadId=" + remindThread.getId());
}
// 清空旧线程列表
sRemindThreadList.clear();
LogUtils.d(TAG, "旧线程已全部标记退出,列表已清空");
}
// ================================== 线程核心运行逻辑=================================
@Override
public void run() {
LogUtils.d(TAG, "run执行 | threadId=" + getId() + " | 状态=" + getState());
// 初始化提醒状态(加锁保护,避免多线程竞争)
synchronized (mRemindLock) {
if (isReminding) {
LogUtils.w(TAG, "线程已在提醒状态,退出运行 | threadId=" + getId());
return;
}
isReminding = true;
}
// 核心电量检测循环
LogUtils.d(TAG, "进入电量检测循环 | 休眠时间=" + sleepTime + "ms | threadId=" + getId());
while (!isExist) {
try {
// 快速退出判断
if (isExist) break;
// 电量有效性校验非0-100视为无效退出电量提醒线程
if (quantityOfElectricity < BATTERY_LEVEL_MIN || quantityOfElectricity > BATTERY_LEVEL_MAX) {
LogUtils.w(TAG, "电量无效,退出电量提醒线程 | 当前电量=" + quantityOfElectricity + " | threadId=" + getId());
break;
}
// 充电/耗电提醒触发逻辑
if (isCharging && isEnableChargeReminder && quantityOfElectricity >= chargeReminderValue) {
LogUtils.d(TAG, "触发充电提醒 | 当前电量=" + quantityOfElectricity + " ≥ 阈值=" + chargeReminderValue + " | threadId=" + getId());
sendNotificationMessageInternal(REMIND_TYPE_CHARGE, quantityOfElectricity, isCharging);
} else if (!isCharging && isEnableUsageReminder && quantityOfElectricity <= usageReminderValue) {
LogUtils.d(TAG, "触发耗电提醒 | 当前电量=" + quantityOfElectricity + " ≤ 阈值=" + usageReminderValue + " | threadId=" + getId());
sendNotificationMessageInternal(REMIND_TYPE_USAGE, quantityOfElectricity, isCharging);
} else {
// 未有合适类型提醒,退出提醒线程
LogUtils.d(TAG, "未有合适类型提醒,退出提醒线程");
break;
}
// 安全休眠,保留中断标记
safeSleepInternal(sleepTime);
} catch (Exception e) {
LogUtils.e(TAG, "循环运行异常,退出电量提醒线程 | 当前电量=" + quantityOfElectricity + " | threadId=" + getId(), e);
break;
}
}
// 循环退出,清理状态
cleanThreadStateInternal();
LogUtils.d(TAG, "run结束 | threadId=" + getId());
}
// ================================== 内部业务辅助方法=================================
/**
* 发送提醒消息到Handler弱引用避免内存泄漏
* @param type 提醒类型:+充电/-耗电
* @param battery 当前电量
* @param isCharging 充电状态
*/
private void sendNotificationMessageInternal(String type, int battery, boolean isCharging) {
LogUtils.d(TAG, "sendNotificationMessageInternal调用 | 类型=" + type + " | 电量=" + battery + " | isCharging=" + isCharging + " | threadId=" + getId());
// 前置状态校验
if (isExist || !isReminding) {
LogUtils.d(TAG, "消息发送跳过:线程已退出或提醒关闭 | threadId=" + getId());
return;
}
// 获取弱引用的Handler
ControlCenterServiceHandler handler = mwrControlCenterServiceHandler.get();
if (handler == null) {
LogUtils.w(TAG, "消息发送失败Handler已被回收 | threadId=" + getId());
return;
}
Message message = Message.obtain(handler, ControlCenterServiceHandler.MSG_REMIND_TEXT);
message.obj = type;
message.arg1 = battery;
message.arg2 = isCharging ? 1 : 0;
try {
handler.sendMessage(message);
LogUtils.d(TAG, "提醒消息发送成功 | 类型=" + type + " | 电量=" + battery + " | threadId=" + getId());
} catch (Exception e) {
LogUtils.e(TAG, "消息发送异常 | threadId=" + getId(), e);
// 异常时回收Message避免内存泄漏
if (message != null) {
message.recycle();
}
}
}
/**
* 安全休眠,响应线程中断
* @param millis 休眠时长(ms)
*/
private void safeSleepInternal(long millis) {
LogUtils.d(TAG, "safeSleepInternal调用 | 休眠时长=" + millis + "ms | threadId=" + getId());
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LogUtils.w(TAG, "休眠被中断,线程准备退出 | threadId=" + getId());
}
}
/**
* 重置线程初始状态(构造器专用)
*/
private void resetThreadStateInternal() {
LogUtils.d(TAG, "resetThreadStateInternal调用 | threadId=" + getId());
// 状态标记初始化
isExist = false;
isReminding = false;
// 配置参数初始化
isEnableChargeReminder = false;
isEnableUsageReminder = false;
sleepTime = MIN_SLEEP_TIME;
chargeReminderValue = -1;
usageReminderValue = -1;
quantityOfElectricity = INVALID_BATTERY_VALUE;
isCharging = false;
LogUtils.d(TAG, "线程初始状态重置完成 | threadId=" + getId());
}
/**
* 清理线程运行状态(循环退出时调用)
*/
private void cleanThreadStateInternal() {
LogUtils.d(TAG, "cleanThreadStateInternal调用 | threadId=" + getId());
isReminding = false;
isExist = true;
quantityOfElectricity = INVALID_BATTERY_VALUE;
// 中断当前线程(如果存活)
if (isAlive()) {
interrupt();
}
LogUtils.d(TAG, "线程运行状态清理完成 | threadId=" + getId());
}
/**
* 同步应用配置,校验参数有效性
* @param config 应用配置Bean
*/
public void setAppConfigBean(AppConfigBean config) {
LogUtils.d(TAG, "setAppConfigBean调用 | config=" + config + " | threadId=" + getId());
if (config == null) {
LogUtils.e(TAG, "配置同步失败配置Bean为空 | threadId=" + getId());
quantityOfElectricity = INVALID_BATTERY_VALUE;
return;
}
// 配置参数同步 + 范围校验(确保参数合法)
isEnableChargeReminder = config.isEnableChargeReminder();
isEnableUsageReminder = config.isEnableUsageReminder();
chargeReminderValue = Math.min(Math.max(config.getChargeReminderValue(), BATTERY_LEVEL_MIN), BATTERY_LEVEL_MAX);
usageReminderValue = Math.min(Math.max(config.getUsageReminderValue(), BATTERY_LEVEL_MIN), BATTERY_LEVEL_MAX);
sleepTime = Math.max(config.getBatteryDetectInterval(), MIN_SLEEP_TIME);
quantityOfElectricity = (config.getCurrentBatteryValue() >= BATTERY_LEVEL_MIN && config.getCurrentBatteryValue() <= BATTERY_LEVEL_MAX)
? config.getCurrentBatteryValue() : INVALID_BATTERY_VALUE;
isCharging = config.isCharging();
LogUtils.d(TAG, "配置同步完成 | 休眠时间=" + sleepTime + "ms | 提醒开启=" + isReminding + " | 当前电量=" + quantityOfElectricity + " | 充电阈值=" + chargeReminderValue + " | 耗电阈值=" + usageReminderValue + " | threadId=" + getId());
}
/**
* 判断线程是否处于运行状态
* @return true: 运行中false: 已停止
*/
private boolean isRunning() {
boolean running = !isExist && isAlive();
LogUtils.d(TAG, "isRunning调用 | 运行中=" + running + " | 退出标记=" + isExist + " | 存活=" + isAlive() + " | threadId=" + getId());
return running;
}
// ================================== Getter/Setter按需开放=================================
public void setIsExist(boolean isExist) {
LogUtils.d(TAG, "setIsExist调用 | isExist=" + isExist + " | threadId=" + getId());
this.isExist = isExist;
}
@@ -41,157 +373,20 @@ public class RemindThread extends Thread {
return isExist;
}
public static void setIsReminding(boolean isReminding) {
RemindThread.isReminding = isReminding;
}
public static boolean isReminding() {
return isReminding;
}
public static void setIsEnableUsegeReminder(boolean isEnableUsegeReminder) {
RemindThread.isEnableUsegeReminder = isEnableUsegeReminder;
}
public static boolean isEnableUsegeReminder() {
return isEnableUsegeReminder;
}
public static void setIsEnableChargeReminder(boolean isEnableChargeReminder) {
RemindThread.isEnableChargeReminder = isEnableChargeReminder;
}
public static boolean isEnableChargeReminder() {
return isEnableChargeReminder;
}
public static void setSleepTime(int sleepTime) {
RemindThread.sleepTime = sleepTime;
}
public static int getSleepTime() {
return sleepTime;
}
public static void setChargeReminderValue(int chargeReminderValue) {
RemindThread.chargeReminderValue = chargeReminderValue;
}
public static int getChargeReminderValue() {
return chargeReminderValue;
}
public static void setUsegeReminderValue(int usegeReminderValue) {
RemindThread.usegeReminderValue = usegeReminderValue;
}
public static int getUsegeReminderValue() {
return usegeReminderValue;
}
public static void setQuantityOfElectricity(int quantityOfElectricity) {
RemindThread.quantityOfElectricity = quantityOfElectricity;
}
public static int getQuantityOfElectricity() {
return quantityOfElectricity;
}
public static void setIsCharging(boolean isCharging) {
RemindThread.isCharging = isCharging;
}
public static boolean isCharging() {
return isCharging;
}
// 发送消息给用户
//
void sendNotificationMessage(String sz) {
//LogUtils.d(TAG, "sz is " + sz);
Message message = Message.obtain();
message.what = ControlCenterServiceHandler.MSG_REMIND_TEXT;
//message.obj = new NotificationMessage(mContext.getString(R.string.app_name), sz);
message.obj = sz;
ControlCenterServiceHandler handler = mwrControlCenterServiceHandler.get();
if (isReminding && handler != null) {
handler.sendMessage(message);
}
}
public RemindThread(Context context, ControlCenterServiceHandler handler) {
mContext = context;
mwrControlCenterServiceHandler = new WeakReference<ControlCenterServiceHandler>(handler);
}
// ================================== 调试辅助方法=================================
@Override
public void run() {
//LogUtils.d(TAG, "call run()");
if (isReminding == false) {
isReminding = true;
// 等待些许时间,等所有数据初始化完成再执行下面的程序
// 解决窗口移除后自动重启后会发送一个错误消息的问题
try {
Thread.sleep(500);
} catch (InterruptedException e) {}
// 发送提醒线程开始的参数设置
//sendMessageToUser(Integer.toString(_mnTheQuantityOfElectricity) + ">>>" + Integer.toString(_mnTargetNumber));
//ToastUtils.show("Service Is Start.");
//LogUtils.i(TAG, "Service Is Start.");
while (!isExist()) {
/*
LogUtils.d(TAG, "isCharging is " + Boolean.toString(isCharging));
LogUtils.d(TAG, "usegeReminderValue is " + Integer.toString(usegeReminderValue));
LogUtils.d(TAG, "quantityOfElectricity is " + Integer.toString(quantityOfElectricity));
LogUtils.d(TAG, "chargeReminderValue is " + Integer.toString(chargeReminderValue));
LogUtils.d(TAG, "isEnableChargeReminder is " + Boolean.toString(isEnableChargeReminder));
LogUtils.d(TAG, "isEnableUsegeReminder is " + Boolean.toString(isEnableUsegeReminder));
*/
try {
if (isCharging) {
if ((quantityOfElectricity >= chargeReminderValue)
&& (isEnableChargeReminder)) {
// 正在充电时电量大于指定电量发送提醒
sendNotificationMessage("+");
// 应用需要继续提醒,设置退出标志为否
setIsExist(false);
//sendNotificationMessage("I am ready! +");
} else {
// 设置退出标志,如果后续不需要继续提醒就退出当前进程,用于应用节能。
setIsExist(true);
isReminding = false;
return;
}
} else {
if ((quantityOfElectricity <= usegeReminderValue)
&& (isEnableUsegeReminder)) {
// 正在放电时电量小于指定电量发送提醒
sendNotificationMessage("-");
// 应用需要继续提醒,设置退出标志为否
setIsExist(false);
//sendNotificationMessage("I am ready! -");
} else {
// 设置退出标志,如果后续不需要继续提醒就退出当前进程,用于应用节能。
setIsExist(true);
isReminding = false;
return;
}
}
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
//ToastUtils.show("Service Is Stop.");
//LogUtils.i(TAG, "Service Is Stop.");
isReminding = false;
}
public String toString() {
return "RemindThread{" +
"threadId=" + getId() +
", threadName='" + getName() + '\'' +
", isRunning=" + isRunning() +
", isReminding=" + isReminding +
", chargeThreshold=" + chargeReminderValue +
", usageThreshold=" + usageReminderValue +
", currentBattery=" + quantityOfElectricity +
", isCharging=" + isCharging +
", sleepTime=" + sleepTime + "ms" +
'}';
}
}

View File

@@ -7,197 +7,309 @@ import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.models.AppConfigBean;
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog;
import cc.winboll.studio.powerbell.fragments.MainViewFragment;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import java.io.File;
// 应用配置工具类
//
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:59
* @Describe 应用配置工具类:管理应用核心配置(服务开关、电池提醒阈值、背景设置等)
* 适配Java7 | API30 | 小米手机,单例模式,线程安全,配置持久化
*/
public class AppConfigUtils {
// ======================== 静态常量(顶部统一管理,抽离魔法值)========================
public static final String TAG = "AppConfigUtils";
public static final String BACKGROUND_DIR = "Background"; // 背景图片存储目录
private static final int MIN_REMINDER_VALUE = 0; // 提醒阈值最小值
private static final int MAX_REMINDER_VALUE = 100; // 提醒阈值最大值
private static final int MIN_INTERVAL_TIME = 1000; // 最小提醒间隔ms
private static final int MIN_DETECT_INTERVAL = 500; // 最小电量检测间隔ms
public static final String BACKGROUND_DIR = "Background";
// ======================== 静态成员(单例实例,严格控制初始化)========================
private static AppConfigUtils sInstance; // 单例实例(私有,禁止外部直接创建)
// 保存唯一配置实例
static AppConfigUtils _mAppConfigUtils;
// 应用环境上下文
Context mContext;
// ======================== 核心依赖属性优先排列final保障安全========================
private final Context mContext; // 应用上下文(避免内存泄漏)
private App mApplication; // 应用Application实例
// 是否启动铃声提醒服务
volatile boolean mIsEnableService = false;
// ======================== 配置Bean属性持久化核心volatile保障线程安全========================
public volatile AppConfigBean mAppConfigBean; // 应用配置Bean
public volatile AppConfigBean mAppConfigBean;
// 电池充电提醒值。
// Battery charge reminder value.
volatile int mnChargeReminderValue = -1;
volatile boolean mIsEnableChargeReminder = false;
// 电池耗电量提醒值。
// Battery power usege reminder value.
volatile int mnUsegeReminderValue = -1;
volatile boolean mIsEnableUsegeReminder = false;
// ======================== 缓存状态属性减少Bean读取次数提升性能========================
private volatile boolean mIsServiceEnabled = false; // 服务开关缓存状态
volatile boolean mIsUseBackgroundFile = false;
volatile String mszBackgroundFileName = "";
// 保存应用实例
App mApplication;
AppConfigUtils(Context context) {
mContext = context;
String szExternalFilesDir = mContext.getExternalFilesDir(TAG) + File.separator;
//mlistAppConfigBean = new ArrayList<AppConfigBean>();
// ======================== 单例构造方法(私有,禁止外部实例化)========================
private AppConfigUtils(Context context) {
LogUtils.d(TAG, "初始化配置工具类");
this.mContext = context.getApplicationContext(); // 强制取应用上下文,杜绝内存泄漏
this.mApplication = (App) context.getApplicationContext();
// 初始化配置Bean
mAppConfigBean = new AppConfigBean();
loadAppConfigBean();
// 加载持久化配置
loadAppConfig();
LogUtils.d(TAG, "配置工具类初始化完成");
}
// 返回唯一实例
//
// ======================== 单例获取方法(双重校验锁,线程安全,适配多线程)========================
public static AppConfigUtils getInstance(Context context) {
if (_mAppConfigUtils == null) {
_mAppConfigUtils = new AppConfigUtils(context);
if (context == null) {
LogUtils.e(TAG, "getInstance: Context不能为空获取实例失败");
throw new IllegalArgumentException("Context cannot be null");
}
return _mAppConfigUtils;
}
public void setIsEnableService(Activity activity, final boolean isEnableService) {
YesNoAlertDialog.show(activity, "应用设置信息", "是否保存应用配置?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
mIsEnableService = isEnableService;
ControlCenterServiceBean bean = new ControlCenterServiceBean(isEnableService);
ControlCenterServiceBean.saveBean(mContext, bean);
if (mIsEnableService) {
LogUtils.d(TAG, "startControlCenterService");
ControlCenterService.startControlCenterService(mContext);
} else {
LogUtils.d(TAG, "stopControlCenterService");
ControlCenterService.stopControlCenterService(mContext);
}
if (sInstance == null) {
synchronized (AppConfigUtils.class) {
if (sInstance == null) {
sInstance = new AppConfigUtils(context);
LogUtils.d(TAG, "getInstance: 单例实例创建成功");
}
@Override
public void onNo() {
MainActivity.relaodAppConfigs();
}
});
}
}
return sInstance;
}
public boolean getIsEnableService() {
ControlCenterServiceBean bean = ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class);
if (bean == null) {
// ======================== 核心配置加载/保存方法(内部核心逻辑,优先排列)========================
/**
* 加载所有配置(应用配置+服务配置,统一入口,初始化/重载通用)
*/
public AppConfigBean loadAppConfig() {
LogUtils.d(TAG, "loadAllConfig: 开始加载所有配置");
// 加载应用配置
AppConfigBean savedAppBean = (AppConfigBean) AppConfigBean.loadBean(mContext, AppConfigBean.class);
if (savedAppBean != null) {
mAppConfigBean = savedAppBean;
LogUtils.d(TAG, "loadAllConfig: 应用配置加载成功");
} else {
mAppConfigBean = new AppConfigBean();
AppConfigBean.saveBean(mContext, mAppConfigBean);
LogUtils.d(TAG, "loadAllConfig: 无已保存应用配置,使用默认值并持久化");
}
return mAppConfigBean;
}
/**
* 保存应用配置(内部核心方法,直接持久化,同步通知服务+Activity
*/
private void saveAppConfig() {
AppConfigBean.saveBean(mContext, mAppConfigBean);
LogUtils.d(TAG, "saveAppConfig: 应用配置保存成功已同步服务和Activity");
}
// ======================== 充电提醒配置方法(单独归类,逻辑聚焦)========================
/**
* 设置充电提醒开关状态(直接生效,无弹窗)
* @param isEnabled 目标状态true=开启false=关闭)
*/
public void setChargeReminderEnabled(final boolean isEnabled) {
if (isEnabled == mAppConfigBean.isEnableChargeReminder()) {
LogUtils.d(TAG, "setChargeReminderEnabled: 充电提醒状态无变化,无需操作");
return;
}
mAppConfigBean.setEnableChargeReminder(isEnabled);
saveAppConfig();
LogUtils.d(TAG, "setChargeReminderEnabled: 充电提醒状态更新为=" + (isEnabled ? "开启" : "关闭"));
}
/**
* 获取充电提醒开关状态
* @return 充电提醒状态true=开启false=关闭)
*/
public boolean isChargeReminderEnabled() {
boolean isEnabled = mAppConfigBean.isEnableChargeReminder();
LogUtils.d(TAG, "isChargeReminderEnabled: 获取充电提醒状态=" + (isEnabled ? "开启" : "关闭"));
return isEnabled;
}
/**
* 设置充电提醒阈值直接生效无弹窗自动校准范围适配API30数据安全
* @param value 目标阈值自动校准0-100
*/
public void setChargeReminderValue(final int value) {
final int calibratedValue = Math.min(Math.max(value, MIN_REMINDER_VALUE), MAX_REMINDER_VALUE);
if (calibratedValue == mAppConfigBean.getChargeReminderValue()) {
LogUtils.d(TAG, "setChargeReminderValue: 充电提醒阈值无变化,无需操作");
return;
}
mAppConfigBean.setChargeReminderValue(calibratedValue);
saveAppConfig();
LogUtils.d(TAG, "setChargeReminderValue: 充电提醒阈值更新为=" + calibratedValue + "%");
}
/**
* 获取充电提醒阈值
* @return 充电提醒阈值0-100
*/
public int getChargeReminderValue() {
int value = mAppConfigBean.getChargeReminderValue();
LogUtils.d(TAG, "getChargeReminderValue: 获取充电提醒阈值=" + value + "%");
return value;
}
// ======================== 耗电提醒配置方法(单独归类,逻辑聚焦)========================
/**
* 设置耗电提醒开关状态(直接生效,无弹窗)
* @param isEnabled 目标状态true=开启false=关闭)
*/
public void setUsageReminderEnabled(final boolean isEnabled) {
if (isEnabled == mAppConfigBean.isEnableUsageReminder()) {
LogUtils.d(TAG, "setUsageReminderEnabled: 耗电提醒状态无变化,无需操作");
return;
}
mAppConfigBean.setEnableUsageReminder(isEnabled);
saveAppConfig();
LogUtils.d(TAG, "setUsageReminderEnabled: 耗电提醒状态更新为=" + (isEnabled ? "开启" : "关闭"));
}
/**
* 获取耗电提醒开关状态
* @return 耗电提醒状态true=开启false=关闭)
*/
public boolean isUsageReminderEnabled() {
boolean isEnabled = mAppConfigBean.isEnableUsageReminder();
LogUtils.d(TAG, "isUsageReminderEnabled: 获取耗电提醒状态=" + (isEnabled ? "开启" : "关闭"));
return isEnabled;
}
/**
* 设置耗电提醒阈值(直接生效,无弹窗,自动校准范围,适配小米手机电量跳变)
* @param value 目标阈值自动校准0-100
*/
public void setUsageReminderValue(final int value) {
final int calibratedValue = Math.min(Math.max(value, MIN_REMINDER_VALUE), MAX_REMINDER_VALUE);
if (calibratedValue == mAppConfigBean.getUsageReminderValue()) {
LogUtils.d(TAG, "setUsageReminderValue: 耗电提醒阈值无变化,无需操作");
return;
}
mAppConfigBean.setUsageReminderValue(calibratedValue);
saveAppConfig();
LogUtils.d(TAG, "setUsageReminderValue: 耗电提醒阈值更新为=" + calibratedValue + "%");
}
/**
* 获取耗电提醒阈值
* @return 耗电提醒阈值0-100
*/
public int getUsageReminderValue() {
int value = mAppConfigBean.getUsageReminderValue();
LogUtils.d(TAG, "getUsageReminderValue: 获取耗电提醒阈值=" + value + "%");
return value;
}
// ======================== 实时电池状态配置方法(临时缓存,不持久化,无需弹窗)========================
/**
* 设置当前充电状态(仅内存缓存,不持久化)
* @param isCharging 充电状态true=充电中false=未充电)
*/
public void setCharging(boolean isCharging) {
if (isCharging == mAppConfigBean.isCharging()) {
LogUtils.d(TAG, "setCharging: 充电状态无变化,无需操作");
return;
}
mAppConfigBean.setIsCharging(isCharging);
LogUtils.d(TAG, "setCharging: 充电状态更新为=" + (isCharging ? "充电中" : "未充电"));
}
/**
* 获取当前充电状态
* @return 充电状态true=充电中false=未充电)
*/
public boolean isCharging() {
boolean isCharging = mAppConfigBean.isCharging();
LogUtils.d(TAG, "isCharging: 获取充电状态=" + (isCharging ? "充电中" : "未充电"));
return isCharging;
}
/**
* 设置当前电池电量(仅内存缓存,不持久化,自动校准范围)
* @param value 当前电量自动校准0-100
*/
public void setCurrentBatteryValue(int value) {
int calibratedValue = Math.min(Math.max(value, MIN_REMINDER_VALUE), MAX_REMINDER_VALUE);
if (calibratedValue == mAppConfigBean.getCurrentBatteryValue()) {
LogUtils.d(TAG, "setCurrentBatteryValue: 电池电量无变化,无需操作");
return;
}
mAppConfigBean.setCurrentBatteryValue(calibratedValue);
LogUtils.d(TAG, "setCurrentBatteryValue: 电池电量更新为=" + calibratedValue + "%");
}
/**
* 获取当前电池电量
* @return 当前电池电量0-100
*/
public int getCurrentBatteryValue() {
int value = mAppConfigBean.getCurrentBatteryValue();
LogUtils.d(TAG, "getCurrentBatteryValue: 获取电池电量=" + value + "%");
return value;
}
// ======================== 间隔配置方法(持久化存储,直接生效,无弹窗)========================
/**
* 设置提醒间隔时间直接生效无弹窗自动校准最小1000ms
* @param interval 目标间隔单位ms
*/
public void setReminderIntervalTime(final int interval) {
final int calibratedInterval = Math.max(interval, MIN_INTERVAL_TIME);
if (calibratedInterval == mAppConfigBean.getReminderIntervalTime()) {
LogUtils.d(TAG, "setReminderIntervalTime: 提醒间隔无变化,无需操作");
return;
}
mAppConfigBean.setReminderIntervalTime(calibratedInterval);
saveAppConfig();
LogUtils.d(TAG, "setReminderIntervalTime: 提醒间隔更新为=" + calibratedInterval + "ms");
}
/**
* 获取提醒间隔时间
* @return 提醒间隔单位ms
*/
public int getReminderIntervalTime() {
int interval = mAppConfigBean.getReminderIntervalTime();
LogUtils.d(TAG, "getReminderIntervalTime: 获取提醒间隔=" + interval + "ms");
return interval;
}
/**
* 设置电量检测间隔直接生效无弹窗自动校准最小500ms与RemindThread同步
* @param interval 目标间隔单位ms
*/
public void setBatteryDetectInterval(final int interval) {
final int calibratedInterval = Math.max(interval, MIN_DETECT_INTERVAL);
if (calibratedInterval == mAppConfigBean.getBatteryDetectInterval()) {
LogUtils.d(TAG, "setBatteryDetectInterval: 检测间隔无变化,无需操作");
return;
}
mAppConfigBean.setBatteryDetectInterval(calibratedInterval);
saveAppConfig();
LogUtils.d(TAG, "setBatteryDetectInterval: 电量检测间隔更新为=" + calibratedInterval + "ms");
}
/**
* 获取电量检测间隔
* @return 电量检测间隔单位ms
*/
public int getBatteryDetectInterval() {
int interval = mAppConfigBean.getBatteryDetectInterval();
LogUtils.d(TAG, "getBatteryDetectInterval: 获取电量检测间隔=" + interval + "ms");
return interval;
}
public boolean isServiceEnabled() {
// 加载服务配置
ControlCenterServiceBean savedServiceBean = (ControlCenterServiceBean) ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class);
if (savedServiceBean != null) {
return savedServiceBean.isEnableService();
} else {
ControlCenterServiceBean.saveBean(mContext, new ControlCenterServiceBean(false));
return false;
}
return bean.isEnableService();
}
}
public void setIsEnableChargeReminder(boolean isEnableChargeReminder) {
mAppConfigBean.setIsEnableChargeReminder(isEnableChargeReminder);
saveConfigData(MainActivity._mMainActivity);
}
public boolean getIsEnableChargeReminder() {
return mAppConfigBean.isEnableChargeReminder();
}
public void setIsEnableUsegeReminder(boolean isEnableUsegeReminder) {
mAppConfigBean.setIsEnableUsegeReminder(isEnableUsegeReminder);
saveConfigData(MainActivity._mMainActivity);
}
public boolean getIsEnableUsegeReminder() {
return mAppConfigBean.isEnableUsegeReminder();
}
public void setChargeReminderValue(int value) {
mAppConfigBean.setChargeReminderValue(value);
saveConfigData(MainActivity._mMainActivity);
}
public int getChargeReminderValue() {
return mAppConfigBean.getChargeReminderValue();
}
public void setUsegeReminderValue(int value) {
mAppConfigBean.setUsegeReminderValue(value);
saveConfigData(MainActivity._mMainActivity);
}
public int getUsegeReminderValue() {
return mAppConfigBean.getUsegeReminderValue();
}
public void setIsCharging(boolean isCharging) {
mAppConfigBean.setIsCharging(isCharging);
}
public boolean isCharging() {
return mAppConfigBean.isCharging();
}
public void setCurrentValue(int nCurrentValue) {
mAppConfigBean.setCurrentValue(nCurrentValue);
}
public int getCurrentValue() {
return mAppConfigBean.getCurrentValue();
}
public void setReminderIntervalTime(int reminderIntervalTime) {
mAppConfigBean.setReminderIntervalTime(reminderIntervalTime);
}
public int getReminderIntervalTime() {
return mAppConfigBean.getReminderIntervalTime();
}
//
// 加载电池提醒配置数据
//
public void loadAppConfigBean() {
AppConfigBean bean = AppConfigBean.loadBean(mContext, AppConfigBean.class);
if (bean == null) {
bean = new AppConfigBean();
AppConfigBean.saveBean(mContext, mAppConfigBean);
}
mAppConfigBean.setIsEnableUsegeReminder(bean.isEnableUsegeReminder());
mAppConfigBean.setUsegeReminderValue(bean.getUsegeReminderValue());
mAppConfigBean.setIsEnableChargeReminder(bean.isEnableChargeReminder());
mAppConfigBean.setChargeReminderValue(bean.getChargeReminderValue());
}
public void saveConfigData(final MainActivity activity) {
if (MainActivity._mMainActivity == null) {
return;
}
YesNoAlertDialog.show(activity, "应用设置信息", "是否保存应用配置?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
saveConfigData();
}
@Override
public void onNo() {
AppConfigUtils.getInstance(activity).loadAppConfigBean();
MainActivity.relaodAppConfigs();
}
});
}
//
// 保存应用配置数据
//
void saveConfigData() {
// 更新配置先取消一下旧的的提醒消息
//NotificationHelper.cancelRemindNotification(mContext);
AppConfigBean.saveBean(mContext, mAppConfigBean);
// 通知活动窗口和服务配置已更新
ControlCenterService.updateStatus(mContext, mAppConfigBean);
MainActivity.relaodAppConfigs();
}
public void setIsServiceEnabled(boolean isServiceEnabled) {
ControlCenterServiceBean.saveBean(mContext, new ControlCenterServiceBean(isServiceEnabled));
}
}

View File

@@ -1,6 +1,7 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.media.ExifInterface;
import android.net.Uri;
@@ -11,6 +12,7 @@ import androidx.core.content.FileProvider;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.BuildConfig;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -190,7 +192,7 @@ public class BackgroundSourceUtils {
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
AssetsCopyUtils.copyAssetsFileToDir(mContext, "images/blank10x10.png", mCropSourceFile.getAbsolutePath());
AssetsCopyUtils.copyAssetsFileToDir(mContext, "images/blank100x100.png", mCropSourceFile.getAbsolutePath());
try {
mCropResultFile.createNewFile();
} catch (IOException e) {
@@ -198,6 +200,7 @@ public class BackgroundSourceUtils {
}
loadSettings();
previewBackgroundBean.setPixelColor(0xFFFFFFFF);
previewBackgroundBean.setIsUseBackgroundFile(true);
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(false);
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());

View File

@@ -1,28 +1,75 @@
package cc.winboll.studio.powerbell.utils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 04:32:46
* @Describe 电池工具类
*/
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2024/07/18 04:32:46
* @Describe 电池状态工具类
* 功能解析电池广播Intent获取充电状态、当前电量
* 适配Java7 | API30 | 小米手机
*/
public class BatteryUtils {
// ================================== 静态常量区(置顶归类,消除魔法值)=================================
public static final String TAG = "BatteryUtils";
// 电池电量计算常量
private static final int BATTERY_SCALE_DEFAULT = 100;
private static final int BATTERY_LEVEL_MIN = 0;
private static final int BATTERY_LEVEL_MAX = 100;
// ================================== 工具方法(静态方法,无状态设计)=================================
/**
* 判断当前是否处于充电状态
* @param intent 电池状态广播Intent非空
* @return true=充电中/已充满false=未充电
*/
public static boolean isCharging(Intent intent) {
LogUtils.d(TAG, "isCharging: 调用 | intent=" + intent);
// 入参非空校验
if (intent == null) {
LogUtils.e(TAG, "isCharging: intent为空返回false");
return false;
}
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
LogUtils.d(TAG, "isCharging: 解析完成 | status=" + status + " | result=" + isCharging);
return isCharging;
}
public static int getTheQuantityOfElectricity(Intent intent) {
int intLevel = intent.getIntExtra("level", 0);
int intScale = intent.getIntExtra("scale", 100);
return intLevel * 100 / intScale;
/**
* 获取当前电池电量百分比0-100
* @param intent 电池状态广播Intent非空
* @return 电量百分比异常返回0
*/
public static int getCurrentBatteryLevel(Intent intent) {
LogUtils.d(TAG, "getCurrentBatteryLevel: 调用 | intent=" + intent);
// 入参非空校验
if (intent == null) {
LogUtils.e(TAG, "getCurrentBatteryLevel: intent为空返回0");
return BATTERY_LEVEL_MIN;
}
// 解析电量原始值与刻度值
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_MIN);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE_DEFAULT);
LogUtils.d(TAG, "getCurrentBatteryLevel: 原始值 | level=" + level + " | scale=" + scale);
// 计算并校验电量百分比避免除以0或数值越界
int batteryLevel;
if (scale <= 0) {
batteryLevel = level;
LogUtils.w(TAG, "getCurrentBatteryLevel: scale无效直接使用level值");
} else {
batteryLevel = level * BATTERY_SCALE_DEFAULT / scale;
}
// 确保电量值在0-100范围内
batteryLevel = Math.max(BATTERY_LEVEL_MIN, Math.min(batteryLevel, BATTERY_LEVEL_MAX));
LogUtils.d(TAG, "getCurrentBatteryLevel: 计算完成 | batteryLevel=" + batteryLevel + "%");
return batteryLevel;
}
}

View File

@@ -1,21 +1,22 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/11 01:57
* @Describe 单例 Bitmap 缓存工具类Java 7 兼容)
* 功能:内存缓存 Bitmap支持路径关联缓存、全局获取、缓存清空
* 特点1. 单例模式 2. 压缩加载避免OOM 3. 路径- Bitmap 映射 4. 线程安全
* 功能:内存缓存 Bitmap支持路径关联缓存、全局获取、缓存清空、SP 持久化最后缓存路径、构造时预加载
* 特点1. 单例模式 2. 压缩加载避免OOM 3. 路径-Bitmap 映射 4. 线程安全 5. SP 持久化最后缓存路径 6. 构造时预加载
*/
public class BitmapCacheUtils {
public static final String TAG = "BitmapCacheUtils";
@@ -23,14 +24,24 @@ public class BitmapCacheUtils {
private static final int MAX_WIDTH = 1080;
private static final int MAX_HEIGHT = 1920;
// SP 相关常量
private static final String SP_NAME = "BitmapCacheSP";
private static final String SP_KEY_LAST_CACHE_PATH = "last_cache_image_path";
// 单例实例volatile 保证多线程可见性)
private static volatile BitmapCacheUtils sInstance;
// 路径-Bitmap 缓存容器(内存缓存)
private final Map<String, Bitmap> mBitmapCacheMap;
// SP 实例(用于持久化最后缓存路径)
private final SharedPreferences mSp;
// 私有构造器(单例模式)
private BitmapCacheUtils() {
mBitmapCacheMap = new HashMap<>();
// 初始化 SP使用 App 全局上下文,避免内存泄漏)
mSp = App.getInstance().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
// 构造时自动预加载 SP 中保存的最后一次缓存路径的图片
preloadLastCachedBitmap();
}
/**
@@ -48,7 +59,7 @@ public class BitmapCacheUtils {
}
/**
* 核心接口:根据图片路径缓存 Bitmap 到内存
* 核心接口:根据图片路径缓存 Bitmap 到内存,并持久化路径到 SP
* @param imagePath 图片绝对路径
* @return 缓存成功的 Bitmap / null路径无效/文件不存在/解码失败)
*/
@@ -70,6 +81,8 @@ public class BitmapCacheUtils {
// 额外校验缓存的Bitmap是否有效
if (cachedBitmap != null && !cachedBitmap.isRecycled()) {
LogUtils.d(TAG, "cacheBitmap: 图片已缓存,直接返回 - " + imagePath);
// 持久化当前路径到 SP更新最后缓存路径
saveLastCachePathToSp(imagePath);
return cachedBitmap;
} else {
// 缓存的Bitmap已失效移除后重新加载
@@ -83,7 +96,9 @@ public class BitmapCacheUtils {
if (bitmap != null) {
// 存入缓存容器
mBitmapCacheMap.put(imagePath, bitmap);
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功 - " + imagePath);
// 持久化当前路径到 SP更新最后缓存路径
saveLastCachePathToSp(imagePath);
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径 - " + imagePath);
} else {
LogUtils.e(TAG, "cacheBitmap: 图片解码失败 - " + imagePath);
}
@@ -109,7 +124,7 @@ public class BitmapCacheUtils {
}
/**
* 清空所有 Bitmap 缓存(释放内存)
* 清空所有 Bitmap 缓存(释放内存),并清空 SP 中保存的最后缓存路径
*/
public void clearAllCache() {
synchronized (mBitmapCacheMap) {
@@ -120,7 +135,9 @@ public class BitmapCacheUtils {
}
mBitmapCacheMap.clear();
}
LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空");
// 清空 SP 中保存的最后缓存路径
clearLastCachePathInSp();
LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空SP 路径已清除");
}
/**
@@ -137,6 +154,12 @@ public class BitmapCacheUtils {
bitmap.recycle();
LogUtils.d(TAG, "removeCachedBitmap: 移除并回收缓存 - " + imagePath);
}
// 若移除的是最后缓存的路径,清空 SP
String lastPath = getLastCachePathFromSp();
if (imagePath.equals(lastPath)) {
clearLastCachePathInSp();
LogUtils.d(TAG, "removeCachedBitmap: 移除的是最后缓存路径,已清空 SP");
}
}
}
@@ -202,5 +225,53 @@ public class BitmapCacheUtils {
}
return inSampleSize;
}
/**
* 从 SP 中获取最后一次缓存的图片路径
* @return 最后缓存的路径 / null未保存
*/
private String getLastCachePathFromSp() {
return mSp.getString(SP_KEY_LAST_CACHE_PATH, null);
}
/**
* 将当前缓存路径持久化到 SP
* @param imagePath 图片绝对路径
*/
private void saveLastCachePathToSp(String imagePath) {
if (TextUtils.isEmpty(imagePath)) {
return;
}
mSp.edit().putString(SP_KEY_LAST_CACHE_PATH, imagePath).commit(); // Java 7 兼容,使用 commit 而非 apply
LogUtils.d(TAG, "saveLastCachePathToSp: 持久化最后缓存路径 - " + imagePath);
}
/**
* 清空 SP 中保存的最后缓存路径
*/
private void clearLastCachePathInSp() {
mSp.edit().remove(SP_KEY_LAST_CACHE_PATH).commit();
LogUtils.d(TAG, "clearLastCachePathInSp: SP 中最后缓存路径已清空");
}
/**
* 构造时预加载 SP 中保存的最后一次缓存路径的图片
*/
private void preloadLastCachedBitmap() {
String lastPath = getLastCachePathFromSp();
if (TextUtils.isEmpty(lastPath)) {
LogUtils.d(TAG, "preloadLastCachedBitmap: SP 中无保存的缓存路径,跳过预加载");
return;
}
// 调用 cacheBitmap 预加载(内部已做文件校验和缓存判断)
Bitmap bitmap = cacheBitmap(lastPath);
if (bitmap != null) {
LogUtils.d(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径成功 - " + lastPath);
} else {
LogUtils.w(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径失败,清空无效路径 - " + lastPath);
// 预加载失败,清空 SP 中无效路径
clearLastCachePathInSp();
}
}
}

View File

@@ -4,413 +4,503 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.RingtoneManager;
import android.os.Build;
import android.view.View;
import android.widget.RemoteViews;
import androidx.core.app.NotificationCompat;
import android.provider.Settings;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.MainActivity;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.models.NotificationMessage;
import cc.winboll.studio.powerbell.services.ControlCenterService;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/13 20:44
* @Describe 全局通知管理工具类整合所有通知能力适配API29-30兼容Java7所有通知统一跳转MainActivity
* 通知工具类:统一管理前台服务/电池提醒/应用配置信息通知
* 适配API19-30 | Java7 | 小米手机
* 特性前台服务无铃声、提醒通知系统默认铃声、配置通知低优先级无打扰、API分级适配、内存泄漏防护
*/
public class NotificationManagerUtils {
// ====================== 常量定义(统一管理,避免冲突,首屏可见)======================
public static final String TAG = "NotificationManagerUtils";
// 通知渠道4大渠道场景隔离API26+必填
// 1. 前台服务保活渠道(低优先级,无打扰)
private static final String CHANNEL_ID_FOREGROUND_SERVICE = "channel_foreground_service";
private static final String CHANNEL_NAME_FOREGROUND_SERVICE = "前台服务保活通知";
private static final String CHANNEL_DESC_FOREGROUND_SERVICE = "后台服务运行状态,无声音无震动,不打扰用户";
// 2. 电量提醒渠道(高优先级,闹钟铃声+震动,强提醒)
private static final String CHANNEL_ID_BATTERY_REMIND = "channel_battery_remind";
private static final String CHANNEL_NAME_BATTERY_REMIND = "电量异常提醒通知";
private static final String CHANNEL_DESC_BATTERY_REMIND = "电量过高/过低提醒,强震动+闹钟铃声,突破免打扰";
// 3. 通用临时通知渠道(高优先级,仅震动,普通告警)
private static final String CHANNEL_ID_TEMP_ALERT = "channel_temp_alert";
private static final String CHANNEL_NAME_TEMP_ALERT = "通用临时提醒通知";
private static final String CHANNEL_DESC_TEMP_ALERT = "普通即时告警,仅震动提醒,自动取消";
// 通知ID唯一区分避免覆盖按场景分段
public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001; // 前台服务
public static final int NOTIFY_ID_BATTERY_REMIND = 1002; // 电量提醒
public static final int NOTIFY_ID_TEMP_ALERT = 1003; // 通用临时通知
public static final int NOTIFY_ID_CUSTOM_LAYOUT = 1004; // 自定义布局通知
// 通用配置
private static final int PENDING_INTENT_FLAGS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
: PendingIntent.FLAG_UPDATE_CURRENT; // API30安全标志
private static final long[] VIBRATE_PATTERN = new long[]{100, 200, 300, 400}; // 标准震动节奏
//private static final String DEFAULT_JUMP_PACKAGE = "cc.winboll.studio.powerbell"; // 默认跳转包名API29+必填)
// ================================== 静态常量(置顶统一管理,杜绝魔法值)=================================
public static final String TAG = "NotificationManagerUtils";
// 通知渠道IDAPI26+ 必需,区分通知类型
public static final String CHANNEL_ID_FOREGROUND = "cc.winboll.studio.powerbell.channel.foreground";
public static final String CHANNEL_ID_REMIND = "cc.winboll.studio.powerbell.channel.remind";
public static final String CHANNEL_ID_CONFIG = "cc.winboll.studio.powerbell.channel.config"; // 新增:应用配置信息渠道
// 通知ID唯一标识避免重复
public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001;
public static final int NOTIFY_ID_REMIND = 1002;
public static final int NOTIFY_ID_CONFIG = 1003; // 新增应用配置信息通知ID
// 低版本兼容默认通知图标API<21 避免显示异常)
private static final int NOTIFICATION_DEFAULT_ICON = R.drawable.ic_launcher;
// 通知内容兜底常量
private static final String FOREGROUND_NOTIFY_TITLE_DEFAULT = "电池服务运行中";
private static final String FOREGROUND_NOTIFY_CONTENT_DEFAULT = "后台监测电池状态";
private static final String REMIND_NOTIFY_TITLE_DEFAULT = "电池状态提醒";
private static final String REMIND_NOTIFY_CONTENT_DEFAULT = "电池状态异常,请及时处理";
private static final String CONFIG_NOTIFY_TITLE_DEFAULT = "应用配置更新"; // 新增:配置通知默认标题
private static final String CONFIG_NOTIFY_CONTENT_DEFAULT = "配置信息已更新,生效中"; // 新增:配置通知默认内容
// PendingIntent请求码
private static final int PENDING_INTENT_REQUEST_CODE_FOREGROUND = 0;
private static final int PENDING_INTENT_REQUEST_CODE_REMIND = 1;
private static final int PENDING_INTENT_REQUEST_CODE_CONFIG = 2; // 新增:配置通知请求码
// ====================== 成员变量(按场景分组,私有封装,避免外部篡改)======================
private final Context mContext;
private final NotificationManager mNotificationManager;
// 前台服务通知专属
private Notification mForegroundServiceNotify;
private RemoteViews mForegroundServiceRemoteViews;
// 电量提醒通知专属
private Notification mBatteryRemindNotify;
private RemoteViews mBatteryRemindRemoteViews;
// ================================== 成员变量(私有封装,按依赖优先级排序)=================================
// 核心上下文(应用级,避免内存泄漏)
private Context mContext;
// 系统通知服务(核心依赖)
private NotificationManager mNotificationManager;
// 前台服务通知实例(单独持有,便于更新/取消)
private Notification mForegroundServiceNotify;
// ====================== 构造方法(单例思想/实例化通用,自动初始化渠道)======================
public NotificationManagerUtils(Context context) {
LogUtils.d(TAG, "【初始化】全局通知管理工具类 构造方法调用");
this.mContext = context.getApplicationContext(); // 用应用上下文,避免内存泄漏
this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
createAllNotificationChannels(); // 自动创建所有渠道API26+
LogUtils.d(TAG, "【初始化】全局通知管理工具类 完成,渠道创建状态:" + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? "已创建4个渠道" : "无需创建"));
}
// ================================== 构造方法(初始化核心资源,前置校验)=================================
public NotificationManagerUtils(Context context) {
LogUtils.d(TAG, "NotificationManagerUtils: 构造方法执行 | context=" + context);
// 前置校验Context非空
if (context == null) {
LogUtils.e(TAG, "NotificationManagerUtils: 构造失败context is null");
return;
}
// 初始化核心资源
this.mContext = context.getApplicationContext();
this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
LogUtils.d(TAG, "NotificationManagerUtils: 核心资源初始化完成 | mContext=" + mContext + " | mNotificationManager=" + mNotificationManager);
// 初始化通知渠道API26+ 必需)
initNotificationChannels();
LogUtils.d(TAG, "NotificationManagerUtils: 构造完成");
}
// ====================== 核心基础能力(渠道创建+Intent构建复用逻辑减少冗余======================
/**
* 创建所有通知渠道API26+专属,低版本自动跳过,确保通知正常显示
*/
@SuppressWarnings("deprecation")
public void createAllNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LogUtils.d(TAG, "【渠道管理】开始创建所有通知渠道");
createForegroundServiceChannel();
createBatteryRemindChannel();
createTempAlertChannel();
LogUtils.d(TAG, "【渠道管理】4个通知渠道创建完成含3个核心渠道+预留扩展)");
}
}
// ================================== 核心初始化方法通知渠道API分级适配=================================
/**
* 初始化通知渠道:前台服务渠道(无铃声+无振动)、提醒渠道(系统默认铃声+无振动)、配置信息渠道(低优先级无打扰
*/
private void initNotificationChannels() {
LogUtils.d(TAG, "initNotificationChannels: 执行通知渠道初始化");
// API<26 无渠道机制,直接返回
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
LogUtils.d(TAG, "initNotificationChannels: API<26无需创建渠道");
return;
}
// 通知服务为空,避免空指针
if (mNotificationManager == null) {
LogUtils.e(TAG, "initNotificationChannels: 失败NotificationManager is null");
return;
}
/**
* 创建前台服务保活渠道IMPORTANCE_LOW无声音无震动不打扰用户
*/
private void createForegroundServiceChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID_FOREGROUND_SERVICE,
CHANNEL_NAME_FOREGROUND_SERVICE,
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription(CHANNEL_DESC_FOREGROUND_SERVICE);
channel.setSound(null, null);
channel.enableVibration(false);
channel.setShowBadge(false); // 不显示应用角标
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); // 锁屏隐藏
mNotificationManager.createNotificationChannel(channel);
LogUtils.d(TAG, "【渠道管理】前台服务保活渠道创建成功:" + CHANNEL_NAME_FOREGROUND_SERVICE);
}
}
// 1. 前台服务渠道(低优先级,后台保活无打扰)
NotificationChannel foregroundChannel = new NotificationChannel(
CHANNEL_ID_FOREGROUND,
"电池服务保活",
NotificationManager.IMPORTANCE_LOW
);
foregroundChannel.setDescription("电池监测服务后台运行,无声音、无振动");
foregroundChannel.enableLights(false);
foregroundChannel.enableVibration(false);
foregroundChannel.setSound(null, null); // 强制无铃声
foregroundChannel.setShowBadge(false);
foregroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
LogUtils.d(TAG, "initNotificationChannels: 前台服务渠道配置完成");
/**
* 创建电量提醒渠道IMPORTANCE_HIGH闹钟铃声+震动,突破免打扰)
*/
private void createBatteryRemindChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID_BATTERY_REMIND,
CHANNEL_NAME_BATTERY_REMIND,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(CHANNEL_DESC_BATTERY_REMIND);
channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), null); // 闹钟铃声
channel.enableVibration(true);
channel.setVibrationPattern(VIBRATE_PATTERN);
channel.setBypassDnd(true); // 突破免打扰
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); // 锁屏可见
channel.setShowBadge(true);
mNotificationManager.createNotificationChannel(channel);
LogUtils.d(TAG, "【渠道管理】电量提醒渠道创建成功:" + CHANNEL_NAME_BATTERY_REMIND);
}
}
// 2. 电池提醒渠道(中优先级,系统默认铃声,无振动)
NotificationChannel remindChannel = new NotificationChannel(
CHANNEL_ID_REMIND,
"电池状态提醒",
NotificationManager.IMPORTANCE_DEFAULT
);
remindChannel.setDescription("电池满电/低电量提醒,系统默认铃声,无振动");
remindChannel.enableLights(true);
remindChannel.enableVibration(false);
remindChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), Notification.AUDIO_ATTRIBUTES_DEFAULT);
remindChannel.setShowBadge(false);
remindChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
LogUtils.d(TAG, "initNotificationChannels: 电池提醒渠道配置完成");
/**
* 创建通用临时通知渠道IMPORTANCE_HIGH仅震动普通告警
*/
private void createTempAlertChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID_TEMP_ALERT,
CHANNEL_NAME_TEMP_ALERT,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(CHANNEL_DESC_TEMP_ALERT);
//channel.setSound(null, null); // 仅震动,不发声
channel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), null); // 闹钟铃声
channel.enableVibration(true);
channel.setVibrationPattern(VIBRATE_PATTERN);
channel.setBypassDnd(false); // 不突破免打扰
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
channel.setShowBadge(true);
mNotificationManager.createNotificationChannel(channel);
LogUtils.d(TAG, "【渠道管理】通用临时通知渠道创建成功:" + CHANNEL_NAME_TEMP_ALERT);
}
}
// 3. 应用配置信息渠道(新增:最低优先级,无铃声无振动,仅提示不打扰)
NotificationChannel configChannel = new NotificationChannel(
CHANNEL_ID_CONFIG,
"应用配置信息",
NotificationManager.IMPORTANCE_MIN
);
configChannel.setDescription("应用配置更新、参数变更等提示,无声音、无振动");
configChannel.enableLights(false);
configChannel.enableVibration(false);
configChannel.setSound(null, null);
configChannel.setShowBadge(false);
configChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
LogUtils.d(TAG, "initNotificationChannels: 应用配置信息渠道配置完成");
/**
* 构建固定跳转PendingIntent所有通知统一跳转MainActivity适配API29-30安全规范
* @return 安全的PendingIntent确保跳转稳定不泄露
*/
private PendingIntent buildFixedPendingIntent() {
// 固定跳MainActivity不支持自定义目标
Intent intent = new Intent(mContext, MainActivity.class);
// API29+ 强制要求:明确包名,避免跳转目标模糊
intent.setPackage(mContext.getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // 跳转时清除栈顶避免重复创建Activity
LogUtils.d(TAG, "【Intent构建】所有通知统一跳转MainActivity包名" + mContext.getPackageName());
// 注册渠道到系统
mNotificationManager.createNotificationChannel(foregroundChannel);
mNotificationManager.createNotificationChannel(remindChannel);
mNotificationManager.createNotificationChannel(configChannel); // 注册新增渠道
LogUtils.d(TAG, "initNotificationChannels: 成功:创建前台服务+电池提醒+应用配置信息渠道");
}
PendingIntent pendingIntent = PendingIntent.getActivity(
mContext,
0,
intent,
PENDING_INTENT_FLAGS
);
LogUtils.d(TAG, "【Intent构建】PendingIntent创建成功安全标志" + PENDING_INTENT_FLAGS);
return pendingIntent;
}
// ================================== 对外核心方法(前台服务通知:启动/更新/取消)=================================
/**
* 启动前台服务通知API30适配无铃声
*/
public void startForegroundServiceNotify(Service service, NotificationMessage message) {
LogUtils.d(TAG, "startForegroundServiceNotify: 执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE + " | service=" + service + " | message=" + message);
// 前置校验:参数非空
if (service == null || message == null || mNotificationManager == null) {
LogUtils.e(TAG, "startForegroundServiceNotify: 失败param is null | service=" + service + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
return;
}
// ====================== 场景1前台服务保活通知支持自定义布局+更新)======================
/**
* 初始化前台服务通知自定义布局RemoteViews
*/
private void initForegroundServiceRemoteViews(ControlCenterService service, NotificationMessage msg) {
LogUtils.d(TAG, "【布局初始化】开始初始化前台服务通知布局,标题:" + msg.getTitle());
mForegroundServiceRemoteViews = new RemoteViews(service.getPackageName(), R.layout.view_servicenotification);
mForegroundServiceRemoteViews.setTextViewText(R.id.remoteviewTextView1, msg.getTitle());
mForegroundServiceRemoteViews.setTextViewText(R.id.remoteviewTextView3, msg.getContent());
mForegroundServiceRemoteViews.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher);
LogUtils.d(TAG, "【布局初始化】前台服务通知布局填充完成");
}
// 构建前台通知
mForegroundServiceNotify = buildForegroundNotification(message);
if (mForegroundServiceNotify == null) {
LogUtils.e(TAG, "startForegroundServiceNotify: 失败:构建通知为空");
return;
}
/**
* 启动前台服务保活通知ControlCenterService专用API26+强制要求,保活后台服务)
*/
public void startForegroundServiceNotify(ControlCenterService service, NotificationMessage msg) {
LogUtils.d(TAG, "【前台服务通知】开始构建保活通知,内容:" + msg.getContent());
if (service == null || msg == null) {
LogUtils.e(TAG, "【前台服务通知】构建失败Service/NotificationMessage为空");
return;
}
// 启动前台服务API30无FOREGROUND_SERVICE_TYPE限制全版本通用
try {
service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "startForegroundServiceNotify: 成功");
} catch (Exception e) {
LogUtils.e(TAG, "startForegroundServiceNotify: 异常", e);
}
}
// 1. 构建固定跳转Intent统一跳MainActivity
PendingIntent pendingIntent = buildFixedPendingIntent();
// 2. 构建基础通知兼容API26+渠道低版本用Builder
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(service, CHANNEL_ID_FOREGROUND_SERVICE);
} else {
builder = new Notification.Builder(service);
}
mForegroundServiceNotify = builder
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher))
.setContentTitle(msg.getTitle())
.setContentText(msg.getContent())
.setWhen(System.currentTimeMillis())
.setColor(Color.parseColor("#F00606")) // 小图标背景色
.setContentIntent(pendingIntent)
.setOngoing(true) // 常驻通知,不可滑动取消(保活关键)
.setAutoCancel(false) // 禁止点击取消
.build();
// 3. 设置自定义布局
initForegroundServiceRemoteViews(service, msg);
mForegroundServiceNotify.contentView = mForegroundServiceRemoteViews;
mForegroundServiceNotify.bigContentView = mForegroundServiceRemoteViews;
// 4. 启动前台服务必须调用否则Service易被回收
service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "【前台服务通知】保活通知启动成功通知ID" + NOTIFY_ID_FOREGROUND_SERVICE);
}
/**
* 更新前台服务通知内容复用通知ID保持无铃声
*/
public void updateForegroundServiceNotify(NotificationMessage message) {
LogUtils.d(TAG, "updateForegroundServiceNotify: 执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE + " | message=" + message);
if (message == null || mNotificationManager == null) {
LogUtils.e(TAG, "updateForegroundServiceNotify: 失败param is null | message=" + message + " | mNotificationManager=" + mNotificationManager);
return;
}
/**
* 更新前台服务保活通知内容(无需重启服务,直接刷新布局)
*/
public void updateForegroundServiceNotify(ControlCenterService service, NotificationMessage msg) {
LogUtils.d(TAG, "【前台服务通知】开始更新保活通知,新内容:" + msg.getContent());
if (mForegroundServiceNotify == null || mForegroundServiceRemoteViews == null) {
LogUtils.e(TAG, "【前台服务通知】更新失败通知对象未初始化先调用startForegroundServiceNotify");
return;
}
// 更新自定义布局数据
initForegroundServiceRemoteViews(service, msg);
mForegroundServiceNotify.contentView = mForegroundServiceRemoteViews;
mForegroundServiceNotify.bigContentView = mForegroundServiceRemoteViews;
// 发送更新
mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "【前台服务通知】保活通知更新成功");
}
mForegroundServiceNotify = buildForegroundNotification(message);
if (mForegroundServiceNotify == null) {
LogUtils.e(TAG, "updateForegroundServiceNotify: 失败:构建通知为空");
return;
}
// ====================== 场景2电量提醒通知支持自定义布局+更新+单独取消)======================
/**
* 初始化电量提醒通知自定义布局RemoteViews支持充电/耗电切换)
*/
private void initBatteryRemindRemoteViews(ControlCenterService service, NotificationMessage msg) {
LogUtils.d(TAG, "【布局初始化】开始初始化电量提醒布局,提醒类型:" + msg.getRemindMSG());
mBatteryRemindRemoteViews = new RemoteViews(service.getPackageName(), R.layout.view_remindnotification);
mBatteryRemindRemoteViews.setTextViewText(R.id.viewremindnotificationTextView1, msg.getTitle());
mBatteryRemindRemoteViews.setImageViewResource(R.id.remoteviewImageView1, R.drawable.ic_launcher);
// 切换布局(+:充电提醒,-:耗电提醒)
String remindType = msg.getRemindMSG() != null ? msg.getRemindMSG().trim() : "";
if ("+".equals(remindType)) {
mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewUsege, View.GONE);
mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewCharge, View.VISIBLE);
LogUtils.d(TAG, "【布局初始化】电量提醒布局切换:充电提醒");
} else if ("-".equals(remindType)) {
mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewCharge, View.GONE);
mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewUsege, View.VISIBLE);
LogUtils.d(TAG, "【布局初始化】电量提醒布局切换:耗电提醒");
} else {
mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewCharge, View.GONE);
mBatteryRemindRemoteViews.setViewVisibility(R.id.remoteviewUsege, View.VISIBLE);
LogUtils.w(TAG, "【布局初始化】未知电量提醒类型:" + remindType);
}
}
try {
mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
LogUtils.d(TAG, "updateForegroundServiceNotify: 成功");
} catch (Exception e) {
LogUtils.e(TAG, "updateForegroundServiceNotify: 异常", e);
}
}
/**
* 初始化电量提醒通知仅构建不发送配合update触发提醒
*/
public void initBatteryRemindNotify(ControlCenterService service, NotificationMessage msg) {
LogUtils.d(TAG, "【电量提醒通知】开始初始化提醒通知,标题:" + msg.getTitle());
if (service == null || msg == null) {
LogUtils.e(TAG, "【电量提醒通知】初始化失败:Service/NotificationMessage为空");
return;
}
// 1. 构建固定跳转Intent统一跳MainActivity
PendingIntent pendingIntent = buildFixedPendingIntent();
// 2. 构建基础通知
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(service, CHANNEL_ID_BATTERY_REMIND);
} else {
builder = new Notification.Builder(service);
}
mBatteryRemindNotify = builder
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(service.getResources(), R.drawable.ic_launcher))
.setContentTitle(msg.getTitle())
.setContentText(msg.getContent())
.setWhen(System.currentTimeMillis())
.setColor(Color.parseColor("#F00606"))
.setContentIntent(pendingIntent)
.setAutoCancel(true) // 点击取消
.build();
// 3. 初始化自定义布局
initBatteryRemindRemoteViews(service, msg);
mBatteryRemindNotify.contentView = mBatteryRemindRemoteViews;
mBatteryRemindNotify.bigContentView = mBatteryRemindRemoteViews;
LogUtils.d(TAG, "【电量提醒通知】初始化完成");
}
/**
* 取消前台服务通知Service销毁时调用
*/
public void cancelForegroundServiceNotify() {
LogUtils.d(TAG, "cancelForegroundServiceNotify: 执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE);
cancelNotification(NOTIFY_ID_FOREGROUND_SERVICE);
mForegroundServiceNotify = null; // 置空释放
LogUtils.d(TAG, "cancelForegroundServiceNotify: 成功");
}
/**
* 发送/更新电量提醒通知(初始化后调用,触发强提醒)
*/
public void sendOrUpdateBatteryRemindNotify() {
LogUtils.d(TAG, "【电量提醒通知】开始发送/更新提醒");
if (mBatteryRemindNotify == null || mBatteryRemindRemoteViews == null) {
LogUtils.e(TAG, "【电量提醒通知】发送失败通知未初始化先调用initBatteryRemindNotify");
return;
}
mNotificationManager.notify(NOTIFY_ID_BATTERY_REMIND, mBatteryRemindNotify);
LogUtils.d(TAG, "【电量提醒通知】发送/更新成功通知ID" + NOTIFY_ID_BATTERY_REMIND);
}
// ================================== 对外核心方法(电池提醒通知:发送)=================================
/**
* 发送电池提醒通知(系统默认铃声,无振动)
*/
public void showRemindNotification(Context context, NotificationMessage message) {
LogUtils.d(TAG, "showRemindNotification: 执行 | notifyId=" + NOTIFY_ID_REMIND + " | context=" + context + " | message=" + message);
if (context == null || message == null || mNotificationManager == null) {
LogUtils.e(TAG, "showRemindNotification: 失败param is null | context=" + context + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
return;
}
/**
* 单独取消电量提醒通知(静态方法,外部可直接调用,无需实例化)
*/
public static void cancelBatteryRemindNotify(Context context) {
LogUtils.d(TAG, "【电量提醒通知】开始取消提醒通知ID" + NOTIFY_ID_BATTERY_REMIND);
if (context == null) {
LogUtils.e(TAG, "【电量提醒通知】取消失败Context为空");
return;
}
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(NOTIFY_ID_BATTERY_REMIND);
LogUtils.d(TAG, "【电量提醒通知】取消成功");
}
Notification remindNotify = buildRemindNotification(context, message);
if (remindNotify == null) {
LogUtils.e(TAG, "showRemindNotification: 失败:构建通知为空");
return;
}
// ====================== 场景3通用临时通知简单文本自动取消无需自定义布局======================
/**
* 显示通用临时通知普通告警仅震动自动取消统一跳转MainActivity
* @param title 通知标题
* @param content 通知内容
*/
public void showTempAlertNotify(String title, String content) {
LogUtils.d(TAG, "【通用临时通知】开始构建,标题:" + title + ",内容:" + content);
if (title == null || content == null) {
LogUtils.e(TAG, "【通用临时通知】构建失败:标题/内容为空");
return;
}
// 1. 构建固定跳转Intent统一跳MainActivity
PendingIntent pendingIntent = buildFixedPendingIntent();
// 2. 用NotificationCompat.Builder兼容所有版本简化逻辑
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMP_ALERT)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher))
try {
mNotificationManager.notify(NOTIFY_ID_REMIND, remindNotify);
LogUtils.d(TAG, "showRemindNotification: 成功");
} catch (Exception e) {
LogUtils.e(TAG, "showRemindNotification: 异常", e);
}
}
// ================================== 对外核心方法(应用配置信息通知:发送)=================================
/**
* 发送应用配置信息通知(新增:低优先级无铃声,仅提示不打扰)
*/
public void showConfigNotification(Context context, NotificationMessage message) {
LogUtils.d(TAG, "showConfigNotification: 执行 | notifyId=" + NOTIFY_ID_CONFIG + " | context=" + context + " | message=" + message);
if (context == null || message == null || mNotificationManager == null) {
LogUtils.e(TAG, "showConfigNotification: 失败param is null | context=" + context + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
return;
}
Notification configNotify = buildConfigNotification(context, message);
if (configNotify == null) {
LogUtils.e(TAG, "showConfigNotification: 失败:构建通知为空");
return;
}
try {
mNotificationManager.notify(NOTIFY_ID_CONFIG, configNotify);
LogUtils.d(TAG, "showConfigNotification: 成功");
} catch (Exception e) {
LogUtils.e(TAG, "showConfigNotification: 异常", e);
}
}
// ================================== 对外工具方法(通知取消:单个/全部)=================================
/**
* 取消指定ID的通知
*/
public void cancelNotification(int notifyId) {
LogUtils.d(TAG, "cancelNotification: 执行 | notifyId=" + notifyId);
if (mNotificationManager == null) {
LogUtils.e(TAG, "cancelNotification: 失败NotificationManager is null");
return;
}
try {
mNotificationManager.cancel(notifyId);
LogUtils.d(TAG, "cancelNotification: 成功 | notifyId=" + notifyId);
} catch (Exception e) {
LogUtils.e(TAG, "cancelNotification: 异常 | notifyId=" + notifyId, e);
}
}
/**
* 取消所有通知(兜底场景使用)
*/
public void cancelAllNotifications() {
LogUtils.d(TAG, "cancelAllNotifications: 执行");
if (mNotificationManager == null) {
LogUtils.e(TAG, "cancelAllNotifications: 失败NotificationManager is null");
return;
}
try {
mNotificationManager.cancelAll();
LogUtils.d(TAG, "cancelAllNotifications: 成功");
} catch (Exception e) {
LogUtils.e(TAG, "cancelAllNotifications: 异常", e);
}
}
// ================================== 内部辅助方法(通知构建:前台服务通知)=================================
/**
* 构建前台服务通知(全版本无铃声+无振动)
*/
private Notification buildForegroundNotification(NotificationMessage message) {
LogUtils.d(TAG, "buildForegroundNotification: 执行 | message=" + message);
if (message == null || mContext == null) {
LogUtils.e(TAG, "buildForegroundNotification: 失败param is null | message=" + message + " | mContext=" + mContext);
return null;
}
// 内容兜底
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : FOREGROUND_NOTIFY_TITLE_DEFAULT;
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : FOREGROUND_NOTIFY_CONTENT_DEFAULT;
LogUtils.d(TAG, "buildForegroundNotification: 内容兜底完成 | title=" + title + " | content=" + content);
Notification.Builder builder;
// API分级构建
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// API26+:绑定前台渠道(渠道已配置无铃声)
builder = new Notification.Builder(mContext, CHANNEL_ID_FOREGROUND);
LogUtils.d(TAG, "buildForegroundNotification: 使用API26+渠道构建");
} else {
// API<26直接构建手动禁用铃声振动
builder = new Notification.Builder(mContext);
builder.setSound(null);
builder.setVibrate(new long[]{0});
builder.setDefaults(0);
LogUtils.d(TAG, "buildForegroundNotification: 使用API<26手动配置");
}
// 通用配置
builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(false)
.setOngoing(true) // 不可手动关闭
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setVibrate(VIBRATE_PATTERN);
// 3. 发送通知
Notification notification = builder.build();
mNotificationManager.notify(NOTIFY_ID_TEMP_ALERT, notification);
LogUtils.d(TAG, "【通用临时通知】显示成功通知ID" + NOTIFY_ID_TEMP_ALERT);
}
.setContentIntent(createJumpPendingIntent(mContext, PENDING_INTENT_REQUEST_CODE_FOREGROUND));
// ====================== 场景4自定义布局通知灵活扩展支持复杂样式======================
/**
* 显示自定义布局通知(支持普通布局+大布局通用所有场景统一跳转MainActivity
* @param contentView 普通自定义布局(必填)
* @param bigContentView 下拉大布局(可选)
*/
public void showCustomLayoutNotify(RemoteViews contentView, RemoteViews bigContentView) {
LogUtils.d(TAG, "【自定义布局通知】开始构建布局ID" + (contentView != null ? contentView.getLayoutId() : null));
if (contentView == null) {
LogUtils.e(TAG, "【自定义布局通知】构建失败普通布局contentView为空");
return;
}
// 1. 构建固定跳转Intent统一跳MainActivity
PendingIntent pendingIntent = buildFixedPendingIntent();
// 2. 构建自定义布局通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID_TEMP_ALERT)
.setSmallIcon(R.drawable.ic_launcher) // 必传,不可省略
.setContentIntent(pendingIntent)
.setContent(contentView)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
// 添加大布局(可选)
if (bigContentView != null) {
builder.setCustomBigContentView(bigContentView);
LogUtils.d(TAG, "【自定义布局通知】已添加下拉大布局布局ID" + bigContentView.getLayoutId());
}
// 3. 发送通知
Notification notification = builder.build();
mNotificationManager.notify(NOTIFY_ID_CUSTOM_LAYOUT, notification);
LogUtils.d(TAG, "【自定义布局通知】显示成功通知ID" + NOTIFY_ID_CUSTOM_LAYOUT);
}
// API21+ 新增大图标+主题色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setLargeIcon(getAppIcon(mContext))
.setColor(mContext.getResources().getColor(R.color.colorPrimary))
.setPriority(Notification.PRIORITY_LOW);
LogUtils.d(TAG, "buildForegroundNotification: 补充API21+配置");
}
// ====================== 通知取消工具(支持精准取消/全取消)======================
/**
* 取消指定ID的通知精准取消灵活控制
*/
public void cancelNotifyById(int notifyId) {
LogUtils.d(TAG, "【通知管理】开始取消通知ID" + notifyId);
mNotificationManager.cancel(notifyId);
LogUtils.d(TAG, "【通知管理】通知取消成功ID" + notifyId);
}
Notification notification = builder.build();
LogUtils.d(TAG, "buildForegroundNotification: 成功构建前台通知");
return notification;
}
/**
* 取消所有通知(谨慎使用,会清除所有场景的通知)
*/
public void cancelAllNotifies() {
LogUtils.d(TAG, "【通知管理】开始取消所有通知");
mNotificationManager.cancelAll();
LogUtils.d(TAG, "【通知管理】所有通知取消完成");
}
// ================================== 内部辅助方法(通知构建:电池提醒通知)=================================
/**
* 构建电池提醒通知(全版本系统默认铃声+无振动)
*/
private Notification buildRemindNotification(Context context, NotificationMessage message) {
LogUtils.d(TAG, "buildRemindNotification: 执行 | context=" + context + " | message=" + message);
if (context == null || message == null) {
LogUtils.e(TAG, "buildRemindNotification: 失败param is null | context=" + context + " | message=" + message);
return null;
}
// 内容兜底
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : REMIND_NOTIFY_TITLE_DEFAULT;
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : REMIND_NOTIFY_CONTENT_DEFAULT;
LogUtils.d(TAG, "buildRemindNotification: 内容兜底完成 | title=" + title + " | content=" + content);
Notification.Builder builder;
// API分级构建
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// API26+:绑定提醒渠道(渠道已配置默认铃声)
builder = new Notification.Builder(context, CHANNEL_ID_REMIND);
LogUtils.d(TAG, "buildRemindNotification: 使用API26+渠道构建");
} else {
// API<26手动配置默认铃声关闭振动
builder = new Notification.Builder(context);
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI) // 显式默认铃声
.setVibrate(new long[]{0})
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_SOUND);
LogUtils.d(TAG, "buildRemindNotification: 使用API<26手动配置");
}
// 通用配置
builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(title)
.setContentText(content)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), Notification.AUDIO_ATTRIBUTES_DEFAULT)
.setAutoCancel(true) // 点击关闭
.setOngoing(false)
.setWhen(System.currentTimeMillis())
.setContentIntent(createJumpPendingIntent(context, PENDING_INTENT_REQUEST_CODE_REMIND));
// API21+ 新增大图标+主题色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setLargeIcon(getAppIcon(context))
.setColor(context.getResources().getColor(R.color.colorPrimary))
.setPriority(Notification.PRIORITY_DEFAULT);
LogUtils.d(TAG, "buildRemindNotification: 补充API21+配置");
}
Notification notification = builder.build();
LogUtils.d(TAG, "buildRemindNotification: 成功构建提醒通知");
return notification;
}
// ================================== 内部辅助方法(通知构建:应用配置信息通知)=================================
/**
* 构建应用配置信息通知(新增:全版本无铃声+无振动,低优先级)
*/
private Notification buildConfigNotification(Context context, NotificationMessage message) {
LogUtils.d(TAG, "buildConfigNotification: 执行 | context=" + context + " | message=" + message);
if (context == null || message == null) {
LogUtils.e(TAG, "buildConfigNotification: 失败param is null | context=" + context + " | message=" + message);
return null;
}
// 内容兜底
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : CONFIG_NOTIFY_TITLE_DEFAULT;
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : CONFIG_NOTIFY_CONTENT_DEFAULT;
LogUtils.d(TAG, "buildConfigNotification: 内容兜底完成 | title=" + title + " | content=" + content);
Notification.Builder builder;
// API分级构建
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// API26+:绑定配置渠道(渠道已配置无铃声)
builder = new Notification.Builder(context, CHANNEL_ID_CONFIG);
LogUtils.d(TAG, "buildConfigNotification: 使用API26+渠道构建");
} else {
// API<26直接构建手动禁用铃声振动
builder = new Notification.Builder(context);
builder.setSound(null);
builder.setVibrate(new long[]{0});
builder.setDefaults(0);
LogUtils.d(TAG, "buildConfigNotification: 使用API<26手动配置");
}
// 通用配置
builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true) // 点击关闭
.setOngoing(false)
.setWhen(System.currentTimeMillis())
.setContentIntent(createJumpPendingIntent(context, PENDING_INTENT_REQUEST_CODE_CONFIG));
// API21+ 新增大图标+主题色
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setLargeIcon(getAppIcon(context))
.setColor(context.getResources().getColor(R.color.colorPrimary))
.setPriority(Notification.PRIORITY_MIN); // 最低优先级
LogUtils.d(TAG, "buildConfigNotification: 补充API21+配置");
}
Notification notification = builder.build();
LogUtils.d(TAG, "buildConfigNotification: 成功构建配置信息通知");
return notification;
}
// ================================== 内部辅助方法创建跳转PendingIntentAPI30安全适配=================================
/**
* 创建跳转MainActivity的PendingIntentAPI23+ 添加IMMUTABLE标记避免安全异常
*/
private PendingIntent createJumpPendingIntent(Context context, int requestCode) {
LogUtils.d(TAG, "createJumpPendingIntent: 执行 | requestCode=" + requestCode + " | context=" + context);
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
LogUtils.d(TAG, "createJumpPendingIntent: 跳转Intent配置完成");
// API23+ 必需添加IMMUTABLE适配API30安全规范
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags |= PendingIntent.FLAG_IMMUTABLE;
LogUtils.d(TAG, "createJumpPendingIntent: 添加FLAG_IMMUTABLE标记API23+");
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, flags);
LogUtils.d(TAG, "createJumpPendingIntent: 成功 | requestCode=" + requestCode);
return pendingIntent;
}
// ================================== 内部辅助方法获取APP图标异常兜底=================================
/**
* 获取APP图标失败返回默认图标
*/
private Bitmap getAppIcon(Context context) {
LogUtils.d(TAG, "getAppIcon: 执行 | context=" + context);
try {
PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
Bitmap appIcon = BitmapFactory.decodeResource(context.getResources(), pkgInfo.applicationInfo.icon);
LogUtils.d(TAG, "getAppIcon: 成功:获取应用图标");
return appIcon;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "getAppIcon: 异常:获取应用图标失败,使用默认图标", e);
return BitmapFactory.decodeResource(context.getResources(), NOTIFICATION_DEFAULT_ICON);
}
}
// ================================== 资源释放方法(避免内存泄漏)=================================
/**
* 释放资源,销毁时调用
*/
public void release() {
LogUtils.d(TAG, "release: 执行资源释放");
cancelForegroundServiceNotify();
mNotificationManager = null;
mContext = null;
LogUtils.d(TAG, "release: 成功:所有资源已释放");
}
// ================================== 对外 getter 方法(仅前台通知实例,只读)=================================
public Notification getForegroundServiceNotify() {
return mForegroundServiceNotify;
}
}

View File

@@ -15,6 +15,7 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import java.io.File;
/**
@@ -289,5 +290,6 @@ public class BackgroundView extends RelativeLayout {
super.onSizeChanged(w, h, oldw, oldh);
adjustImageViewSize(); // 尺寸变化时重新调整
}
}

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,835 @@
package cc.winboll.studio.powerbell.views;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
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.Switch;
import android.widget.TextView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
import cc.winboll.studio.powerbell.services.ControlCenterService;
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 13:14
* @Describe 主页面核心视图封装类:统一管理视图绑定、数据更新、事件监听,解耦 Activity 逻辑
* 适配Java7 | API30 | 小米手机,优化性能与资源回收,杜绝内存泄漏,配置变更确认对话框
* 新增:拖动进度条时实时预览 sbUsageReminder 与 sbChargeReminder 比值
*/
public class MainContentView {
// ======================== 静态常量(置顶,唯一标识)========================
public static final String TAG = "MainContentView";
// 变更类型常量(区分不同控件,精准处理逻辑)
private static final int CHANGE_TYPE_CHARGE_SWITCH = 1;
private static final int CHANGE_TYPE_USAGE_SWITCH = 2;
private static final int CHANGE_TYPE_SERVICE_SWITCH = 3;
private static final int CHANGE_TYPE_CHARGE_SEEKBAR = 4;
private static final int CHANGE_TYPE_USAGE_SEEKBAR = 5;
// ======================== 内部静态类(临时数据载体,避免外部依赖)========================
/**
* 临时配置数据实体(缓存变更信息,取消时恢复)
*/
private static class TempConfigData {
int changeType;
boolean originalBooleanValue;
int originalIntValue;
boolean newBooleanValue;
int newIntValue;
// 构造方法(开关类型)
TempConfigData(int changeType, boolean originalValue, boolean newValue) {
this.changeType = changeType;
this.originalBooleanValue = originalValue;
this.newBooleanValue = newValue;
}
// 构造方法(进度条类型)
TempConfigData(int changeType, int originalValue, int newValue) {
this.changeType = changeType;
this.originalIntValue = originalValue;
this.newIntValue = newValue;
}
}
// ======================== 事件回调接口(解耦视图与业务,提升扩展性)========================
public interface OnViewActionListener {
void onChargeReminderSwitchChanged(boolean isChecked);
void onUsageReminderSwitchChanged(boolean isChecked);
void onServiceSwitchChanged(boolean isChecked);
void onChargeReminderProgressChanged(int progress);
void onUsageReminderProgressChanged(int progress);
}
// ======================== 成员变量(按功能分类,避免混乱)========================
// 外部依赖实例(生命周期关联,优先声明)
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;
// 进度条控件(使用自定义 VerticalSeekBar
public VerticalSeekBar sbChargeReminder;
public VerticalSeekBar sbUsageReminder;
// 图标显示控件
public ImageView ivCurrentBattery;
public ImageView ivChargeReminderBattery;
public ImageView ivUsageReminderBattery;
// 进度缓存(用于实时计算比值,避免频繁调用 getProgress()
private int mCurrentChargeProgress;
private int mCurrentUsageProgress;
// 内部复用资源(避免重复创建,优化性能)
private BatteryDrawable mCurrentBatteryDrawable;
private BatteryDrawable mChargeReminderBatteryDrawable;
private BatteryDrawable mUsageReminderBatteryDrawable;
// 配置变更确认对话框(单例复用,避免重复创建)
private AlertDialog mConfigConfirmDialog;
private AlertDialog.Builder mDialogBuilder;
// 临时存储变更数据(对话框确认前缓存,取消时恢复)
private TempConfigData mTempConfigData;
// 对话框状态锁(避免快速点击重复弹窗)
private boolean isDialogShowing = false;
// ======================== 构造方法(初始化入口,逻辑闭环)========================
public MainContentView(Context context, View rootView, OnViewActionListener actionListener) {
LogUtils.d(TAG, "MainContentView() | context=" + context + " | rootView=" + rootView + " | actionListener=" + actionListener);
// 初始化外部依赖
this.mContext = context;
this.mActionListener = actionListener;
this.mAppConfigUtils = AppConfigUtils.getInstance(context.getApplicationContext());
// 执行核心初始化流程(按顺序执行,避免依赖空指针)
bindViews(rootView);
initBatteryDrawables();
initConfirmDialog();
bindViewListeners();
LogUtils.d(TAG, "MainContentView 初始化完成");
}
// ======================== 私有初始化方法(封装内部逻辑,仅暴露入口)========================
/**
* 绑定视图控件(显式强转适配 Java7适配 API30 视图加载机制)
*/
private void bindViews(View rootView) {
LogUtils.d(TAG, "bindViews() | rootView=" + rootView);
// 基础布局绑定
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);
// 进度条控件绑定(自定义 VerticalSeekBar
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);
// 初始化进度缓存(从配置读取初始值)
mCurrentChargeProgress = mAppConfigUtils.getChargeReminderValue();
mCurrentUsageProgress = mAppConfigUtils.getUsageReminderValue();
// 关键视图绑定校验(仅保留核心控件错误日志,精简冗余)
if (mainLayout == null) LogUtils.e(TAG, "mainLayout 绑定失败");
if (backgroundView == null) LogUtils.e(TAG, "backgroundView 绑定失败");
}
/**
* 初始化电池 Drawable集成 BatteryDrawable默认能量风格适配小米机型渲染
*/
private void initBatteryDrawables() {
LogUtils.d(TAG, "initBatteryDrawables()");
// 当前电量 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);
}
/**
* 初始化配置变更确认对话框(核心优化:保存 Builder 实例,解决消息不生效问题)
*/
private void initConfirmDialog() {
LogUtils.d(TAG, "initConfirmDialog()");
if (mContext == null) {
LogUtils.e(TAG, "Context 为空,初始化失败");
return;
}
// 1. 初始化 Builder核心后续通过 Builder 更新消息)
mDialogBuilder = new AlertDialog.Builder(mContext);
mDialogBuilder.setTitle("配置变更确认");
mDialogBuilder.setMessage("是否确认修改当前配置?");
// 确定按钮:保存配置+回调+更新视图
mDialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
confirmConfigChange();
dialog.dismiss();
}
});
// 取消按钮:恢复原始配置(补充物理取消按钮,提升用户体验)
mDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cancelConfigChange();
dialog.dismiss();
}
});
// 对话框外部点击监听:关闭对话框+恢复原始配置
mDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
cancelConfigChange();
dialog.dismiss();
}
});
// 2. 初始化对话框实例(设置可取消,支持外部点击关闭)
mConfigConfirmDialog = mDialogBuilder.create();
mConfigConfirmDialog.setCancelable(true);
mConfigConfirmDialog.setCanceledOnTouchOutside(true);
}
/**
* 绑定视图事件监听Java7 显式实现接口,适配 API30 事件分发,修复进度条弹窗失效)
*/
private void bindViewListeners() {
LogUtils.d(TAG, "bindViewListeners()");
// 依赖校验,避免空指针
if (mAppConfigUtils == null || mActionListener == null || mConfigConfirmDialog == null) {
LogUtils.e(TAG, "依赖实例为空,跳过监听绑定");
return;
}
// 充电提醒进度条监听(使用 VerticalSeekBar 专属接口确保弹窗100%触发)
if (sbChargeReminder != null) {
// 原有:触摸抬起/取消监听(用于配置确认)
sbChargeReminder.setOnVerticalSeekBarTouchListener(new VerticalSeekBar.OnVerticalSeekBarTouchListener() {
@Override
public void onTouchUp(VerticalSeekBar seekBar, int progress) {
int originalValue = mAppConfigUtils.getChargeReminderValue();
// 进度无变化,不处理
if (originalValue == progress) {
LogUtils.d(TAG, "ChargeReminderSeekBar: 进度无变化,跳过");
return;
}
// 缓存变更数据,显示确认对话框
mTempConfigData = new TempConfigData(CHANGE_TYPE_CHARGE_SEEKBAR, originalValue, progress);
updateDialogMessageByChangeType();
showConfigConfirmDialog();
LogUtils.d(TAG, "ChargeReminderSeekBar触摸抬起 | 原始值=" + originalValue + " | 新进度=" + progress);
}
@Override
public void onTouchCancel(VerticalSeekBar seekBar, int progress) {
// 触摸取消回滚视图进度UI 与配置保持一致)
int originalValue = mAppConfigUtils.getChargeReminderValue();
if (tvChargeReminderValue != null && mChargeReminderBatteryDrawable != null && ivChargeReminderBattery != null) {
mChargeReminderBatteryDrawable.setBatteryValue(originalValue);
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
tvChargeReminderValue.setText(originalValue + "%");
}
seekBar.setProgress(originalValue);
// 恢复进度缓存
mCurrentChargeProgress = originalValue;
LogUtils.d(TAG, "ChargeReminderSeekBar触摸取消 | 进度回滚至=" + originalValue);
}
});
// 新增:实时进度变化监听(用于比值预览)
sbChargeReminder.setOnVerticalSeekBarChangeListener(new VerticalSeekBar.OnVerticalSeekBarChangeListener() {
@Override
public void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mCurrentChargeProgress = progress;
// 同步更新进度文本和电池图标保持UI一致性
if (tvChargeReminderValue != null && mChargeReminderBatteryDrawable != null && ivChargeReminderBattery != null) {
mChargeReminderBatteryDrawable.setBatteryValue(progress);
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
tvChargeReminderValue.setText(progress + "%");
}
}
}
@Override
public void onStartTrackingTouch(VerticalSeekBar seekBar) {}
@Override
public void onStopTrackingTouch(VerticalSeekBar seekBar) {}
});
LogUtils.d(TAG, "充电提醒进度条专属监听绑定完成");
}
// 充电提醒开关监听
if (cbEnableChargeReminder != null) {
cbEnableChargeReminder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean originalValue = mAppConfigUtils.isChargeReminderEnabled();
boolean newValue = cbEnableChargeReminder.isChecked();
// 状态无变化,不处理
if (originalValue == newValue) return;
// 缓存变更数据,显示确认对话框
mTempConfigData = new TempConfigData(CHANGE_TYPE_CHARGE_SWITCH, originalValue, newValue);
updateDialogMessageByChangeType();
showConfigConfirmDialog();
LogUtils.d(TAG, "cbEnableChargeReminder点击 | 原始值=" + originalValue + " | 变更后=" + newValue);
}
});
LogUtils.d(TAG, "充电提醒开关监听绑定完成");
}
// 耗电提醒进度条监听(使用 VerticalSeekBar 专属接口确保弹窗100%触发)
if (sbUsageReminder != null) {
// 原有:触摸抬起/取消监听(用于配置确认)
sbUsageReminder.setOnVerticalSeekBarTouchListener(new VerticalSeekBar.OnVerticalSeekBarTouchListener() {
@Override
public void onTouchUp(VerticalSeekBar seekBar, int progress) {
int originalValue = mAppConfigUtils.getUsageReminderValue();
// 进度无变化,不处理
if (originalValue == progress) {
LogUtils.d(TAG, "UsageReminderSeekBar: 进度无变化,跳过");
return;
}
// 缓存变更数据,显示确认对话框
mTempConfigData = new TempConfigData(CHANGE_TYPE_USAGE_SEEKBAR, originalValue, progress);
updateDialogMessageByChangeType();
showConfigConfirmDialog();
LogUtils.d(TAG, "UsageReminderSeekBar触摸抬起 | 原始值=" + originalValue + " | 新进度=" + progress);
}
@Override
public void onTouchCancel(VerticalSeekBar seekBar, int progress) {
// 触摸取消回滚视图进度UI 与配置保持一致)
int originalValue = mAppConfigUtils.getUsageReminderValue();
if (tvUsageReminderValue != null && mUsageReminderBatteryDrawable != null && ivUsageReminderBattery != null) {
mUsageReminderBatteryDrawable.setBatteryValue(originalValue);
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
tvUsageReminderValue.setText(originalValue + "%");
}
seekBar.setProgress(originalValue);
// 恢复进度缓存
mCurrentUsageProgress = originalValue;
LogUtils.d(TAG, "UsageReminderSeekBar触摸取消 | 进度回滚至=" + originalValue);
}
});
// 新增:实时进度变化监听(用于比值预览)
sbUsageReminder.setOnVerticalSeekBarChangeListener(new VerticalSeekBar.OnVerticalSeekBarChangeListener() {
@Override
public void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mCurrentUsageProgress = progress;
// 同步更新进度文本和电池图标保持UI一致性
if (tvUsageReminderValue != null && mUsageReminderBatteryDrawable != null && ivUsageReminderBattery != null) {
mUsageReminderBatteryDrawable.setBatteryValue(progress);
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
tvUsageReminderValue.setText(progress + "%");
}
}
}
@Override
public void onStartTrackingTouch(VerticalSeekBar seekBar) {}
@Override
public void onStopTrackingTouch(VerticalSeekBar seekBar) {}
});
LogUtils.d(TAG, "耗电提醒进度条专属监听绑定完成");
}
// 耗电提醒开关监听
if (cbEnableUsageReminder != null) {
cbEnableUsageReminder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean originalValue = mAppConfigUtils.isUsageReminderEnabled();
boolean newValue = cbEnableUsageReminder.isChecked();
// 状态无变化,不处理
if (originalValue == newValue) return;
// 缓存变更数据,显示确认对话框
mTempConfigData = new TempConfigData(CHANGE_TYPE_USAGE_SWITCH, originalValue, newValue);
updateDialogMessageByChangeType();
showConfigConfirmDialog();
LogUtils.d(TAG, "cbEnableUsageReminder点击 | 原始值=" + originalValue + " | 变更后=" + newValue);
}
});
LogUtils.d(TAG, "耗电提醒开关监听绑定完成");
}
// 服务总开关监听(核心优化:逻辑与其他控件完全对齐)
if (swEnableService != null) {
swEnableService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 从服务控制Bean读取原始状态确保与实际一致
boolean originalValue = getServiceEnableState();
boolean newValue = ((Switch) v).isChecked();
// 状态无变化,不处理
if (originalValue == newValue) return;
// 缓存变更数据
mTempConfigData = new TempConfigData(CHANGE_TYPE_SERVICE_SWITCH, originalValue, newValue);
// 更新差异化提示语
updateDialogMessageByChangeType();
// 显示确认对话框
showConfigConfirmDialog();
LogUtils.d(TAG, "swEnableService点击 | 原始值=" + originalValue + " | 变更后=" + newValue);
}
});
LogUtils.d(TAG, "服务总开关监听绑定完成");
}
LogUtils.d(TAG, "所有事件监听绑定完成");
}
// ======================== 对外暴露核心方法(业务入口,精简参数,明确职责)========================
/**
* 更新所有视图数据(从配置读取数据,统一刷新 UI适配 API30 视图更新规范)
* @param frameDrawable 进度条背景 Drawable外部传入适配主题切换
*/
public void updateViewData(Drawable frameDrawable) {
LogUtils.d(TAG, "updateViewData() | frameDrawable=" + frameDrawable);
if (mAppConfigUtils == null) {
LogUtils.e(TAG, "AppConfigUtils 为空,跳过更新");
return;
}
// 一次读取所有配置参数,减少工具类调用,提升性能
int chargeVal = mAppConfigUtils.getChargeReminderValue();
int usageVal = mAppConfigUtils.getUsageReminderValue();
int currentVal = mAppConfigUtils.getCurrentBatteryValue();
boolean chargeEnable = mAppConfigUtils.isChargeReminderEnabled();
boolean usageEnable = mAppConfigUtils.isUsageReminderEnabled();
// 从服务控制Bean读取状态确保UI与实际一致
boolean serviceEnable = getServiceEnableState();
// 更新进度缓存
mCurrentChargeProgress = chargeVal;
mCurrentUsageProgress = usageVal;
LogUtils.d(TAG, "配置数据读取完成 | charge=" + chargeVal + " | usage=" + usageVal + " | current=" + currentVal + " | serviceEnable=" + serviceEnable);
// 进度条背景更新
if (frameDrawable != null) {
if (llLeftSeekBar != null) llLeftSeekBar.setBackground(frameDrawable);
if (llRightSeekBar != null) llRightSeekBar.setBackground(frameDrawable);
}
// 当前电量更新(联动 BatteryDrawable实时刷新图标
if (ivCurrentBattery != null && mCurrentBatteryDrawable != null) {
mCurrentBatteryDrawable.setBatteryValue(currentVal);
ivCurrentBattery.setImageDrawable(mCurrentBatteryDrawable);
}
if (tvCurrentBatteryValue != null) {
tvCurrentBatteryValue.setTextColor(getResourceColor(R.color.colorCurrent));
tvCurrentBatteryValue.setText(currentVal + "%");
}
// 充电提醒视图更新
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);
// 耗电提醒视图更新
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);
// 服务开关+提示文本更新(确保状态准确)
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, "所有视图数据更新完成");
}
/**
* 实时更新当前电量(单独抽离,适配电池实时监控场景,优化 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, "视图/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, "更新完成 | 校准后值=" + validValue);
}
/**
* 释放资源(主动回收,适配 API30 资源管控机制,优化小米手机内存占用)
*/
public void releaseResources() {
LogUtils.d(TAG, "releaseResources()");
// 释放对话框资源(安全销毁,避免内存泄漏)
if (mConfigConfirmDialog != null) {
if (mConfigConfirmDialog.isShowing()) {
mConfigConfirmDialog.dismiss();
}
mConfigConfirmDialog.setOnDismissListener(null);
mConfigConfirmDialog.setOnCancelListener(null);
mConfigConfirmDialog = null;
}
// 释放 Builder
mDialogBuilder = null;
// 释放临时数据
mTempConfigData = null;
// 释放 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, "所有资源释放完成");
}
/**
* 设置服务开关启用状态(外部调用,同步 UI 与服务状态,适配 Activity 视图刷新)
* @param enabled 服务启用状态
*/
public void setServiceSwitchChecked(boolean enabled) {
LogUtils.d(TAG, "setServiceSwitchChecked() | enabled=" + enabled);
if (swEnableService != null) {
swEnableService.setChecked(enabled);
}
}
/**
* 设置服务开关点击状态(外部调用,避免更新 UI 时触发重复回调)
* @param enabled 是否允许点击
*/
public void setServiceSwitchEnabled(boolean enabled) {
LogUtils.d(TAG, "setServiceSwitchEnabled() | enabled=" + enabled);
if (swEnableService != null) {
swEnableService.setEnabled(enabled);
}
}
// ======================== 内部核心逻辑方法(对话框相关,封装确认/取消逻辑)========================
/**
* 显示配置变更确认对话框(确保 Activity 处于前台,避免异常,防止重复弹窗)
*/
private void showConfigConfirmDialog() {
LogUtils.d(TAG, "showConfigConfirmDialog() | isDialogShowing=" + isDialogShowing);
// 对话框状态锁:正在显示则跳过,避免重复触发
if (isDialogShowing) {
LogUtils.d(TAG, "对话框已显示,跳过重复调用");
return;
}
// 基础校验:对话框/上下文/Builder 为空
if (mDialogBuilder == null || mContext == null) {
LogUtils.e(TAG, "对话框Builder/上下文异常,无法显示");
if (mTempConfigData != null) cancelConfigChange();
return;
}
// Activity 状态校验:避免销毁后弹窗崩溃(适配 API30
Activity activity = (Activity) mContext;
if (activity.isFinishing() || activity.isDestroyed()) {
LogUtils.e(TAG, "Activity 已销毁,无法显示对话框");
if (mTempConfigData != null) cancelConfigChange();
return;
}
// 重新构建对话框(核心:确保最新消息生效)
mConfigConfirmDialog = mDialogBuilder.create();
// 显示对话框,设置状态锁+关闭监听
mConfigConfirmDialog.show();
isDialogShowing = true;
// 对话框关闭时解锁
mConfigConfirmDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
isDialogShowing = false;
mConfigConfirmDialog.setOnDismissListener(null);
}
});
LogUtils.d(TAG, "确认对话框显示成功");
}
/**
* 确认配置变更(保存数据+回调监听+更新视图)
*/
private void confirmConfigChange() {
LogUtils.d(TAG, "confirmConfigChange() | mTempConfigData=" + mTempConfigData);
if (mTempConfigData == null || mAppConfigUtils == null || mActionListener == null) {
LogUtils.e(TAG, "依赖数据为空,确认失败");
return;
}
switch (mTempConfigData.changeType) {
// 充电提醒开关
case CHANGE_TYPE_CHARGE_SWITCH:
mAppConfigUtils.setChargeReminderEnabled(mTempConfigData.newBooleanValue);
mActionListener.onChargeReminderSwitchChanged(mTempConfigData.newBooleanValue);
LogUtils.d(TAG, "充电提醒开关确认 | 值=" + mTempConfigData.newBooleanValue);
break;
// 耗电提醒开关
case CHANGE_TYPE_USAGE_SWITCH:
mAppConfigUtils.setUsageReminderEnabled(mTempConfigData.newBooleanValue);
mActionListener.onUsageReminderSwitchChanged(mTempConfigData.newBooleanValue);
LogUtils.d(TAG, "耗电提醒开关确认 | 值=" + mTempConfigData.newBooleanValue);
break;
// 服务总开关(核心:持久化配置+触发 Activity 回调)
case CHANGE_TYPE_SERVICE_SWITCH:
// 1. 设置服务启停
if (mTempConfigData.newBooleanValue) {
ControlCenterService.startControlCenterService(mContext);
} else {
ControlCenterService.stopControlCenterService(mContext);
}
// 2. 强制触发 Activity 回调,执行服务启停逻辑
mActionListener.onServiceSwitchChanged(mTempConfigData.newBooleanValue);
LogUtils.d(TAG, "服务开关确认 | 值=" + mTempConfigData.newBooleanValue + ",已持久化配置");
break;
// 充电提醒进度条
case CHANGE_TYPE_CHARGE_SEEKBAR:
mAppConfigUtils.setChargeReminderValue(mTempConfigData.newIntValue);
mActionListener.onChargeReminderProgressChanged(mTempConfigData.newIntValue);
LogUtils.d(TAG, "充电提醒进度确认 | 值=" + mTempConfigData.newIntValue);
break;
// 耗电提醒进度条
case CHANGE_TYPE_USAGE_SEEKBAR:
mAppConfigUtils.setUsageReminderValue(mTempConfigData.newIntValue);
mActionListener.onUsageReminderProgressChanged(mTempConfigData.newIntValue);
LogUtils.d(TAG, "耗电提醒进度确认 | 值=" + mTempConfigData.newIntValue);
break;
default:
LogUtils.w(TAG, "未知变更类型,跳过");
break;
}
// 确认完成,清空临时数据
mTempConfigData = null;
}
/**
* 取消配置变更(恢复原始值+刷新视图,确保 UI 与配置一致)
*/
private void cancelConfigChange() {
LogUtils.d(TAG, "cancelConfigChange() | mTempConfigData=" + mTempConfigData);
if (mTempConfigData == null || mAppConfigUtils == null) {
LogUtils.e(TAG, "依赖数据为空,取消失败");
return;
}
switch (mTempConfigData.changeType) {
case CHANGE_TYPE_CHARGE_SWITCH:
if (cbEnableChargeReminder != null) {
cbEnableChargeReminder.setChecked(mTempConfigData.originalBooleanValue);
}
LogUtils.d(TAG, "充电提醒开关取消 | 恢复值=" + mTempConfigData.originalBooleanValue);
break;
case CHANGE_TYPE_USAGE_SWITCH:
if (cbEnableUsageReminder != null) {
cbEnableUsageReminder.setChecked(mTempConfigData.originalBooleanValue);
}
LogUtils.d(TAG, "耗电提醒开关取消 | 恢复值=" + mTempConfigData.originalBooleanValue);
break;
case CHANGE_TYPE_SERVICE_SWITCH:
if (swEnableService != null) {
swEnableService.setChecked(mTempConfigData.originalBooleanValue);
}
LogUtils.d(TAG, "服务开关取消 | 恢复值=" + mTempConfigData.originalBooleanValue);
break;
case CHANGE_TYPE_CHARGE_SEEKBAR:
if (sbChargeReminder != null) {
sbChargeReminder.setProgress(mTempConfigData.originalIntValue);
}
if (tvChargeReminderValue != null && mChargeReminderBatteryDrawable != null && ivChargeReminderBattery != null) {
mChargeReminderBatteryDrawable.setBatteryValue(mTempConfigData.originalIntValue);
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
tvChargeReminderValue.setText(mTempConfigData.originalIntValue + "%");
}
LogUtils.d(TAG, "充电提醒进度取消 | 恢复值=" + mTempConfigData.originalIntValue);
break;
case CHANGE_TYPE_USAGE_SEEKBAR:
if (sbUsageReminder != null) {
sbUsageReminder.setProgress(mTempConfigData.originalIntValue);
}
if (tvUsageReminderValue != null && mUsageReminderBatteryDrawable != null && ivUsageReminderBattery != null) {
mUsageReminderBatteryDrawable.setBatteryValue(mTempConfigData.originalIntValue);
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
tvUsageReminderValue.setText(mTempConfigData.originalIntValue + "%");
}
LogUtils.d(TAG, "耗电提醒进度取消 | 恢复值=" + mTempConfigData.originalIntValue);
break;
default:
LogUtils.w(TAG, "未知变更类型,跳过");
break;
}
// 取消完成,清空临时数据
mTempConfigData = null;
}
/**
* 根据变更类型更新对话框提示语(核心优化:通过 Builder 更新,确保生效)
*/
private void updateDialogMessageByChangeType() {
LogUtils.d(TAG, "updateDialogMessageByChangeType() | mTempConfigData=" + mTempConfigData);
if (mDialogBuilder == null || mTempConfigData == null) return;
String message;
if (mTempConfigData.changeType == CHANGE_TYPE_SERVICE_SWITCH) {
// 服务开关差异化提示语
message = mTempConfigData.newBooleanValue ?
"启用服务后,将后台持续监控电池状态,是否确认?" :
"禁用服务后,电池监控功能将停止,是否确认?";
} else {
// 普通配置默认提示语
message = "是否确认修改当前配置?";
}
// 通过 Builder 设置消息,确保弹窗显示最新内容
mDialogBuilder.setMessage(message);
}
// ======================== 内部工具方法(封装重复逻辑,提升复用性)========================
/**
* 实时计算并更新比值预览sbUsageReminder / sbChargeReminder
* 处理除数为0的情况避免崩溃
*/
// private void updateRatioPreview() {
// if (mTvRatioPreview == null) return;
// float ratio;
// // 处理除数为0充电进度为0时显示0可根据需求改为“--”)
// if (mCurrentChargeProgress == 0) {
// ratio = 0.0f;
// } else {
// ratio = (float) mCurrentUsageProgress / mCurrentChargeProgress;
// }
// // 格式化比值保留1位小数适配本地化解决小米手机小数分隔符问题
// String ratioText = String.format(Locale.getDefault(), "比值:%.1f", ratio);
// mTvRatioPreview.setText(ratioText);
// // 触发比值变化回调
// if (mActionListener != null) {
// mActionListener.onRatioChanged(ratio);
// }
// LogUtils.d(TAG, "比值预览更新 | usage=" + mCurrentUsageProgress + " | charge=" + mCurrentChargeProgress + " | ratio=" + ratio);
// }
/**
* 获取资源颜色(适配 API30 主题颜色读取机制,兼容低版本,优化小米机型颜色显示,防御空指针)
* @param colorResId 颜色资源 ID
* @return 校准后的颜色值
*/
private int getResourceColor(int colorResId) {
LogUtils.d(TAG, "getResourceColor() | colorResId=" + colorResId);
// 空指针防御Context 为空返回默认黑色
if (mContext == null) {
LogUtils.e(TAG, "Context 为空,返回默认黑色");
return 0xFF000000;
}
// 适配 API30 主题颜色读取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return mContext.getResources().getColor(colorResId, mContext.getTheme());
} else {
return mContext.getResources().getColor(colorResId);
}
}
/**
* 获取服务启用状态统一从服务控制Bean读取确保全链路状态一致
* @return 服务启用状态true=启用false=禁用)
*/
private boolean getServiceEnableState() {
LogUtils.d(TAG, "getServiceEnableState()");
ControlCenterServiceBean serviceBean = ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class);
// 本地无配置时,默认禁用服务(与服务初始化逻辑对齐)
boolean state = serviceBean != null && serviceBean.isEnableService();
LogUtils.d(TAG, "服务启用状态获取完成 | state=" + state);
return state;
}
}

View File

@@ -5,92 +5,227 @@ import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.SeekBar;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/17 14:11
* @Describe 垂直进度条控件,适配 API30支持逆时针旋转0在下100在上修复滑块同步+弹窗触发bug
* 新增:实时进度变化监听接口,支持拖动时实时回调进度
*/
public class VerticalSeekBar extends SeekBar {
public static final String TAG = VerticalSeekBar.class.getSimpleName();
// ======================== 静态常量 =========================
private static final String TAG = VerticalSeekBar.class.getSimpleName();
public volatile int _mnProgress = -1;
public VerticalSeekBar(Context context) {
super(context);
// ======================== 接口定义(前置,便于外部调用)========================
/**
* 垂直进度条触摸事件回调接口,解决原生 OnSeekBarChangeListener 回调失效问题
* 直接在触摸抬起时回调确保配置变更对话框100%触发
*/
public interface OnVerticalSeekBarTouchListener {
/**
* 触摸抬起时回调(滑块停止滑动,触发弹窗的核心时机)
* @param seekBar 当前垂直进度条实例
* @param progress 最终滑动进度0~100
*/
void onTouchUp(VerticalSeekBar seekBar, int progress);
/**
* 触摸取消时回调(可选,用于异常场景进度回滚)
* @param seekBar 当前垂直进度条实例
* @param progress 取消时的进度
*/
void onTouchCancel(VerticalSeekBar seekBar, int progress);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
/**
* 垂直进度条实时进度变化监听接口
* 支持拖动过程中实时回调进度用于比值预览等实时UI更新场景
*/
public interface OnVerticalSeekBarChangeListener {
/**
* 进度变化时回调
* @param seekBar 当前垂直进度条实例
* @param progress 当前进度0~100
* @param fromUser 是否是用户触摸导致的进度变化
*/
void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser);
/**
* 开始触摸进度条时回调
* @param seekBar 当前垂直进度条实例
*/
void onStartTrackingTouch(VerticalSeekBar seekBar);
/**
* 停止触摸进度条时回调
* @param seekBar 当前垂直进度条实例
*/
void onStopTrackingTouch(VerticalSeekBar seekBar);
}
// ======================== 成员变量 =========================
// 核心状态当前进度缓存修复滑块同步问题volatile 保证多线程可见性)
private volatile int mProgress = -1;
// 监听接口:触摸事件回调(原有,用于弹窗触发)
private OnVerticalSeekBarTouchListener mTouchListener;
// 监听接口:实时进度变化回调(新增,用于比值计算)
private OnVerticalSeekBarChangeListener mProgressChangeListener;
// ======================== 构造方法 =========================
public VerticalSeekBar(Context context) {
super(context);
initView();
LogUtils.d(TAG, "VerticalSeekBar(Context) 初始化");
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
// 去除冗余的水平阴影
initView();
LogUtils.d(TAG, "VerticalSeekBar(Context, AttributeSet) 初始化");
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
LogUtils.d(TAG, "VerticalSeekBar(Context, AttributeSet, int) 初始化");
}
// ======================== 初始化方法 =========================
private void initView() {
// 移除水平默认阴影,优化垂直显示效果,减少 API30 不必要的绘制开销
setBackgroundDrawable(null);
LogUtils.d(TAG, "initView: 移除默认背景阴影,完成视图初始化");
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
// ======================== 对外设置方法(监听接口绑定)========================
/**
* 设置触摸事件监听器(给外部调用,如 MainContentView 绑定)
* @param listener 触摸事件回调实例
*/
public void setOnVerticalSeekBarTouchListener(OnVerticalSeekBarTouchListener listener) {
this.mTouchListener = listener;
LogUtils.d(TAG, "setOnVerticalSeekBarTouchListener: 触摸监听器绑定完成");
}
/**
* 设置实时进度变化监听器(给外部调用,如 MainContentView 绑定)
* @param listener 实时进度变化回调实例
*/
public void setOnVerticalSeekBarChangeListener(OnVerticalSeekBarChangeListener listener) {
this.mProgressChangeListener = listener;
LogUtils.d(TAG, "setOnVerticalSeekBarChangeListener: 实时进度监听器绑定完成");
}
// ======================== 重写系统方法(测量/布局/绘制)========================
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
LogUtils.v(TAG, "onMeasure: 垂直测量完成,宽=" + getMeasuredHeight() + ", 高=" + getMeasuredWidth());
}
protected void onDraw(Canvas c) {
// 0--------100,顺时针旋转,小在上
// c.rotate(+90);
// c.translate(0, -getWidth());
// 0--------100,逆时针旋转,小在下
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 调用基类的处理函数
// 该方法可以使得
// SeekBar.OnSeekBarChangeListener
// 的 onStopTrackingTouch 和 onStartTrackingTouch 等函数有效。
boolean handled = super.onTouchEvent(event);
if (handled) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 0--------100,顺时针旋转,小在上
//_mnProgress = (int)(getMax() * event.getY() / getHeight());
// // 0--------100,逆时针旋转,小在下
_mnProgress = getMax() - (int) (getMax() * event.getY() / getHeight());
_mnProgress = _mnProgress > 100 ? 100 : _mnProgress ;
//LogUtils.d(TAG, "_mnProgress is " + Integer.toString(_mnProgress));
setProgress(_mnProgress);
//onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
default :
//LogUtils.d(TAG, "event.getAction() is " + event.getAction());
break;
}
}
return handled;
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
LogUtils.v(TAG, "onSizeChanged: 尺寸变化,新宽=" + h + ", 新高=" + w);
}
// 解决调用setProgress方法时滑块不跟随的bug
@Override
protected void onDraw(Canvas canvas) {
// 逆时针旋转90度平移画布避免绘制偏移0在下100在上
canvas.rotate(-90);
canvas.translate(-getHeight(), 0);
super.onDraw(canvas);
LogUtils.v(TAG, "onDraw: 完成垂直绘制,旋转角度=-90°");
}
// ======================== 重写进度设置方法(修复滑块同步+新增实时回调)========================
/**
* 重写进度设置,调用尺寸变化方法强制刷新,解决 setProgress 滑块不跟随问题
* 新增:支持外部调用 setProgress 时触发实时进度回调
*/
@Override
public synchronized void setProgress(int progress) {
super.setProgress(progress);
// 强制触发尺寸变化同步刷新滑块位置核心bug修复逻辑
onSizeChanged(getWidth(), getHeight(), 0, 0);
mProgress = progress;
LogUtils.d(TAG, "setProgress: 进度设置为" + progress + ",滑块同步刷新");
// 触发实时进度监听(外部调用 setProgress 时 fromUser 为 false
if (mProgressChangeListener != null) {
mProgressChangeListener.onProgressChanged(this, progress, false);
}
}
// ======================== 重写触摸事件(优化事件透传+实时进度回调)========================
@Override
public boolean onTouchEvent(MotionEvent event) {
// 先调用父类方法,保留原生监听器兼容性,同时强制透传事件
super.onTouchEvent(event);
boolean handled = true; // 强制消费事件,避免事件被拦截导致回调丢失
boolean fromUser = true; // 标记是否是用户触摸导致的进度变化
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
LogUtils.d(TAG, "onTouchEvent: 触摸按下Y坐标=" + event.getY());
// 触发实时进度监听:开始触摸
if (mProgressChangeListener != null) {
mProgressChangeListener.onStartTrackingTouch(this);
}
break;
case MotionEvent.ACTION_MOVE:
calculateProgress(event.getY());
setProgress(mProgress);
LogUtils.v(TAG, "onTouchEvent: 触摸滑动,进度更新为" + mProgress);
// 触发实时进度监听:进度变化
if (mProgressChangeListener != null) {
mProgressChangeListener.onProgressChanged(this, mProgress, fromUser);
}
break;
case MotionEvent.ACTION_UP:
calculateProgress(event.getY());
setProgress(mProgress);
LogUtils.d(TAG, "onTouchEvent: 触摸抬起,进度=" + mProgress + ",触发弹窗回调");
// 触发实时进度监听:停止触摸
if (mProgressChangeListener != null) {
mProgressChangeListener.onProgressChanged(this, mProgress, fromUser);
mProgressChangeListener.onStopTrackingTouch(this);
}
// 核心:调用原有触摸接口,通知外部触发配置变更对话框
if (mTouchListener != null) {
mTouchListener.onTouchUp(this, mProgress);
}
break;
case MotionEvent.ACTION_CANCEL:
LogUtils.d(TAG, "onTouchEvent: 触摸取消,当前进度=" + getProgress());
// 触发实时进度监听:停止触摸
if (mProgressChangeListener != null) {
mProgressChangeListener.onStopTrackingTouch(this);
}
// 可选:触摸取消时回调,外部可做进度回滚处理
if (mTouchListener != null) {
mTouchListener.onTouchCancel(this, getProgress());
}
break;
}
return handled;
}
// ======================== 内部工具方法 =========================
/**
* 计算垂直进度,校准范围 0~100避免异常值
* @param touchY 触摸点Y坐标
*/
private void calculateProgress(float touchY) {
// 核心进度计算公式(逆时针旋转适配)
mProgress = getMax() - (int) (getMax() * touchY / getHeight());
// 校准进度范围,防止超出 0~100兼容 API30 进度边界校验)
mProgress = Math.max(0, Math.min(mProgress, getMax()));
LogUtils.v(TAG, "calculateProgress: 触摸Y=" + touchY + ",计算进度=" + mProgress);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/darker_gray" />
<corners android:radius="6dp" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/holo_blue_light" />
<corners android:radius="6dp" />
</shape>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按压状态:浅灰色背景 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#E0E0E0" /> <!-- 按压深色 -->
<corners android:radius="8dp" /> <!-- 圆角适配小米UI风格 -->
<stroke android:width="1dp" android:color="#CCCCCC" /> <!-- 边框 -->
</shape>
</item>
<!-- 正常状态:白色背景 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" /> <!-- 正常白色 -->
<corners android:radius="8dp" />
<stroke android:width="1dp" android:color="#CCCCCC" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按压状态:深灰色背景 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#D0D0D0" />
<corners android:radius="8dp" /> <!-- 与亮度按钮圆角一致,统一风格 -->
</shape>
</item>
<!-- 正常状态:浅灰色背景 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#F0F0F0" />
<corners android:radius="8dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按压状态:深灰色 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#CCCCCC" /> <!-- 按压时颜色 -->
<corners android:radius="8dp" /> <!-- 圆角(可按需调整) -->
<stroke android:width="1dp" android:color="#EEEEEE" /> <!-- 边框(可选,不加可删除) -->
</shape>
</item>
<!-- 正常状态:浅灰色 -->
<item>
<shape android:shape="rectangle">
<solid android:color="#F5F5F5" /> <!-- 正常时颜色 -->
<corners android:radius="8dp" /> <!-- 圆角(和按压状态一致) -->
<stroke android:width="1dp" android:color="#EEEEEE" /> <!-- 边框(可选) -->
</shape>
</item>
</selector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按压状态:加深深色背景 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#2D7CFF" /> <!-- 按压深蓝 -->
<corners android:radius="8dp" />
</shape>
</item>
<!-- 正常状态:主色背景(可改为项目主题色) -->
<item>
<shape android:shape="rectangle">
<solid android:color="#4096FF" /> <!-- 正常浅蓝适配小米系统UI -->
<corners android:radius="8dp" />
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 背景色:白色 -->
<solid android:color="@android:color/white" />
<!-- 圆角12dp适配小米机型圆角无锯齿 -->
<corners android:radius="12dp" />
<!-- 边框:浅灰色细边框(避免弹窗边缘模糊) -->
<stroke
android:width="1dp"
android:color="@android:color/darker_gray" />
<!-- 内边距:轻微留白,避免内容贴边 -->
<padding
android:bottom="5dp"
android:top="5dp" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="6dp" />
<stroke
android:width="1dp"
android:color="@android:color/darker_gray" />
</shape>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 进度条未完成部分:浅灰色 -->
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="@android:color/darker_gray" />
<corners android:radius="10dp" />
</shape>
</item>
<!-- 进度条已完成部分:系统蓝色(无需额外定义颜色) -->
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<solid android:color="@android:color/holo_blue_light" />
<corners android:radius="10dp" />
</shape>
</clip>
</item>
</layer-list>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- 滑块颜色:系统蓝色 -->
<solid android:color="@android:color/holo_blue_light" />
<!-- 滑块大小20dp适配小米机型触摸区域 -->
<size
android:width="20dp"
android:height="20dp" />
<!-- 白色边框:区分滑块与进度条 -->
<stroke
android:width="2dp"
android:color="@android:color/white" />
</shape>

View File

@@ -117,6 +117,15 @@
android:layout_margin="5dp"
android:id="@+id/activitybackgroundpictureAButton7"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"
android:text="[©]"
android:layout_gravity="center_vertical"
android:layout_margin="5dp"
android:id="@+id/activitybackgroundsettingsAButton1"
android:onClick="onColorPaletteDialog"/>
<cc.winboll.studio.libaes.views.AButton
android:layout_width="50dp"
android:layout_height="36dp"

View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
android:background="#FFFFFFFF">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<ImageView
android:id="@+id/iv_color_picker"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#FF0000"
android:clickable="true"
android:focusable="true"/>
<ImageView
android:id="@+id/iv_color_scaler"
android:layout_width="100dp"
android:layout_height="100dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/color_scale_logo"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RGB"
android:textSize="16sp"/>
<EditText
android:id="@+id/et_r"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:hint="R"
android:inputType="number"
android:gravity="center"
android:maxLength="3"/>
<EditText
android:id="@+id/et_g"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:hint="G"
android:inputType="number"
android:gravity="center"
android:maxLength="3"/>
<EditText
android:id="@+id/et_b"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:hint="B"
android:inputType="number"
android:gravity="center"
android:maxLength="3"/>
</LinearLayout>
<EditText
android:id="@+id/et_color_value"
android:layout_width="match_parent"
android:layout_height="40dp"
android:hint="#AARRGGBB"
android:inputType="text"
android:gravity="center"
android:maxLength="9"
android:layout_marginBottom="15dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="透明度:"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_alpha_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="100%"
android:textSize="16sp"
android:layout_marginLeft="10dp"/>
</LinearLayout>
<SeekBar
android:id="@+id/sb_alpha"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="100"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal"
android:layout_marginBottom="20dp">
<TextView
android:id="@+id/tv_brightness_minus"
android:layout_width="40dp"
android:layout_height="40dp"
android:text="-"
android:textSize="20sp"
android:gravity="center"
android:background="@drawable/btn_common"
android:clickable="true"
android:focusable="true"/>
<TextView
android:id="@+id/tv_brightness_value"
android:layout_width="80dp"
android:layout_height="40dp"
android:text="100%"
android:textSize="16sp"
android:gravity="center"/>
<TextView
android:id="@+id/tv_brightness_plus"
android:layout_width="40dp"
android:layout_height="40dp"
android:text="+"
android:textSize="20sp"
android:gravity="center"
android:background="@drawable/btn_common"
android:clickable="true"
android:focusable="true"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal">
<TextView
android:id="@+id/tv_cancel"
android:layout_width="100dp"
android:layout_height="45dp"
android:text="取消"
android:textSize="16sp"
android:gravity="center"
android:background="@drawable/btn_common"
android:clickable="true"
android:focusable="true"
android:layout_marginRight="20dp"/>
<TextView
android:id="@+id/tv_confirm"
android:layout_width="100dp"
android:layout_height="45dp"
android:text="确认"
android:textSize="16sp"
android:gravity="center"
android:background="@drawable/btn_common"
android:clickable="true"
android:focusable="true"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="10dp">
</LinearLayout>

View File

@@ -58,9 +58,73 @@
<color name="colorUsege">@color/colorRed</color>
<color name="colorCurrent">@color/colorBlue</color>
<color name="colorCharge">@color/colorYellow</color>
<!--CustomSlideToUnlockView控件配置-->
<color name="colorCustomSlideToUnlockViewWhite">#FFFFFFFF</color>
<!---->
<!-- ============== 基础黑白(必含,适配文字/背景) ============== -->
<color name="white">#FFFFFF</color> <!-- 纯白色(文字/背景) -->
<color name="black">#000000</color> <!-- 近黑色(重要文字) -->
<!-- ============== 基础色系(按钮/强调色常用) ============== -->
<!-- 蓝色系(常用:确认/链接/主题色) -->
<color name="blue_light">#4A90E2</color> <!-- 浅蓝(次要按钮) -->
<color name="blue_normal">#2196F3</color> <!-- 标准蓝(主题/确认按钮) -->
<color name="blue_dark">#1976D2</color> <!-- 深蓝(按压态/重要强调) -->
<!-- 绿色系(常用:成功/完成/安全提示) -->
<color name="green_light">#66BB6A</color> <!-- 浅绿(次要成功态) -->
<color name="green_normal">#4CAF50</color> <!-- 标准绿(成功按钮/提示) -->
<color name="green_dark">#388E3C</color> <!-- 深绿(按压态/重要成功) -->
<!-- 红色系(常用:错误/警告/删除按钮) -->
<color name="red_light">#EF5350</color> <!-- 浅红(次要错误提示) -->
<color name="red_normal">#F44336</color> <!-- 标准红(删除/错误按钮) -->
<color name="red_dark">#D32F2F</color> <!-- 深红(按压态/重要错误) -->
<!-- 黄色系(常用:警告/提醒/高亮) -->
<color name="yellow_light">#FFF59D</color> <!-- 浅黄(次要提醒) -->
<color name="yellow_normal">#FFC107</color> <!-- 标准黄(警告提示/高亮) -->
<color name="yellow_dark">#FFA000</color> <!-- 深黄(重要警告) -->
<!-- 橙色系(常用:提醒/进度/活力色) -->
<color name="orange_normal">#FF9800</color> <!-- 标准橙(提醒按钮/进度) -->
<!-- 紫色系(常用:特殊强调/个性按钮) -->
<color name="purple_normal">#9C27B0</color> <!-- 标准紫(特殊功能按钮) -->
<!-- ============== 透明色(遮罩/背景叠加) ============== -->
<color name="transparent">#00000000</color> <!-- 全透明 -->
<color name="black_transparent_50">#80000000</color> <!-- 50%透明黑(遮罩) -->
<!-- 1. 不透明灰色(常用深浅梯度,直接用) -->
<color name="gray_100">#F5F5F5</color> <!-- 极浅灰(接近白色,背景用) -->
<color name="gray_200">#EEEEEE</color> <!-- 浅灰(卡片/分割线背景) -->
<color name="gray_300">#E0E0E0</color> <!-- 中浅灰(边框/次要背景) -->
<color name="gray_400">#BDBDBD</color> <!-- 中灰(次要文字/图标) -->
<color name="gray_500">#9E9E9E</color> <!-- 标准中灰(常用辅助文字) -->
<color name="gray_600">#757575</color> <!-- 中深灰(常规辅助文字) -->
<color name="gray_700">#616161</color> <!-- 深灰(重要辅助文字) -->
<color name="gray_800">#424242</color> <!-- 极深灰(接近黑色,标题副文本) -->
<color name="gray_900">#212121</color> <!-- 近黑色(特殊场景用) -->
<!-- 2. 半透明灰色(带透明度,遮罩/蒙层用) -->
<color name="gray_transparent_30">#4D9E9E9E</color> <!-- 30%透明中灰A=4D -->
<color name="gray_transparent_50">#809E9E9E</color> <!-- 50%透明中灰A=80 -->
<color name="gray_transparent_70">#B39E9E9E</color> <!-- 70%透明中灰A=B3 -->
<color name="gray_light">#EEE</color> <!-- 等价 #EEEEEE浅灰 -->
<color name="gray_mid">#999</color> <!-- 等价 #999999中灰 -->
<color name="gray_dark">#666</color> <!-- 等价 #666666深灰 -->
<color name="gray_black">#333</color> <!-- 等价 #333333极深灰 -->
<!-- 50% 透明中灰(弹窗遮罩常用) -->
<color name="mask_gray">#809E9E9E</color>
<!-- 30% 透明深灰(背景叠加) -->
<color name="bg_overlay_gray">#4D424242</color>
<!-- 1. 常规灰色(按钮默认态,常用中灰) -->
<color name="btn_gray_normal">#9E9E9E</color>
<!-- 2. 按压深色(按钮点击态,加深一级,提升交互感) -->
<color name="btn_gray_pressed">#757575</color>
<!-- 3. 禁用灰色(按钮不可点击态,浅灰) -->
<color name="btn_gray_disabled">#E0E0E0</color>
</resources>

View File

@@ -26,4 +26,18 @@
<item name="android:textSize">@dimen/text_subtitle_size</item>
</style>
<!-- 自定义调色板对话框样式 -->
<style name="CustomDialogStyle" parent="@android:style/Theme.Dialog">
<!-- 去除标题栏 -->
<item name="android:windowNoTitle">true</item>
<!-- 背景透明(避免小米机型弹窗周围黑边) -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 禁止弹窗全屏 -->
<item name="android:windowFullscreen">false</item>
<!-- 小米机型适配:弹窗大小自适应 -->
<item name="android:windowContentOverlay">@null</item>
<!-- 禁止触摸外部关闭(可选,避免误触) -->
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
</resources>