From 5e6de914308b98b28268e66b1eb413092313605c Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Wed, 17 Dec 2025 20:40:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=90=AF=E5=81=9C=E8=B0=83?= =?UTF-8?q?=E8=AF=95=E4=B8=AD=E3=80=82=E3=80=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../studio/powerbell/MainActivity.java | 371 ++++++------------ .../services/ControlCenterService.java | 150 ++++--- .../powerbell/views/MainContentView.java | 116 +++--- 4 files changed, 271 insertions(+), 370 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index d2c9587..bb0dafd 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Dec 17 08:49:06 GMT 2025 +#Wed Dec 17 12:37:54 GMT 2025 stageCount=10 libraryProject= baseVersion=15.14 publishVersion=15.14.9 -buildCount=18 +buildCount=25 baseBetaVersion=15.14.10 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java index 129b3ad..b2eca9d 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java @@ -45,6 +45,7 @@ import cc.winboll.studio.powerbell.views.MainContentView; * @Date 2025/12/17 13:14 * @Describe 主活动类:应用核心页面,管理电池监控、背景设置、服务控制等核心功能 * 适配:Java7 | API30 | 小米手机,优化性能与稳定性,杜绝内存泄漏 + * 核心规则:UI开关仅管理本地配置,服务启停仅在应用启动时根据配置执行 */ public class MainActivity extends WinBoLLActivity implements MainContentView.OnViewActionListener { // ======================== 静态常量(置顶统一,抽离魔法值,便于维护)======================== @@ -58,11 +59,11 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV public static final int MSG_CURRENTVALUEBATTERY = 1; // 更新当前电量 public static final int MSG_LOAD_BACKGROUND = 2; // 加载背景 private static final int MSG_RESTART_REMIND_THREAD = 3; // 重启RemindThread线程 - private static final int MSG_UPDATE_SERVICE_SWITCH = 4; // 更新服务开关UI(新增) + private static final int MSG_UPDATE_SERVICE_SWITCH = 4; // 更新服务开关UI // ======================== 静态成员(全局共享,严格管控生命周期)======================== - public static MainActivity sMainActivity; // 全局Activity实例(销毁时必须置空) - private static Handler sGlobalHandler; // 全局Handler(统一消息分发,避免多Handler混乱) + private static MainActivity sMainActivity; // 全局Activity实例(销毁时必须置空) + private static Handler sGlobalHandler; // 全局Handler(统一消息分发) // ======================== 成员属性(按「依赖→核心→辅助」分类,优先级递减)======================== // 工具类实例(单例,避免重复初始化) @@ -71,15 +72,15 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV private BackgroundSourceUtils mBgSourceUtils; // 应用核心实例 private App mApplication; - private MainContentView mMainContentView; // 核心视图封装类(统一管理视图) + private MainContentView mMainContentView; // 核心视图封装类 // 基础视图组件 private Toolbar mToolbar; private ViewStub mAdsViewStub; - private ADsBannerView mADsBannerView; // 广告视图(非核心,延迟加载) + private ADsBannerView mADsBannerView; // 广告视图(非核心,延迟加载) // 资源与菜单 private Menu mMenu; - private Drawable mFrameDrawable; // 框架背景资源(适配API30主题) - // 新增:服务控制核心Bean(本地持久化,管理服务自启动状态) + private Drawable mFrameDrawable; // 框架背景资源 + // 服务控制核心Bean private ControlCenterServiceBean mServiceControlBean; // ======================== 生命周期方法(按系统调用顺序排列,逻辑闭环)======================== @@ -97,15 +98,15 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV protected void onCreate(Bundle savedInstanceState) { LogUtils.d(TAG, "onCreate: 进入"); super.onCreate(savedInstanceState); - // 1. 初始化全局核心组件(Handler优先,避免消息丢失) + // 1. 初始化全局Handler(优先初始化,避免消息丢失) initGlobalHandler(); // 2. 加载布局+初始化核心流程 setContentView(R.layout.activity_main); - initPermissionUtils(); // 权限工具类(提前初始化,保障权限请求时效) - initMainContentView(); // 核心视图封装(替代原ViewHolder) - initCriticalView(); // 首屏核心视图(Toolbar等,保障首屏加载速度) - initCoreUtilsAsync(); // 异步初始化工具类(避免主线程阻塞) - loadNonCriticalViewDelayed(); // 延迟加载非核心视图(广告,不影响首屏体验) + initPermissionUtils(); + initMainContentView(); + initCriticalView(); + initCoreUtilsAsync(); + loadNonCriticalViewDelayed(); LogUtils.d(TAG, "onCreate: 退出"); } @@ -113,7 +114,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV protected void onPostCreate(Bundle savedInstanceState) { LogUtils.d(TAG, "onPostCreate: 进入"); super.onPostCreate(savedInstanceState); - // 视图初始化完成后请求权限(避免视图未就绪导致的异常) mPermissionUtils.startPermissionRequest(this); LogUtils.d(TAG, "onPostCreate: 退出"); } @@ -122,19 +122,17 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV protected void onResume() { LogUtils.d(TAG, "onResume: 进入"); super.onResume(); - // 1. 恢复背景(适配后台切前台场景,小米手机保活优化) + // 恢复背景+同步服务开关UI(仅本地配置) if (sGlobalHandler != null) { sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); - LogUtils.d(TAG, "onResume: 发送加载背景消息"); - // 新增:恢复时同步服务开关UI(避免开关状态与实际服务状态不一致) sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH); + LogUtils.d(TAG, "onResume: 发送背景加载+服务开关更新消息"); } - // 2. 恢复广告(非核心,容错处理) + // 恢复广告 if (mADsBannerView != null) { mADsBannerView.resumeADs(this); - LogUtils.d(TAG, "onResume: 恢复广告展示"); } - // 3. 关键调整:应用切前台,设置RemindThread为前台状态(开启提醒) + // 设置RemindThread前台状态 setRemindThreadForeground(true); LogUtils.d(TAG, "onResume: 退出"); } @@ -143,7 +141,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV protected void onPause() { LogUtils.d(TAG, "onPause: 进入"); super.onPause(); - // 关键调整:应用切后台,设置RemindThread为后台状态(停止提醒+重置无效电量) + // 设置RemindThread后台状态 setRemindThreadForeground(false); LogUtils.d(TAG, "onPause: 退出"); } @@ -152,40 +150,33 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV protected void onDestroy() { LogUtils.d(TAG, "onDestroy: 进入"); super.onDestroy(); - // 1. 释放广告资源(避免内存泄漏,适配API30资源管控) + // 1. 释放广告资源 if (mADsBannerView != null) { mADsBannerView.releaseAdResources(); mADsBannerView = null; - LogUtils.d(TAG, "onDestroy: 释放广告资源完成"); } - // 2. 释放核心视图资源(调用封装类释放方法,彻底断开引用) + // 2. 释放核心视图资源 if (mMainContentView != null) { mMainContentView.releaseResources(); mMainContentView = null; - LogUtils.d(TAG, "onDestroy: 释放核心视图资源完成"); } - // 3. 关键调整:Activity销毁,强制销毁RemindThread单例(彻底停止线程) + // 3. 销毁线程+清理Handler RemindThread.destroyInstance(); - LogUtils.d(TAG, "onDestroy: 强制销毁RemindThread单例完成"); - // 4. 置空全局实例(杜绝内存泄漏,必须执行) - sMainActivity = null; - // 5. 清理Handler(移除所有消息,避免Activity销毁后回调) if (sGlobalHandler != null) { sGlobalHandler.removeCallbacksAndMessages(null); sGlobalHandler = null; - LogUtils.d(TAG, "onDestroy: 清理全局Handler完成"); } - // 6. 释放Drawable资源(断开回调,辅助GC回收) + // 4. 释放Drawable+置空全局实例 if (mFrameDrawable != null) { mFrameDrawable.setCallback(null); mFrameDrawable = null; - LogUtils.d(TAG, "onDestroy: 释放Drawable资源完成"); } - // 7. 置空所有辅助实例(辅助GC,避免残留引用) + sMainActivity = null; + // 5. 置空所有辅助实例 mPermissionUtils = null; mAppConfigUtils = null; mBgSourceUtils = null; - mServiceControlBean = null; // 新增:置空服务控制Bean + mServiceControlBean = null; mMenu = null; mApplication = null; mToolbar = null; @@ -197,32 +188,26 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV protected void onActivityResult(int requestCode, int resultCode, Intent data) { LogUtils.d(TAG, "onActivityResult: 进入,requestCode=" + requestCode + ",resultCode=" + resultCode); super.onActivityResult(requestCode, resultCode, data); - // 1. 权限请求结果处理 mPermissionUtils.handlePermissionRequest(this, requestCode, resultCode, data); - // 2. 背景设置完成后重新加载背景 + // 背景设置完成后重新加载背景 if (requestCode == REQUEST_READ_MEDIA_IMAGES) { if (sGlobalHandler != null) { sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); - LogUtils.d(TAG, "onActivityResult: 背景设置完成,发送加载背景消息"); } } LogUtils.d(TAG, "onActivityResult: 退出"); } - // ======================== 菜单与导航方法(统一归类,用户交互入口)======================== + // ======================== 菜单与导航方法 ======================== @Override public boolean onCreateOptionsMenu(Menu menu) { LogUtils.d(TAG, "onCreateOptionsMenu: 进入"); mMenu = menu; - // 1. 加载主题菜单 AESThemeUtil.inflateMenu(this, menu); - // 2. 调试模式加载开发菜单+单元测试菜单 if (App.isDebugging()) { DevelopUtils.inflateMenu(this, menu); getMenuInflater().inflate(R.menu.toolbar_unittest, mMenu); - LogUtils.d(TAG, "onCreateOptionsMenu: 调试模式,加载开发/单元测试菜单"); } - // 3. 加载主菜单 getMenuInflater().inflate(R.menu.toolbar_main, mMenu); LogUtils.d(TAG, "onCreateOptionsMenu: 退出"); return true; @@ -231,18 +216,13 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV @Override public boolean onOptionsItemSelected(MenuItem item) { LogUtils.d(TAG, "onOptionsItemSelected: 进入,itemId=" + item.getItemId()); - // 1. 主题切换处理(切换后重建Activity生效) if (AESThemeUtil.onAppThemeItemSelected(this, item)) { recreate(); - LogUtils.d(TAG, "onOptionsItemSelected: 主题切换,重建Activity"); return true; } - // 2. 开发模式菜单处理 if (DevelopUtils.onDevelopItemSelected(this, item)) { - LogUtils.d(TAG, "onOptionsItemSelected: 开发菜单操作完成"); return true; } - // 3. 主菜单逻辑分发(按功能分类,清晰易维护) switch (item.getItemId()) { case R.id.action_settings: startActivity(new Intent(this, SettingsActivity.class)); @@ -272,31 +252,23 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV @Override public void setupToolbar() { super.setupToolbar(); - // 主页面隐藏返回按钮(符合用户操作习惯) if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(false); - LogUtils.d(TAG, "setupToolbar: 隐藏Toolbar返回按钮"); } } - // ======================== 页面交互方法(用户操作响应,统一归类)======================== @Override public void onBackPressed() { - // 退到后台,不销毁Activity(提升用户体验,小米手机后台保活优化) moveTaskToBack(true); LogUtils.d(TAG, "onBackPressed: 应用退到后台"); } @Override public boolean dispatchKeyEvent(KeyEvent event) { - // 保留系统默认按键分发逻辑,无自定义需求 return super.dispatchKeyEvent(event); } - // ======================== 核心初始化方法(按优先级排序,核心优先加载)======================== - /** - * 初始化权限工具类(提前初始化,保障权限请求无延迟) - */ + // ======================== 核心初始化方法 ======================== private void initPermissionUtils() { LogUtils.d(TAG, "initPermissionUtils: 进入"); mPermissionUtils = PermissionUtils.getInstance(); @@ -304,7 +276,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV } /** - * 初始化全局Handler(统一消息分发,Java7显式实现,避免内存泄漏) + * 初始化全局Handler(核心优化:弱引用+生命周期校验,杜绝内存泄漏) */ private void initGlobalHandler() { LogUtils.d(TAG, "initGlobalHandler: 进入"); @@ -312,19 +284,20 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV sGlobalHandler = new Handler() { @Override public void handleMessage(Message msg) { - LogUtils.d(TAG, "handleMessage: 接收消息,what=" + msg.what); - // 消息处理前先校验Activity状态(避免销毁后操作UI) - if (sMainActivity == null || sMainActivity.isFinishing()) { + // 严格校验Activity状态,避免销毁后操作UI + if (sMainActivity == null || sMainActivity.isFinishing() || sMainActivity.isDestroyed()) { LogUtils.w(TAG, "handleMessage: Activity已销毁,跳过消息处理"); return; } - // 按消息类型分发逻辑 + LogUtils.d(TAG, "handleMessage: 接收消息,what=" + msg.what); switch (msg.what) { case MSG_RELOAD_APPCONFIG: sMainActivity.updateViewData(); break; case MSG_CURRENTVALUEBATTERY: - sMainActivity.mMainContentView.updateCurrentBattery(msg.arg1); + if (sMainActivity.mMainContentView != null) { + sMainActivity.mMainContentView.updateCurrentBattery(msg.arg1); + } break; case MSG_LOAD_BACKGROUND: sMainActivity.reloadBackground(); @@ -334,7 +307,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV AppConfigBean configBean = (AppConfigBean) msg.obj; sMainActivity.restartRemindThread(configBean); break; - // 新增:更新服务开关UI(同步实际服务状态,避免开关与服务状态不一致) case MSG_UPDATE_SERVICE_SWITCH: sMainActivity.updateServiceSwitchUI(); break; @@ -347,9 +319,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV LogUtils.d(TAG, "initGlobalHandler: 退出"); } - /** - * 初始化核心视图封装类(统一管理视图,替代原ViewHolder模式) - */ private void initMainContentView() { LogUtils.d(TAG, "initMainContentView: 进入"); View rootView = findViewById(android.R.id.content); @@ -358,26 +327,20 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV LogUtils.d(TAG, "initMainContentView: 退出"); } - /** - * 初始化首屏核心视图(Toolbar等,保障首屏加载速度,优先渲染) - */ private void initCriticalView() { LogUtils.d(TAG, "initCriticalView: 进入"); - sMainActivity = this; // 赋值全局实例(用于Handler回调) - // 初始化Toolbar + sMainActivity = this; mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); if (mToolbar != null) { mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText); - LogUtils.d(TAG, "initCriticalView: Toolbar样式设置完成"); } - // 初始化广告ViewStub(仅初始化Stub,不立即加载广告) mAdsViewStub = findViewById(R.id.stub_ads_banner); LogUtils.d(TAG, "initCriticalView: 退出"); } /** - * 异步初始化核心工具类(子线程执行,避免主线程阻塞,提升首屏加载速度) + * 异步初始化核心工具类(保留服务启停逻辑:应用启动时根据配置执行) */ private void initCoreUtilsAsync() { LogUtils.d(TAG, "initCoreUtilsAsync: 进入"); @@ -385,41 +348,59 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV @Override public void run() { LogUtils.d(TAG, "initCoreUtilsAsync: 异步线程启动"); - // 子线程初始化工具类(无UI操作,安全执行) mApplication = (App) getApplication(); mAppConfigUtils = AppConfigUtils.getInstance(getApplicationContext()); mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity()); - // 新增:初始化服务控制Bean(读取本地持久化状态,管理自启动) - mServiceControlBean = ControlCenterServiceBean.loadBean(getApplicationContext(), ControlCenterServiceBean.class); - if (mServiceControlBean == null) { - // 本地无配置,默认禁用服务(与服务初始化逻辑一致) - mServiceControlBean = new ControlCenterServiceBean(false); - ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean); - LogUtils.d(TAG, "initCoreUtilsAsync: 本地无服务控制配置,创建默认禁用配置"); - } - LogUtils.d(TAG, "initCoreUtilsAsync: 核心工具类+服务控制Bean初始化完成"); - // 切换主线程更新UI(Java7显式runOnUiThread) + // 初始化服务控制Bean(优先读取本地配置,无则创建默认) + mServiceControlBean = ControlCenterServiceBean.loadBean(getApplicationContext(), ControlCenterServiceBean.class); + if (mServiceControlBean == null) { + mServiceControlBean = new ControlCenterServiceBean(false); + ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean); + LogUtils.d(TAG, "initCoreUtilsAsync: 创建默认服务控制配置(禁用)"); + } + + // 应用启动时:根据本地配置执行服务启停(仅执行一次) + if (mServiceControlBean.isEnableService()) { + if (!ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName())) { + runOnUiThread(new Runnable() { + @Override + public void run() { + ControlCenterService.startControlCenterService(getApplicationContext()); + LogUtils.d(TAG, "initCoreUtilsAsync: 应用启动,根据配置启动服务"); + } + }); + } + } else { + if (ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName())) { + runOnUiThread(new Runnable() { + @Override + public void run() { + ControlCenterService.stopControlCenterService(getApplicationContext()); + LogUtils.d(TAG, "initCoreUtilsAsync: 应用启动,根据配置停止服务"); + } + }); + } + } + + // 切换主线程更新UI runOnUiThread(new Runnable() { @Override public void run() { - LogUtils.d(TAG, "initCoreUtilsAsync: 切换主线程更新UI"); - if (isFinishing()) { + if (isFinishing() || isDestroyed()) { LogUtils.w(TAG, "initCoreUtilsAsync: Activity已销毁,跳过UI更新"); return; } - // 适配API30加载Drawable(添加主题参数,避免系统警告) + // 适配API30+主题加载Drawable 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(); // 更新视图数据 - checkServiceAsync(); // 异步检查服务状态 - sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); // 加载背景 - // 新增:初始化服务开关UI(确保开关状态与本地配置一致) - sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH); + // 执行核心业务 + updateViewData(); + sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); + sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH); } }); LogUtils.d(TAG, "initCoreUtilsAsync: 异步线程结束"); @@ -428,49 +409,38 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV LogUtils.d(TAG, "initCoreUtilsAsync: 退出"); } - /** - * 延迟加载非核心视图(广告,延迟500ms加载,不影响首屏体验) - */ private void loadNonCriticalViewDelayed() { LogUtils.d(TAG, "loadNonCriticalViewDelayed: 进入"); new Handler().postDelayed(new Runnable() { @Override public void run() { - if (isFinishing()) { - LogUtils.w(TAG, "loadNonCriticalViewDelayed: Activity已销毁,跳过广告加载"); + if (isFinishing() || isDestroyed()) { return; } - loadAdsView(); // 加载广告视图 + loadAdsView(); } }, DELAY_LOAD_NON_CRITICAL); LogUtils.d(TAG, "loadNonCriticalViewDelayed: 退出"); } - // ======================== 视图操作方法(UI更新,统一归类)======================== - /** - * 加载广告视图(非核心,容错处理,避免广告加载失败影响主功能) - */ + // ======================== 视图操作方法 ======================== private void loadAdsView() { LogUtils.d(TAG, "loadAdsView: 进入"); if (mAdsViewStub == null) { - LogUtils.e(TAG, "loadAdsView: 广告ViewStub为空,加载失败"); + LogUtils.e(TAG, "loadAdsView: 广告ViewStub为空"); return; } if (mADsBannerView == null) { View adsView = mAdsViewStub.inflate(); mADsBannerView = adsView.findViewById(R.id.adsbanner); - LogUtils.d(TAG, "loadAdsView: 广告视图加载完成"); } LogUtils.d(TAG, "loadAdsView: 退出"); } - /** - * 更新视图数据(调用核心视图封装类方法,统一更新UI) - */ private void updateViewData() { LogUtils.d(TAG, "updateViewData: 进入"); if (mMainContentView == null || mFrameDrawable == null) { - LogUtils.e(TAG, "updateViewData: 核心视图/框架资源为空,跳过更新"); + LogUtils.e(TAG, "updateViewData: 核心视图/框架资源为空"); return; } mMainContentView.updateViewData(mFrameDrawable); @@ -478,234 +448,145 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV LogUtils.d(TAG, "updateViewData: 退出"); } - /** - * 重新加载背景(适配背景切换,小米手机图片加载优化,容错处理) - */ private void reloadBackground() { LogUtils.d(TAG, "reloadBackground: 进入"); if (mMainContentView == null || mBgSourceUtils == null) { - LogUtils.e(TAG, "reloadBackground: 核心视图/背景工具类为空,跳过加载"); + LogUtils.e(TAG, "reloadBackground: 核心视图/背景工具类为空"); return; } BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean(); if (currentBgBean != null) { mMainContentView.backgroundView.loadBackgroundBean(currentBgBean); - LogUtils.d(TAG, "reloadBackground: 加载自定义背景完成"); } else { - LogUtils.e(TAG, "reloadBackground: 无自定义背景,加载默认背景"); mMainContentView.backgroundView.setBackgroundResource(R.drawable.default_background); } LogUtils.d(TAG, "reloadBackground: 退出"); } - /** - * 设置主布局背景颜色(适配背景配置,API30安全防护) - */ private void setMainLayoutBackgroundColor() { LogUtils.d(TAG, "setMainLayoutBackgroundColor: 进入"); - if (isFinishing() || mMainContentView == null || mBgSourceUtils == null) { - LogUtils.e(TAG, "setMainLayoutBackgroundColor: 环境无效,跳过设置"); + if (isFinishing() || isDestroyed() || mMainContentView == null || mBgSourceUtils == null) { + LogUtils.e(TAG, "setMainLayoutBackgroundColor: 环境无效"); return; } BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean(); if (currentBgBean != null) { - int bgColor = currentBgBean.getPixelColor(); - mMainContentView.mainLayout.setBackgroundColor(bgColor); - LogUtils.d(TAG, "setMainLayoutBackgroundColor: 主布局颜色设置完成"); + mMainContentView.mainLayout.setBackgroundColor(currentBgBean.getPixelColor()); } LogUtils.d(TAG, "setMainLayoutBackgroundColor: 退出"); } /** - * 新增:更新服务开关UI(同步「本地配置+实际服务状态」,避免开关与服务状态不一致) + * 更新服务开关UI(仅同步本地配置,不校验服务状态) */ private void updateServiceSwitchUI() { LogUtils.d(TAG, "updateServiceSwitchUI: 进入"); if (mMainContentView == null || mServiceControlBean == null) { - LogUtils.e(TAG, "updateServiceSwitchUI: 核心视图/服务控制Bean为空,跳过更新"); + LogUtils.e(TAG, "updateServiceSwitchUI: 核心视图/服务控制Bean为空"); return; } - // 开关状态 = 本地服务控制配置启用状态(自启动状态) - boolean isServiceEnabled = mServiceControlBean.isEnableService(); - // 禁用开关点击(避免更新UI时触发重复回调) + // 仅读取本地配置状态,不涉及服务状态校验 + boolean configEnabled = mServiceControlBean.isEnableService(); + LogUtils.d(TAG, "updateServiceSwitchUI: 本地配置状态=" + configEnabled); + + // 仅更新UI开关状态 mMainContentView.setServiceSwitchEnabled(false); - // 更新开关状态 - mMainContentView.setServiceSwitchChecked(isServiceEnabled); - // 启用开关点击 + mMainContentView.setServiceSwitchChecked(configEnabled); mMainContentView.setServiceSwitchEnabled(true); - LogUtils.d(TAG, "updateServiceSwitchUI: 服务开关UI更新完成,当前状态=" + isServiceEnabled); LogUtils.d(TAG, "updateServiceSwitchUI: 退出"); } - // ======================== 服务与线程管理方法(业务核心,统一归类)======================== + // ======================== 服务与线程管理方法 ======================== /** - * 异步检查服务状态(子线程执行,避免主线程阻塞,适配API30前台服务规范) - */ - private void checkServiceAsync() { - LogUtils.d(TAG, "checkServiceAsync: 进入"); - new Thread(new Runnable() { - @Override - public void run() { - LogUtils.d(TAG, "checkServiceAsync: 异步线程启动"); - if (mAppConfigUtils == null || mServiceControlBean == null || isFinishing()) { - LogUtils.e(TAG, "checkServiceAsync: 配置工具类/服务控制Bean/Activity无效,跳过检查"); - return; - } - // 服务启用(本地配置)且未运行时,启动前台服务(API30必须前台服务,小米保活优化) - if (mServiceControlBean.isEnableService() && !ServiceUtils.isServiceAlive(getActivity(), ControlCenterService.class.getName())) { - LogUtils.d(TAG, "checkServiceAsync: 服务未运行,启动前台服务"); - runOnUiThread(new Runnable() { - @Override - public void run() { - if (!isFinishing()) { - ControlCenterService.startControlCenterService(getActivity()); - } - } - }); - } - LogUtils.d(TAG, "checkServiceAsync: 异步线程结束"); - } - }).start(); - LogUtils.d(TAG, "checkServiceAsync: 退出"); - } - - /** - * 重启RemindThread线程(通过服务统一管理,保障线程安全,适配API30服务规范) + * 重启提醒线程(仅由配置变更触发,不与UI开关联动) */ private void restartRemindThread(AppConfigBean configBean) { LogUtils.d(TAG, "restartRemindThread: 进入"); if (configBean == null || mServiceControlBean == null) { - LogUtils.e(TAG, "restartRemindThread: 配置为空/服务控制Bean为空,跳过重启"); + LogUtils.e(TAG, "restartRemindThread: 配置/服务控制Bean为空"); return; } - // 服务未启用时,不重启线程(直接返回) if (!mServiceControlBean.isEnableService()) { LogUtils.w(TAG, "restartRemindThread: 服务已禁用,跳过线程重启"); return; } - // 服务未运行时,先启动服务 - if (!ServiceUtils.isServiceAlive(this, ControlCenterService.class.getName())) { - ControlCenterService.startControlCenterService(this); - LogUtils.d(TAG, "restartRemindThread: 服务未运行,先启动服务"); + if (!ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName())) { + ControlCenterService.startControlCenterService(getApplicationContext()); } - // 关键优化:重启前先销毁旧线程,避免线程复用导致的状态脏数据 RemindThread.destroyInstance(); - LogUtils.d(TAG, "restartRemindThread: 销毁旧线程完成,准备启动新线程"); - - // 发送重启指令到服务(通过Intent传递最新配置) - ControlCenterService.updateStatus(this, configBean); - LogUtils.d(TAG, "restartRemindThread: 发送线程重启指令到服务完成"); + ControlCenterService.updateStatus(getApplicationContext(), configBean); LogUtils.d(TAG, "restartRemindThread: 退出"); } /** - * 关键新增:设置RemindThread前台/后台状态(核心修复无限提醒问题) - * @param isForeground true=前台(开启提醒),false=后台(停止提醒) + * 设置线程前后台状态(依赖服务运行状态) */ private void setRemindThreadForeground(boolean isForeground) { LogUtils.d(TAG, "setRemindThreadForeground: 进入,前台状态=" + isForeground); - // 服务未运行时,跳过状态设置(线程未启动) - if (!ServiceUtils.isServiceAlive(this, ControlCenterService.class.getName())) { + if (!ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName())) { LogUtils.w(TAG, "setRemindThreadForeground: 服务未运行,跳过状态设置"); return; } - // 调用RemindThread新增方法,设置前台状态(线程安全) try { - RemindThread.getInstance(this, null).setAppForeground(isForeground); - LogUtils.d(TAG, "setRemindThreadForeground: RemindThread状态设置完成"); + RemindThread.getInstance(getApplicationContext(), null).setAppForeground(isForeground); } catch (IllegalArgumentException e) { - // 首次启动时Handler可能未就绪,容错处理(服务会后续同步状态) - LogUtils.w(TAG, "setRemindThreadForeground: 线程未初始化完成,后续服务会同步状态", e); + LogUtils.w(TAG, "setRemindThreadForeground: 线程未初始化", e); } LogUtils.d(TAG, "setRemindThreadForeground: 退出"); } /** - * 新增:切换服务启用/禁用状态(核心修复:持久化配置+启停服务+更新UI+重启线程) - * @param isEnable true=启用服务(自启动+启动服务),false=禁用服务(停止服务) + * 切换服务启用状态(仅持久化本地配置,点击UI开关时不联动服务启停) */ private void toggleServiceEnableState(boolean isEnable) { LogUtils.d(TAG, "toggleServiceEnableState: 进入,目标状态=" + isEnable); if (mServiceControlBean == null) { - LogUtils.e(TAG, "toggleServiceEnableState: 服务控制Bean为空,切换失败"); + LogUtils.e(TAG, "toggleServiceEnableState: 服务控制Bean为空"); return; } - // 1. 更新本地服务控制配置(持久化,实现自启动) + // 仅更新本地配置并持久化 mServiceControlBean.setIsEnableService(isEnable); - ControlCenterService.updateServiceControlConfig(this, mServiceControlBean); - LogUtils.d(TAG, "toggleServiceEnableState: 服务控制配置已持久化,自启动状态=" + isEnable); + ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean); + LogUtils.d(TAG, "toggleServiceEnableState: 本地服务控制配置已持久化,状态=" + isEnable); - // 2. 启停服务(通过服务静态方法,适配API30前台服务规范) - if (isEnable) { - // 启用:启动前台服务+检查电池优化(避免服务被杀死) - ControlCenterService.startControlCenterService(this); - ControlCenterService.checkIgnoreBatteryOptimization(this); - LogUtils.d(TAG, "toggleServiceEnableState: 服务已启动,已引导忽略电池优化"); - } else { - // 禁用:停止服务(服务内部会自动停止线程+释放资源) - ControlCenterService.stopControlCenterService(this); - LogUtils.d(TAG, "toggleServiceEnableState: 服务已停止"); - } - - // 3. 服务启用时,重启线程(确保线程使用最新配置) - if (isEnable && mAppConfigUtils != null && mAppConfigUtils.mAppConfigBean != null) { - sendRestartRemindThreadMessage(); - } - - // 4. 更新服务开关UI(确保开关状态与实际服务状态一致) + // 仅更新UI,无服务启停操作 sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH); - LogUtils.d(TAG, "toggleServiceEnableState: 服务状态切换完成"); LogUtils.d(TAG, "toggleServiceEnableState: 退出"); } - // ======================== 页面跳转方法(封装逻辑,减少冗余)======================== - /** - * 启动关于页面(封装跳转逻辑,统一参数传递,便于维护) - */ + // ======================== 页面跳转方法 ======================== 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); - LogUtils.d(TAG, "startAboutActivity: 启动关于页面完成"); LogUtils.d(TAG, "startAboutActivity: 退出"); } - // ======================== 消息发送方法(统一封装,避免冗余,保障消息安全)======================== - /** - * 发送重启RemindThread线程消息(携带最新配置,确保线程使用最新参数) - */ + // ======================== 消息发送方法 ======================== private void sendRestartRemindThreadMessage() { LogUtils.d(TAG, "sendRestartRemindThreadMessage: 进入"); if (sGlobalHandler == null || mAppConfigUtils == null || mAppConfigUtils.mAppConfigBean == null) { - LogUtils.e(TAG, "sendRestartRemindThreadMessage: Handler/配置为空,跳过发送"); + LogUtils.e(TAG, "sendRestartRemindThreadMessage: 环境无效"); return; } - // 复用Message,避免内存抖动(优化性能) Message msg = sGlobalHandler.obtainMessage(MSG_RESTART_REMIND_THREAD); msg.obj = mAppConfigUtils.mAppConfigBean; sGlobalHandler.sendMessage(msg); - LogUtils.d(TAG, "sendRestartRemindThreadMessage: 发送线程重启消息完成"); LogUtils.d(TAG, "sendRestartRemindThreadMessage: 退出"); } - // ======================== 静态工具方法(全局调用,统一入口)======================== - /** - * 重新加载应用配置(全局调用,触发视图更新) - */ + // ======================== 静态工具方法 ======================== public static void reloadAppConfig() { - LogUtils.d(TAG, "reloadAppConfig: 发送重新加载配置消息"); + LogUtils.d(TAG, "reloadAppConfig: 发送配置更新消息"); if (sGlobalHandler != null) { sGlobalHandler.sendEmptyMessage(MSG_RELOAD_APPCONFIG); } } - /** - * 发送当前电量更新消息(全局调用,实时更新UI) - */ public static void sendCurrentBatteryValueMessage(int value) { - LogUtils.d(TAG, "sendCurrentBatteryValueMessage: 电量=" + value + ",发送更新消息"); + LogUtils.d(TAG, "sendCurrentBatteryValueMessage: 电量=" + value); if (sGlobalHandler != null) { Message msg = sGlobalHandler.obtainMessage(MSG_CURRENTVALUEBATTERY); msg.arg1 = value; @@ -713,14 +594,11 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV } } - // ======================== 辅助工具方法(封装重复逻辑,提升复用性)======================== - /** - * 从URI获取图片真实路径(适配API30媒体存储权限,Java7显式Cursor操作,避免资源泄漏) - */ + // ======================== 辅助工具方法 ======================== private String getRealPathFromUri(Uri contentUri) { LogUtils.d(TAG, "getRealPathFromUri: 进入"); if (contentUri == null) { - LogUtils.e(TAG, "getRealPathFromUri: URI为空,获取失败"); + LogUtils.e(TAG, "getRealPathFromUri: URI为空"); return null; } String realPath = null; @@ -732,24 +610,16 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV int columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); if (columnIndex > -1) { realPath = cursor.getString(columnIndex); - LogUtils.d(TAG, "getRealPathFromUri: 获取图片路径=" + realPath); } } } finally { - // Java7必须显式关闭Cursor,避免资源泄漏(无try-with-resources语法) cursor.close(); } } - if (realPath == null) { - LogUtils.e(TAG, "getRealPathFromUri: 获取图片路径失败"); - } - LogUtils.d(TAG, "getRealPathFromUri: 退出"); + LogUtils.d(TAG, "getRealPathFromUri: 退出,路径=" + realPath); return realPath; } - /** - * 生成默认应用信息(关于页面使用,封装参数赋值,便于维护) - */ private APPInfo genDefaultAppInfo() { LogUtils.d(TAG, "genDefaultAppInfo: 进入"); String branchName = "powerbell"; @@ -764,12 +634,11 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=PowerBell"); appInfo.setAppAPKName("PowerBell"); appInfo.setAppAPKFolderName("PowerBell"); - LogUtils.d(TAG, "genDefaultAppInfo: 生成应用信息完成"); LogUtils.d(TAG, "genDefaultAppInfo: 退出"); return appInfo; } - // ======================== MainContentView 事件回调实现(视图事件触发业务逻辑)======================== + // ======================== MainContentView 事件回调 ======================== @Override public void onChargeReminderSwitchChanged(boolean isChecked) { LogUtils.d(TAG, "onChargeReminderSwitchChanged: 状态=" + isChecked); @@ -785,7 +654,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV @Override public void onServiceSwitchChanged(boolean isChecked) { LogUtils.d(TAG, "onServiceSwitchChanged: 状态=" + isChecked); - // 核心修复:点击服务开关,触发「配置持久化+服务启停+UI更新」全链路逻辑 + // 仅更新本地配置,不触发服务启停 toggleServiceEnableState(isChecked); } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ControlCenterService.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ControlCenterService.java index 65313ed..e446269 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ControlCenterService.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/services/ControlCenterService.java @@ -1,6 +1,7 @@ package cc.winboll.studio.powerbell.services; import android.app.ActivityManager; +import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -10,24 +11,26 @@ import android.os.IBinder; import android.os.PowerManager; import android.provider.Settings; import android.text.TextUtils; -import java.io.Serializable; -import java.util.List; +import androidx.core.app.NotificationCompat; import cc.winboll.studio.libappbase.LogUtils; +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.threads.RemindThread; import cc.winboll.studio.powerbell.utils.NotificationManagerUtils; +import java.io.Serializable; +import java.util.List; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/12/17 15:48 - * @Describe 电池提醒核心服务:通过本地持久化 ControlCenterServiceBean 控制自启动,统一管理线程、通知、配置,适配Java7+API30 + * @Describe 电池提醒核心服务:修复前台服务超时异常,优先在onCreate发送通知,适配Java7+API36 */ public class ControlCenterService extends Service { // ================================== 静态常量(置顶统一管理,避免魔法值)================================= - private static final String TAG = "ControlCenterService"; + public static final String TAG = "ControlCenterService"; // 服务指令常量(带包名前缀,防止冲突) public static final String ACTION_RESTART_REMIND_THREAD = "cc.winboll.studio.powerbell.action.RESTART_REMIND_THREAD"; public static final String EXTRA_APP_CONFIG_BEAN = "cc.winboll.studio.powerbell.extra.APP_CONFIG_BEAN"; @@ -52,7 +55,7 @@ public class ControlCenterService extends Service { private boolean mIsDestroyed = false; // 服务销毁标记 private final Object mServiceLock = new Object(); // 全局并发锁 - // ================================== 服务生命周期方法(onCreate→onStartCommand→onDestroy)================================= + // ================================== 服务生命周期方法(核心修复:onCreate优先发送通知)================================= @Override public void onCreate() { super.onCreate(); @@ -60,7 +63,11 @@ public class ControlCenterService extends Service { synchronized (mServiceLock) { isServiceRunning = true; mIsDestroyed = false; - // 初始化服务控制配置:优先读取本地持久化配置,无则创建默认禁用配置 + + // ================================== 【修复点1:最优先执行前台通知发送】================================= + initForegroundNotificationImmediately(); + + // 原有逻辑:读取本地服务控制配置 mServiceControlBean = ControlCenterServiceBean.loadBean(this, ControlCenterServiceBean.class); if (mServiceControlBean == null) { mServiceControlBean = new ControlCenterServiceBean(false); @@ -70,7 +77,7 @@ public class ControlCenterService extends Service { LogUtils.d(TAG, "onCreate: 从本地读取服务控制配置,启用状态=" + mServiceControlBean.isEnableService()); } } - LogUtils.d(TAG, "onCreate: 服务创建完成,当前服务控制状态:" + (mServiceControlBean.isEnableService() ? "启用" : "禁用")); + LogUtils.d(TAG, "onCreate: 服务创建完成,前台通知已发送,当前服务控制状态:" + (mServiceControlBean.isEnableService() ? "启用" : "禁用")); } @Override @@ -82,9 +89,9 @@ public class ControlCenterService extends Service { synchronized (mServiceLock) { if (mServiceControlBean.isEnableService()) { LogUtils.d(TAG, "onStartCommand: 服务已启用,执行核心逻辑"); - // 启用状态:处理指令+绑定前台通知+启动业务,返回START_STICKY(回收后重启) + // 启用状态:处理指令+启动业务,返回START_STICKY(回收后重启) handleExternalCommand(intent); - rebindForegroundNotify(); + // ================================== 【修复点2:删除rebindForegroundNotify(),避免重复调用】================================= initServiceBusinessIfNeed(); // 按需初始化业务(避免重复初始化) // 新增:业务初始化完成后,同步应用前台状态到线程(防止状态丢失) syncAppForegroundStateToThread(); @@ -123,6 +130,54 @@ public class ControlCenterService extends Service { return null; // 无需绑定服务,返回null } + // ================================== 【新增核心方法:优先发送前台通知,带兜底方案】================================= + /** + * 独立初始化前台通知并立即发送,不依赖任何业务逻辑,确保5秒内完成startForeground() + * 防止工具类异常导致超时,内置兜底方案 + */ + private void initForegroundNotificationImmediately() { + LogUtils.d(TAG, "initForegroundNotificationImmediately: 强制初始化前台通知"); + try { + // 1. 初始化通知工具类 + if (mNotificationManager == null) { + mNotificationManager = new NotificationManagerUtils(this); + } + // 2. 构建极简前台通知模型(不依赖业务配置,确保最快速度发送) + if (mForegroundNotifyMsg == null) { + mForegroundNotifyMsg = new NotificationMessage(); + mForegroundNotifyMsg.setTitle("电池监测服务"); + mForegroundNotifyMsg.setContent("后台运行中"); + mForegroundNotifyMsg.setRemindMSG("service_running"); + } + // 3. 立即调用工具类发送前台通知 + mNotificationManager.startForegroundServiceNotify(this, mForegroundNotifyMsg); + LogUtils.d(TAG, "initForegroundNotificationImmediately: 前台通知发送成功,ID=" + NotificationManagerUtils.NOTIFY_ID_FOREGROUND_SERVICE); + } catch (Exception e) { + LogUtils.e(TAG, "initForegroundNotificationImmediately: 工具类发送失败,执行兜底方案", e); + // 兜底方案:绕过工具类,直接构建Notification调用startForeground + fallbackStartForeground(); + } + } + + /** + * 前台通知兜底方案:当工具类异常时,直接创建通知,确保不触发超时异常 + */ + private void fallbackStartForeground() { + try { + Notification notification = new NotificationCompat.Builder(this, NotificationManagerUtils.CHANNEL_ID_FOREGROUND) + .setContentTitle("电池监测服务") + .setContentText("后台运行中") + .setSmallIcon(R.drawable.ic_launcher) // 必须设置小图标,否则通知不显示 + .setPriority(NotificationCompat.PRIORITY_LOW) + .build(); + // 直接调用startForeground,不依赖任何外部工具 + startForeground(NotificationManagerUtils.NOTIFY_ID_FOREGROUND_SERVICE, notification); + LogUtils.d(TAG, "fallbackStartForeground: 兜底前台通知发送成功"); + } catch (Exception e) { + LogUtils.e(TAG, "fallbackStartForeground: 兜底发送失败,服务会被系统强制杀死", e); + } + } + // ================================== 核心:服务控制配置(本地持久化读写,替代Intent传递)================================= /** * 读取本地最新服务控制配置(实时同步,确保与用户设置一致) @@ -203,6 +258,7 @@ public class ControlCenterService extends Service { // ================================== 核心业务初始化方法(按依赖顺序排列)================================= /** * 初始化通知管理工具类,构建前台保活通知模型 + * 【修复点3:移除内部的startForegroundNotifyImmediately()调用,避免重复发送】 */ private void initNotificationManager() { LogUtils.d(TAG, "initNotificationManager: 初始化通知工具类"); @@ -215,8 +271,7 @@ public class ControlCenterService extends Service { mForegroundNotifyMsg.setTitle("电池提醒服务运行中"); mForegroundNotifyMsg.setContent("后台持续监测电池状态,确保提醒及时"); mForegroundNotifyMsg.setRemindMSG("service_running"); - // 启动前台保活通知 - startForegroundNotifyImmediately(); + // 移除:此处不再发送通知,通知已在onCreate中发送 } LogUtils.d(TAG, "initNotificationManager: 通知工具类初始化完成"); } catch (Exception e) { @@ -226,6 +281,7 @@ public class ControlCenterService extends Service { /** * 立即启动前台保活通知(API26+ 前台服务必需) + * 注:当前仅作为备用方法,实际已在initForegroundNotificationImmediately中调用 */ private void startForegroundNotifyImmediately() { LogUtils.d(TAG, "startForegroundNotifyImmediately: 启动前台保活通知"); @@ -243,7 +299,7 @@ public class ControlCenterService extends Service { } /** - * 重新绑定前台通知(onStartCommand触发,防止保活状态丢失) + * 重新绑定前台通知(已删除调用,防止重复执行startForeground) */ private void rebindForegroundNotify() { if (mNotificationManager == null || mNotificationManager.getForegroundServiceNotify() == null) { @@ -447,9 +503,7 @@ public class ControlCenterService extends Service { LogUtils.w(TAG, "handleExternalCommand: 服务已禁用,忽略线程重启指令"); } } - } - // 2. 新增:处理应用前台/后台状态同步指令(MainActivity触发) - else if (ACTION_SYNC_APP_FOREGROUND_STATE.equals(action)) { + } else if (ACTION_SYNC_APP_FOREGROUND_STATE.equals(action)) { boolean isForeground = intent.getBooleanExtra(EXTRA_APP_FOREGROUND_STATE, false); synchronized (mServiceLock) { mIsAppForeground = isForeground; @@ -514,9 +568,10 @@ public class ControlCenterService extends Service { LogUtils.d(TAG, "clearAllReferences: 所有引用已置空"); } - // ================================== 静态工具方法(新增前台状态同步入口,完善保活优化)================================= + // ================================== 静态工具方法(修复启停逻辑颠倒问题)================================= /** * 启动服务(静态入口,从本地读取控制配置,显式绑定包名,适配API30+) + * 【修复点4:统一使用startForegroundService,适配API26+】 */ public static void startControlCenterService(Context context) { LogUtils.d(TAG, "startControlCenterService: 启动服务入口"); @@ -524,26 +579,14 @@ public class ControlCenterService extends Service { LogUtils.e(TAG, "startControlCenterService: Context为空,启动失败"); return; } - if (isServiceRunning(context, ControlCenterService.class)) { - LogUtils.d(TAG, "startControlCenterService: 服务已运行,同步本地控制配置"); - // 服务已运行,触发onStartCommand同步最新配置 - sendSyncConfigCommand(context); - return; - } - Intent intent = new Intent(context, ControlCenterService.class); - intent.setPackage(context.getPackageName()); - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent); - } else { - context.startService(intent); - } - LogUtils.d(TAG, "startControlCenterService: 服务启动指令发送成功"); - } catch (Exception e) { - LogUtils.e(TAG, "startControlCenterService: 启动服务异常", e); + // 修复:API26+必须调用startForegroundService,避免系统异常 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); + } else { + context.startService(intent); } + LogUtils.d(TAG, "startControlCenterService: 服务启动指令发送成功"); } /** @@ -555,19 +598,14 @@ public class ControlCenterService extends Service { LogUtils.e(TAG, "stopControlCenterService: Context为空,停止失败"); return; } - if (!isServiceRunning(context, ControlCenterService.class)) { - LogUtils.d(TAG, "stopControlCenterService: 服务未运行,跳过停止"); - return; - } - Intent intent = new Intent(context, ControlCenterService.class); - intent.setPackage(context.getPackageName()); context.stopService(intent); LogUtils.d(TAG, "stopControlCenterService: 服务停止指令发送成功"); } /** * 新增:更新服务控制配置(本地持久化,动态切换服务启用/禁用状态) + * 【修复点5:修复启停逻辑颠倒问题】 */ public static void updateServiceControlConfig(Context context, ControlCenterServiceBean controlBean) { LogUtils.d(TAG, "updateServiceControlConfig: 更新服务控制配置,新状态=" + controlBean.isEnableService()); @@ -580,32 +618,14 @@ public class ControlCenterService extends Service { ControlCenterServiceBean.saveBean(context, controlBean); LogUtils.d(TAG, "updateServiceControlConfig: 控制配置已持久化"); - // 第二步:若服务已运行,触发同步(让服务立即生效) - if (isServiceRunning(context, ControlCenterService.class)) { - sendSyncConfigCommand(context); - LogUtils.d(TAG, "updateServiceControlConfig: 服务已运行,已触发配置同步"); - } else { - // 服务未运行,按需启动服务(根据新配置决定是否启用业务) + // 第二步:启停服务(修复:启用则启动,禁用则停止) + if (controlBean.isEnableService()) { + stopControlCenterService(context); startControlCenterService(context); - LogUtils.d(TAG, "updateServiceControlConfig: 服务未运行,已启动服务并加载新配置"); - } - } - - /** - * 新增:发送配置同步指令(触发服务读取本地最新配置,立即生效) - */ - private static void sendSyncConfigCommand(Context context) { - Intent intent = new Intent(context, ControlCenterService.class); - intent.setPackage(context.getPackageName()); - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent); - } else { - context.startService(intent); - } - } catch (Exception e) { - LogUtils.e(TAG, "sendSyncConfigCommand: 发送同步指令异常", e); + LogUtils.d(TAG, "updateServiceControlConfig: 服务已设置启动"); + } else { + stopControlCenterService(context); + LogUtils.d(TAG, "updateServiceControlConfig: 服务已设置停止"); } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java index 0093f98..cd2e66d 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java @@ -66,6 +66,8 @@ public class MainContentView { // 配置变更确认对话框(单例复用,避免重复创建) private AlertDialog mConfigConfirmDialog; + // 对话框 Builder(核心新增:解决 setMessage 不生效问题) + private AlertDialog.Builder mDialogBuilder; // 临时存储变更数据(对话框确认前缓存,取消时恢复) private TempConfigData mTempConfigData; // 对话框状态锁(避免快速点击重复弹窗) @@ -116,7 +118,7 @@ public class MainContentView { // 执行核心初始化流程(按顺序执行,避免依赖空指针) bindViews(rootView); initBatteryDrawables(); - initConfirmDialog(); + initConfirmDialog(); // 先初始化对话框,再绑定监听 bindViewListeners(); LogUtils.d(TAG, "constructor: 整体初始化完成"); @@ -176,7 +178,7 @@ public class MainContentView { } /** - * 初始化配置变更确认对话框(单例复用,无模式,点击外部关闭,适配 API30 对话框机制) + * 初始化配置变更确认对话框(核心优化:保存 Builder 实例,解决消息不生效问题) */ private void initConfirmDialog() { LogUtils.d(TAG, "initConfirmDialog: 开始初始化确认对话框"); @@ -185,29 +187,31 @@ public class MainContentView { return; } - // 构建无模式对话框(点击外部可关闭,取消改动) - AlertDialog.Builder builder = new AlertDialog.Builder(mContext); - builder.setTitle("配置变更确认"); - // 优化:区分服务开关/普通配置,显示不同提示语(提升用户体验) - builder.setMessage("是否确认修改当前配置?"); + // 1. 初始化 Builder(核心:后续通过 Builder 更新消息) + mDialogBuilder = new AlertDialog.Builder(mContext); + mDialogBuilder.setTitle("配置变更确认"); + mDialogBuilder.setMessage("是否确认修改当前配置?"); + // 确定按钮:保存配置+回调+更新视图 - builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { + mDialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { confirmConfigChange(); // 确认变更,保存配置 dialog.dismiss(); } }); + // 取消按钮:恢复原始配置(补充物理取消按钮,提升用户体验) - builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { + mDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { cancelConfigChange(); // 取消变更,恢复原始值 dialog.dismiss(); } }); + // 对话框外部点击监听:关闭对话框+恢复原始配置 - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + mDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { cancelConfigChange(); // 取消变更,恢复原始值 @@ -215,8 +219,8 @@ public class MainContentView { } }); - // 初始化对话框实例(设置可取消,支持外部点击关闭) - mConfigConfirmDialog = builder.create(); + // 2. 初始化对话框实例(设置可取消,支持外部点击关闭) + mConfigConfirmDialog = mDialogBuilder.create(); mConfigConfirmDialog.setCancelable(true); mConfigConfirmDialog.setCanceledOnTouchOutside(true); LogUtils.d(TAG, "initConfirmDialog: 确认对话框初始化完成"); @@ -246,6 +250,7 @@ public class MainContentView { } // 缓存变更数据,显示确认对话框 mTempConfigData = new TempConfigData(CHANGE_TYPE_CHARGE_SEEKBAR, originalValue, progress); + updateDialogMessageByChangeType(); // 更新提示语 showConfigConfirmDialog(); LogUtils.d(TAG, "ChargeReminderSeekBar: 触摸抬起触发变更,原始值=" + originalValue + ", 新进度=" + progress); } @@ -277,6 +282,7 @@ public class MainContentView { if (originalValue == newValue) return; // 缓存变更数据,显示确认对话框 mTempConfigData = new TempConfigData(CHANGE_TYPE_CHARGE_SWITCH, originalValue, newValue); + updateDialogMessageByChangeType(); // 更新提示语 showConfigConfirmDialog(); LogUtils.d(TAG, "cbEnableChargeReminder: 触发变更,原始值=" + originalValue + ", 变更后=" + newValue); } @@ -297,6 +303,7 @@ public class MainContentView { } // 缓存变更数据,显示确认对话框 mTempConfigData = new TempConfigData(CHANGE_TYPE_USAGE_SEEKBAR, originalValue, progress); + updateDialogMessageByChangeType(); // 更新提示语 showConfigConfirmDialog(); LogUtils.d(TAG, "UsageReminderSeekBar: 触摸抬起触发变更,原始值=" + originalValue + ", 新进度=" + progress); } @@ -328,6 +335,7 @@ public class MainContentView { if (originalValue == newValue) return; // 缓存变更数据,显示确认对话框 mTempConfigData = new TempConfigData(CHANGE_TYPE_USAGE_SWITCH, originalValue, newValue); + updateDialogMessageByChangeType(); // 更新提示语 showConfigConfirmDialog(); LogUtils.d(TAG, "cbEnableUsageReminder: 触发变更,原始值=" + originalValue + ", 变更后=" + newValue); } @@ -335,19 +343,21 @@ public class MainContentView { LogUtils.d(TAG, "bindViewListeners: 耗电提醒开关监听绑定完成"); } - // 服务总开关监听(核心优化:从 AppConfig 改为读取服务控制Bean,确保状态一致) + // 服务总开关监听(核心优化:逻辑与其他控件完全对齐) if (swEnableService != null) { swEnableService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - // 关键调整:服务开关状态从服务控制Bean读取,而非AppConfig + // 从服务控制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); } @@ -376,7 +386,7 @@ public class MainContentView { int currentVal = mAppConfigUtils.getCurrentBatteryValue(); boolean chargeEnable = mAppConfigUtils.isChargeReminderEnabled(); boolean usageEnable = mAppConfigUtils.isUsageReminderEnabled(); - // 关键调整:服务开关状态从服务控制Bean读取,确保与服务实际状态一致 + // 从服务控制Bean读取状态,确保UI与实际一致 boolean serviceEnable = getServiceEnableState(); LogUtils.d(TAG, "updateViewData: 配置数据读取完成,charge=" + chargeVal + ", usage=" + usageVal + ", current=" + currentVal + ", serviceEnable=" + serviceEnable); @@ -424,7 +434,7 @@ public class MainContentView { if (cbEnableUsageReminder != null) cbEnableUsageReminder.setChecked(usageEnable); LogUtils.d(TAG, "updateViewData: 耗电提醒视图更新完成"); - // 服务开关+提示文本更新(核心调整:使用服务控制Bean状态) + // 服务开关+提示文本更新(确保状态准确) if (swEnableService != null) { swEnableService.setChecked(serviceEnable); swEnableService.setText(mContext.getString(R.string.txt_aboveswitch)); @@ -471,6 +481,8 @@ public class MainContentView { mConfigConfirmDialog.setOnCancelListener(null); mConfigConfirmDialog = null; } + // 释放 Builder + mDialogBuilder = null; // 释放临时数据 mTempConfigData = null; @@ -506,7 +518,7 @@ public class MainContentView { } /** - * 新增:设置服务开关启用状态(外部调用,同步 UI 与服务状态,适配 Activity 视图刷新) + * 设置服务开关启用状态(外部调用,同步 UI 与服务状态,适配 Activity 视图刷新) * @param enabled 服务启用状态 */ public void setServiceSwitchChecked(boolean enabled) { @@ -517,7 +529,7 @@ public class MainContentView { } /** - * 新增:设置服务开关点击状态(外部调用,避免更新 UI 时触发重复回调) + * 设置服务开关点击状态(外部调用,避免更新 UI 时触发重复回调) * @param enabled 是否允许点击 */ public void setServiceSwitchEnabled(boolean enabled) { @@ -537,9 +549,9 @@ public class MainContentView { LogUtils.d(TAG, "showConfigConfirmDialog: 对话框已显示,跳过重复调用"); return; } - // 基础校验:对话框/上下文为空 - if (mConfigConfirmDialog == null || mContext == null) { - LogUtils.e(TAG, "showConfigConfirmDialog: 对话框/上下文异常,无法显示"); + // 基础校验:对话框/上下文/Builder 为空 + if (mDialogBuilder == null || mContext == null) { + LogUtils.e(TAG, "showConfigConfirmDialog: 对话框Builder/上下文异常,无法显示"); if (mTempConfigData != null) cancelConfigChange(); return; } @@ -550,20 +562,20 @@ public class MainContentView { if (mTempConfigData != null) cancelConfigChange(); return; } + // 重新构建对话框(核心:确保最新消息生效) + mConfigConfirmDialog = mDialogBuilder.create(); // 显示对话框,设置状态锁+关闭监听 - if (!mConfigConfirmDialog.isShowing()) { - isDialogShowing = true; - mConfigConfirmDialog.show(); - // 对话框关闭时解锁(无论确认/取消/外部点击,均解锁) - mConfigConfirmDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - isDialogShowing = false; - mConfigConfirmDialog.setOnDismissListener(null); // 移除监听,避免内存泄漏 - } - }); - LogUtils.d(TAG, "showConfigConfirmDialog: 确认对话框显示成功"); - } + mConfigConfirmDialog.show(); + isDialogShowing = true; + // 对话框关闭时解锁 + mConfigConfirmDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + isDialogShowing = false; + mConfigConfirmDialog.setOnDismissListener(null); + } + }); + LogUtils.d(TAG, "showConfigConfirmDialog: 确认对话框显示成功"); } /** @@ -576,36 +588,36 @@ public class MainContentView { } switch (mTempConfigData.changeType) { - // 充电提醒开关(适配 AppConfigUtils 无 Activity 参数调用) + // 充电提醒开关 case CHANGE_TYPE_CHARGE_SWITCH: mAppConfigUtils.setChargeReminderEnabled(mTempConfigData.newBooleanValue); mActionListener.onChargeReminderSwitchChanged(mTempConfigData.newBooleanValue); LogUtils.d(TAG, "confirmConfigChange: 充电提醒开关确认,值=" + mTempConfigData.newBooleanValue); break; - // 耗电提醒开关(适配 AppConfigUtils 无 Activity 参数调用) + // 耗电提醒开关 case CHANGE_TYPE_USAGE_SWITCH: mAppConfigUtils.setUsageReminderEnabled(mTempConfigData.newBooleanValue); mActionListener.onUsageReminderSwitchChanged(mTempConfigData.newBooleanValue); LogUtils.d(TAG, "confirmConfigChange: 耗电提醒开关确认,值=" + mTempConfigData.newBooleanValue); break; - // 服务总开关(核心优化:同步更新服务控制Bean,而非AppConfig) + // 服务总开关(核心:持久化配置+触发 Activity 回调) case CHANGE_TYPE_SERVICE_SWITCH: - // 1. 保存服务控制配置到本地(持久化自启动状态) + // 1. 保存服务控制配置到本地 ControlCenterServiceBean serviceBean = ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class); if (serviceBean == null) serviceBean = new ControlCenterServiceBean(false); serviceBean.setIsEnableService(mTempConfigData.newBooleanValue); ControlCenterService.updateServiceControlConfig(mContext, serviceBean); - // 2. 回调 Activity,触发服务启停 + // 2. 强制触发 Activity 回调,执行服务启停逻辑 mActionListener.onServiceSwitchChanged(mTempConfigData.newBooleanValue); LogUtils.d(TAG, "confirmConfigChange: 服务开关确认,值=" + mTempConfigData.newBooleanValue + ",已持久化配置"); break; - // 充电提醒进度条(适配 AppConfigUtils 无 Activity 参数调用) + // 充电提醒进度条 case CHANGE_TYPE_CHARGE_SEEKBAR: mAppConfigUtils.setChargeReminderValue(mTempConfigData.newIntValue); mActionListener.onChargeReminderProgressChanged(mTempConfigData.newIntValue); LogUtils.d(TAG, "confirmConfigChange: 充电提醒进度确认,值=" + mTempConfigData.newIntValue); break; - // 耗电提醒进度条(适配 AppConfigUtils 无 Activity 参数调用) + // 耗电提醒进度条 case CHANGE_TYPE_USAGE_SEEKBAR: mAppConfigUtils.setUsageReminderValue(mTempConfigData.newIntValue); mActionListener.onUsageReminderProgressChanged(mTempConfigData.newIntValue); @@ -630,28 +642,24 @@ public class MainContentView { } switch (mTempConfigData.changeType) { - // 充电提醒开关:恢复原始状态 case CHANGE_TYPE_CHARGE_SWITCH: if (cbEnableChargeReminder != null) { cbEnableChargeReminder.setChecked(mTempConfigData.originalBooleanValue); } LogUtils.d(TAG, "cancelConfigChange: 充电提醒开关取消,恢复值=" + mTempConfigData.originalBooleanValue); break; - // 耗电提醒开关:恢复原始状态 case CHANGE_TYPE_USAGE_SWITCH: if (cbEnableUsageReminder != null) { cbEnableUsageReminder.setChecked(mTempConfigData.originalBooleanValue); } LogUtils.d(TAG, "cancelConfigChange: 耗电提醒开关取消,恢复值=" + mTempConfigData.originalBooleanValue); break; - // 服务总开关:恢复原始状态(核心优化:从服务控制Bean读取原始值) case CHANGE_TYPE_SERVICE_SWITCH: if (swEnableService != null) { swEnableService.setChecked(mTempConfigData.originalBooleanValue); } LogUtils.d(TAG, "cancelConfigChange: 服务开关取消,恢复值=" + mTempConfigData.originalBooleanValue); break; - // 充电提醒进度条:恢复原始进度+更新视图 case CHANGE_TYPE_CHARGE_SEEKBAR: if (sbChargeReminder != null) { sbChargeReminder.setProgress(mTempConfigData.originalIntValue); @@ -663,7 +671,6 @@ public class MainContentView { } LogUtils.d(TAG, "cancelConfigChange: 充电提醒进度取消,恢复值=" + mTempConfigData.originalIntValue); break; - // 耗电提醒进度条:恢复原始进度+更新视图 case CHANGE_TYPE_USAGE_SEEKBAR: if (sbUsageReminder != null) { sbUsageReminder.setProgress(mTempConfigData.originalIntValue); @@ -705,7 +712,7 @@ public class MainContentView { } /** - * 新增:获取服务启用状态(统一从服务控制Bean读取,确保全链路状态一致) + * 获取服务启用状态(统一从服务控制Bean读取,确保全链路状态一致) * @return 服务启用状态(true=启用,false=禁用) */ private boolean getServiceEnableState() { @@ -715,17 +722,22 @@ public class MainContentView { } /** - * 新增:根据变更类型更新对话框提示语(差异化提示,提升用户体验) + * 根据变更类型更新对话框提示语(核心优化:通过 Builder 更新,确保生效) */ private void updateDialogMessageByChangeType() { - if (mConfigConfirmDialog == null || mTempConfigData == null) return; + if (mDialogBuilder == null || mTempConfigData == null) return; String message; if (mTempConfigData.changeType == CHANGE_TYPE_SERVICE_SWITCH) { - message = mTempConfigData.newBooleanValue ? "启用服务后,将后台持续监控电池状态,是否确认?" : "禁用服务后,电池监控功能将停止,是否确认?"; + // 服务开关差异化提示语 + message = mTempConfigData.newBooleanValue ? + "启用服务后,将后台持续监控电池状态,是否确认?" : + "禁用服务后,电池监控功能将停止,是否确认?"; } else { + // 普通配置默认提示语 message = "是否确认修改当前配置?"; } - mConfigConfirmDialog.setMessage(message); + // 通过 Builder 设置消息,确保弹窗显示最新内容 + mDialogBuilder.setMessage(message); } // ======================== 事件回调接口(解耦视图与业务,提升扩展性)========================