From 7b17fae798257940e2cde777871aca046c32f9d9 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Wed, 17 Dec 2025 07:48:32 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B1=86=E5=8C=85=E5=AE=8C=E7=BE=8E=E6=83=B3?= =?UTF-8?q?=E8=B1=A1=E7=89=88=EF=BC=8C=E6=9C=AA=E8=B0=83=E8=AF=95=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 | 1195 +++++++++++------ .../powerbell/fragments/MainViewFragment.java | 42 - .../handlers/ControlCenterServiceHandler.java | 73 +- .../powerbell/models/AppConfigBean.java | 181 ++- .../models/ControlCenterServiceBean.java | 40 +- .../powerbell/models/NotificationMessage.java | 52 +- .../ControlCenterServiceReceiver.java | 338 ++++- .../receivers/GlobalApplicationReceiver.java | 153 ++- .../powerbell/receivers/MainReceiver.java | 2 +- .../powerbell/services/AssistantService.java | 4 +- .../services/ControlCenterService.java | 662 +++++---- .../powerbell/threads/RemindThread.java | 763 ++++++++--- .../powerbell/utils/AppConfigUtils.java | 581 +++++--- .../studio/powerbell/utils/BatteryUtils.java | 84 +- .../utils/NotificationManagerUtils.java | 693 +++++----- 16 files changed, 3186 insertions(+), 1681 deletions(-) delete mode 100644 powerbell/src/main/java/cc/winboll/studio/powerbell/fragments/MainViewFragment.java diff --git a/powerbell/build.properties b/powerbell/build.properties index a4754ed..5995847 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Dec 16 20:29:08 GMT 2025 +#Tue Dec 16 23:46:39 GMT 2025 stageCount=9 libraryProject= baseVersion=15.14 publishVersion=15.14.8 -buildCount=8 +buildCount=25 baseBetaVersion=15.14.9 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 4f829b4..f7182a4 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java @@ -1,11 +1,11 @@ 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; @@ -16,7 +16,6 @@ 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; @@ -25,20 +24,18 @@ 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.AppConfigBean; import cc.winboll.studio.powerbell.models.BackgroundBean; import cc.winboll.studio.powerbell.services.ControlCenterService; import cc.winboll.studio.powerbell.unittest.MainUnitTestActivity; @@ -49,59 +46,68 @@ 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 java.io.Serializable; /** - * 主活动类(Java 7 兼容 | 移除窗体缓存) - * 核心优化点: - * 1. ViewHolder 缓存控件 + ViewStub 延迟加载 - * 2. 资源异步加载 + 非关键任务异步化 + * 主活动类:应用核心页面,管理电池监控、背景设置、服务控制等核心功能 + * 适配:Java7 | API29-30 | 小米手机,优化性能与稳定性,杜绝内存泄漏 */ public class MainActivity extends WinBoLLActivity { - - // ======================== 静态常量(移除缓存相关键)======================== + // ======================== 静态常量(顶部统一,抽离魔法值,便于维护)======================== public static final String TAG = "MainActivity"; - private static final int REQUEST_READ_MEDIA_IMAGES = 1001; - 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 REQUEST_READ_MEDIA_IMAGES = 1001; // 背景设置请求码 + private static final int DELAY_LOAD_NON_CRITICAL = 500; // 非核心视图延迟加载时间(ms) + // Handler消息标识 + 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 MSG_RESTART_REMIND_THREAD = 3; // 重启RemindThread线程 - // ======================== 成员属性(移除缓存标识)======================== - public static MainActivity _mMainActivity; - static MainViewFragment _mMainViewFragment; - static Handler _mHandler; - PermissionUtils permissionUtils = PermissionUtils.getInstance(); + // ======================== 静态成员(全局共享,严格控制生命周期)======================== + public static MainActivity sMainActivity; // 全局Activity实例(避免内存泄漏,及时置空) + private static Handler sGlobalHandler; // 全局Handler(统一消息分发) - private App mApplication; + // ======================== 成员属性(按功能分类,优先核心依赖)======================== + // 工具类实例(单例模式,避免重复初始化) + private PermissionUtils mPermissionUtils; private AppConfigUtils mAppConfigUtils; private BackgroundSourceUtils mBgSourceUtils; - + // 应用与视图核心实例 + private App mApplication; private Toolbar mToolbar; private ViewStub mAdsViewStub; private ADsBannerView mADsBannerView; - + // 视图缓存容器(ViewHolder模式,减少findViewById调用) private ViewHolder mViewHolder; + // 菜单与资源实例 private Menu mMenu; - private Fragment mCurrentShowFragment; - private MainViewFragment mMainViewFragment; - private Drawable mDrawableFrame; - private BatteryDrawable mCurrentValueBatteryDrawable; - private BatteryDrawable mChargeReminderValueBatteryDrawable; - private BatteryDrawable mUsegeReminderValueBatteryDrawable; + private Drawable mFrameDrawable; + // 电池图标Drawable(复用实例,减少内存分配) + private BatteryDrawable mCurrentBatteryDrawable; + private BatteryDrawable mChargeReminderBatteryDrawable; + private BatteryDrawable mUsageReminderBatteryDrawable; - // ======================== 视图缓存容器(ViewHolder 不变)======================== + // ======================== 视图缓存容器(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; + BackgroundView backgroundView; // 背景视图 + RelativeLayout mainLayout; // 主布局 + LinearLayout llLeftSeekBar; // 充电提醒进度条布局 + LinearLayout llRightSeekBar; // 耗电提醒进度条布局 + CheckBox cbEnableChargeReminder; // 充电提醒开关 + CheckBox cbEnableUsageReminder; // 耗电提醒开关 + Switch swEnableService; // 服务总开关 + TextView tvTips; // 提示文本 + TextView tvChargeReminderValue; // 充电提醒阈值文本 + TextView tvUsageReminderValue; // 耗电提醒阈值文本 + TextView tvCurrentBatteryValue; // 当前电量文本 + VerticalSeekBar sbChargeReminder; // 充电提醒进度条 + VerticalSeekBar sbUsageReminder; // 耗电提醒进度条 + ImageView ivCurrentBattery; // 当前电量图标 + ImageView ivChargeReminderBattery; // 充电提醒电量图标 + ImageView ivUsageReminderBattery; // 耗电提醒电量图标 } - // ======================== 重写生命周期(移除缓存恢复/保存逻辑)======================== + // ======================== 生命周期方法(按执行顺序排列,清晰可控)======================== @Override public Activity getActivity() { return this; @@ -114,74 +120,125 @@ public class MainActivity extends WinBoLLActivity { @Override protected void onCreate(Bundle savedInstanceState) { - LogUtils.d(TAG, "onCreate(...) - 移除窗体缓存"); + LogUtils.d(TAG, "onCreate: enter"); super.onCreate(savedInstanceState); + // 初始化全局核心组件(Handler优先,避免消息丢失) initGlobalHandler(); - - // 恢复原始初始化流程:直接加载布局+初始化 + // 加载布局+初始化核心流程 setContentView(R.layout.activity_main); - initCoreUtilsAsync(); + initPermissionUtils(); initViewHolder(); initCriticalView(); + initCoreUtilsAsync(); loadNonCriticalViewDelayed(); - } - - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - permissionUtils.startPermissionRequest(this); - } + LogUtils.d(TAG, "onCreate: exit"); + } @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) { + LogUtils.d(TAG, "onPostCreate: enter"); + super.onPostCreate(savedInstanceState); + // 权限请求(PostCreate确保视图初始化完成) + mPermissionUtils.startPermissionRequest(this); + LogUtils.d(TAG, "onPostCreate: exit"); } @Override protected void onResume() { + LogUtils.d(TAG, "onResume: enter"); super.onResume(); - // 移除缓存恢复后的刷新逻辑,直接发送加载背景消息 - if (_mHandler != null) { - _mHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); + // 恢复时加载背景(适配小米手机后台切前台场景) + if (sGlobalHandler != null) { + sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); } + // 恢复广告(非核心,容错处理) if (mADsBannerView != null) { - mADsBannerView.resumeADs(MainActivity.this); + mADsBannerView.resumeADs(this); } + LogUtils.d(TAG, "onResume: exit"); } - // 其他生命周期方法(onCreateOptionsMenu、onOptionsItemSelected等保持原有不变) + @Override + protected void onDestroy() { + LogUtils.d(TAG, "onDestroy: enter"); + super.onDestroy(); + // 释放广告资源(避免内存泄漏,适配API29+内存管控) + if (mADsBannerView != null) { + mADsBannerView.releaseAdResources(); + mADsBannerView = null; + } + // 置空全局实例(杜绝内存泄漏) + sMainActivity = null; + // 移除Handler所有消息(避免内存泄漏,适配组件生命周期) + if (sGlobalHandler != null) { + sGlobalHandler.removeCallbacksAndMessages(null); + sGlobalHandler = null; + } + // 释放Drawable资源(API30 图片资源泄漏防护) + releaseDrawableResources(); + // 置空工具类与视图实例(辅助GC回收) + mPermissionUtils = null; + mAppConfigUtils = null; + mBgSourceUtils = null; + mViewHolder = null; + mMenu = null; + mFrameDrawable = null; + mApplication = null; + mToolbar = null; + mAdsViewStub = null; + LogUtils.d(TAG, "onDestroy: exit"); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + LogUtils.d(TAG, "onActivityResult: enter, requestCode=" + requestCode + ", resultCode=" + resultCode); + super.onActivityResult(requestCode, resultCode, data); + // 权限请求结果处理 + mPermissionUtils.handlePermissionRequest(this, requestCode, resultCode, data); + // 背景设置完成后重新加载背景 + if (requestCode == REQUEST_READ_MEDIA_IMAGES) { + if (sGlobalHandler != null) { + sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); + LogUtils.d(TAG, "onActivityResult: send load background message"); + } + } + LogUtils.d(TAG, "onActivityResult: exit"); + } + + // ======================== 菜单与导航相关方法(统一归类,逻辑清晰)======================== @Override public boolean onCreateOptionsMenu(Menu menu) { + LogUtils.d(TAG, "onCreateOptionsMenu: enter"); 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); + LogUtils.d(TAG, "onCreateOptionsMenu: exit"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { - int menuItemId = item.getItemId(); + LogUtils.d(TAG, "onOptionsItemSelected: enter, itemId=" + item.getItemId()); + // 主题切换处理 if (AESThemeUtil.onAppThemeItemSelected(this, item)) { recreate(); + LogUtils.d(TAG, "onOptionsItemSelected: theme changed, recreate activity"); return true; } + // 开发模式菜单处理 if (DevelopUtils.onDevelopItemSelected(this, item)) { - LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId())); + LogUtils.d(TAG, "onOptionsItemSelected: develop menu selected"); return true; } - switch (menuItemId) { + // 主菜单逻辑分发 + switch (item.getItemId()) { case R.id.action_settings: startActivity(new Intent(this, SettingsActivity.class)); break; @@ -198,350 +255,85 @@ 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); } + LogUtils.d(TAG, "onOptionsItemSelected: exit"); 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(); + // 隐藏返回按钮(主页面无返回需求) if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(false); + LogUtils.d(TAG, "setupToolbar: hide back button"); } } - // ======================== 移除缓存相关核心方法(restoreFromCache/refreshViewFromCache)======================== + // ======================== 页面交互相关方法(统一归类,用户操作响应)======================== + @Override + public void onBackPressed() { + // 退到后台,不销毁Activity(提升用户体验,适配小米手机后台保活) + moveTaskToBack(true); + LogUtils.d(TAG, "onBackPressed: move task to background"); + } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // 保留系统默认按键分发逻辑,无自定义需求 + return super.dispatchKeyEvent(event); + } + + // ======================== 核心初始化方法(按优先级排列,核心优先)======================== /** - * 初始化电量图标(复用逻辑) + * 初始化权限工具类(提前初始化,避免权限请求延迟) */ - 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)); - } - } - - // ======================== 原有核心方法(移除缓存判断逻辑)======================== - /** - * 刷新背景(移除缓存判断) - */ - 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 initPermissionUtils() { + LogUtils.d(TAG, "initPermissionUtils: enter"); + mPermissionUtils = PermissionUtils.getInstance(); + LogUtils.d(TAG, "initPermissionUtils: exit"); } /** - * 设置主页面背景颜色(移除缓存判断) - */ - 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); - } - } - - /** - * 设置视图数据(移除缓存判断) - */ - void setViewData() { - if (mViewHolder == null || mAppConfigUtils == null) return; - - int nChargeReminderValue = mAppConfigUtils.getChargeReminderValue(); - int nUsegeReminderValue = mAppConfigUtils.getUsageReminderValue(); - 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.isEnableChargeReminder()); - } - - 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.isEnableUsageReminder()); - } - - 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.setEnableChargeReminder(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.setEnableUsageReminder(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() { - new Thread(new Runnable() { - @Override - public void run() { - mApplication = (App) getApplication(); - mAppConfigUtils = App.getAppConfigUtils(getActivity()); - mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity()); - runOnUiThread(new Runnable() { - @Override - public void run() { - if (getActivity() != null) { - mDrawableFrame = getActivity().getDrawable(R.drawable.bg_frame); - } - setViewData(); - setViewListener(); - checkServiceAsync(); - if (_mHandler != null) { - _mHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); - } - } - }); - } - }).start(); - } - - /** - * 初始化首屏核心视图(保持原有) - */ - private void initCriticalView() { - _mMainActivity = this; - setSupportActionBar(mToolbar); - if (mToolbar != null) { - mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText); - } - } - - /** - * 延迟加载非首屏核心视图(保持原有) - */ - private void loadNonCriticalViewDelayed() { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (isFinishing() || isDestroyed()) return; - loadAdsView(); - } - }, DELAY_LOAD_NON_CRITICAL); - } - - /** - * 加载广告视图(保持原有) - */ - private void loadAdsView() { - if (mAdsViewStub == null) return; - if (mADsBannerView == null) { - View adsView = mAdsViewStub.inflate(); - mADsBannerView = (ADsBannerView) 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(); - } - - /** - * 初始化全局Handler(保持原有) + * 初始化全局Handler(统一消息分发,避免多Handler混乱,Java7 显式实现) */ private void initGlobalHandler() { - if (_mHandler == null) { - _mHandler = new Handler() { + LogUtils.d(TAG, "initGlobalHandler: enter"); + if (sGlobalHandler == null) { + sGlobalHandler = new Handler() { @Override public void handleMessage(Message msg) { + LogUtils.d(TAG, "handleMessage: msg.what=" + msg.what); switch (msg.what) { case MSG_RELOAD_APPCONFIG: - if (_mMainActivity != null && _mMainActivity.mViewHolder != null) { - _mMainActivity.setViewData(); + // 重新加载配置,更新视图(判空避免Activity销毁后操作) + if (sMainActivity != null && sMainActivity.mViewHolder != null && !sMainActivity.isFinishing()) { + // 兼容Java7:isDestroyed() 是API17+,但避免在Handler中调用,仅用isFinishing()防护 + sMainActivity.setViewData(); } break; case MSG_CURRENTVALUEBATTERY: - if (_mMainActivity != null && _mMainActivity.mViewHolder != null) { - _mMainActivity.setCurrentValueBattery(msg.arg1); + // 更新当前电量显示(判空防护) + if (sMainActivity != null && sMainActivity.mViewHolder != null && !sMainActivity.isFinishing()) { + sMainActivity.setCurrentBatteryValue(msg.arg1); } break; case MSG_LOAD_BACKGROUND: - if (_mMainActivity != null) { - _mMainActivity.reloadBackground(); - _mMainActivity.setBackgroundColor(); + // 加载背景+设置主布局颜色(判空防护) + if (sMainActivity != null && !sMainActivity.isFinishing()) { + sMainActivity.reloadBackground(); + sMainActivity.setMainLayoutBackgroundColor(); + } + break; + case MSG_RESTART_REMIND_THREAD: + // 重启RemindThread线程(接收配置Bean参数,判空防护) + if (sMainActivity != null && sMainActivity.mAppConfigUtils != null && !sMainActivity.isFinishing()) { + AppConfigBean configBean = (AppConfigBean) msg.obj; + sMainActivity.restartRemindThread(configBean); } break; } @@ -549,95 +341,610 @@ public class MainActivity extends WinBoLLActivity { } }; } + LogUtils.d(TAG, "initGlobalHandler: exit"); } - // 内部监听类(保持原有) - 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(); + /** + * 初始化ViewHolder(缓存视图,减少findViewById调用,提升性能,Java7 显式强转) + */ + private void initViewHolder() { + LogUtils.d(TAG, "initViewHolder: enter"); + mViewHolder = new ViewHolder(); + // 绑定核心视图(按布局层级绑定,便于排查绑定失败,Java7 显式强转) + mViewHolder.mainLayout = (RelativeLayout) findViewById(R.id.activitymainRelativeLayout1); + mViewHolder.backgroundView = (BackgroundView) findViewById(R.id.fragmentmainviewBackgroundView1); + mViewHolder.llLeftSeekBar = (LinearLayout) findViewById(R.id.fragmentmainviewLinearLayout1); + mViewHolder.llRightSeekBar = (LinearLayout) findViewById(R.id.fragmentmainviewLinearLayout2); + mViewHolder.cbEnableChargeReminder = (CheckBox) findViewById(R.id.fragmentmainviewCheckBox1); + mViewHolder.cbEnableUsageReminder = (CheckBox) findViewById(R.id.fragmentmainviewCheckBox2); + mViewHolder.swEnableService = (Switch) findViewById(R.id.fragmentandroidviewSwitch1); + mViewHolder.tvTips = (TextView) findViewById(R.id.fragmentandroidviewTextView1); + mViewHolder.tvChargeReminderValue = (TextView) findViewById(R.id.fragmentandroidviewTextView2); + mViewHolder.tvUsageReminderValue = (TextView) findViewById(R.id.fragmentandroidviewTextView3); + mViewHolder.tvCurrentBatteryValue = (TextView) findViewById(R.id.fragmentandroidviewTextView4); + mViewHolder.sbChargeReminder = (VerticalSeekBar) findViewById(R.id.fragmentandroidviewVerticalSeekBar1); + mViewHolder.sbUsageReminder = (VerticalSeekBar) findViewById(R.id.fragmentandroidviewVerticalSeekBar2); + mViewHolder.ivCurrentBattery = (ImageView) findViewById(R.id.fragmentandroidviewImageView1); + mViewHolder.ivChargeReminderBattery = (ImageView) findViewById(R.id.fragmentandroidviewImageView3); + mViewHolder.ivUsageReminderBattery = (ImageView) findViewById(R.id.fragmentandroidviewImageView2); + // 非核心视图(广告) + mAdsViewStub = (ViewStub) findViewById(R.id.stub_ads_banner); + mToolbar = (Toolbar) findViewById(R.id.toolbar); + // 绑定失败日志(便于调试布局ID错误) + if (mViewHolder.mainLayout == null) LogUtils.e(TAG, "initViewHolder: mainLayout bind failed"); + if (mViewHolder.backgroundView == null) LogUtils.e(TAG, "initViewHolder: backgroundView bind failed"); + LogUtils.d(TAG, "initViewHolder: exit"); + } + + /** + * 初始化首屏核心视图(优先级最高,保障首屏加载速度) + */ + private void initCriticalView() { + LogUtils.d(TAG, "initCriticalView: enter"); + sMainActivity = this; + // 初始化Toolbar + setSupportActionBar(mToolbar); + if (mToolbar != null) { + mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText); + LogUtils.d(TAG, "initCriticalView: toolbar style set success"); + } + LogUtils.d(TAG, "initCriticalView: exit"); + } + + /** + * 异步初始化核心工具类(避免主线程阻塞,提升首屏加载速度,Java7 显式Runnable) + */ + private void initCoreUtilsAsync() { + LogUtils.d(TAG, "initCoreUtilsAsync: enter"); + new Thread(new Runnable() { + @Override + public void run() { + LogUtils.d(TAG, "initCoreUtilsAsync: async thread start"); + // 初始化工具类(子线程执行,不阻塞主线程) + mApplication = (App) getApplication(); + // 修正1:App.getAppConfigUtils() 入参改为 Context,避免静态方法调用异常 + mAppConfigUtils = AppConfigUtils.getInstance(getApplicationContext()); + mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity()); + // 主线程更新UI(工具类初始化完成后,Java7 显式runOnUiThread) + runOnUiThread(new Runnable() { + @Override + public void run() { + LogUtils.d(TAG, "initCoreUtilsAsync: update UI on main thread"); + // 加载框架资源(判空Activity状态,避免销毁后操作) + if (!isFinishing()) { + // 修正2:API30 适配 getDrawable(),添加主题参数,兼容Java7 + 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); + } + } + // 更新视图数据+设置监听+检查服务状态+加载背景 + setViewData(); + setViewListeners(); + checkServiceAsync(); + if (sGlobalHandler != null) { + sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND); + } + } + }); + LogUtils.d(TAG, "initCoreUtilsAsync: async thread exit"); + } + }).start(); + LogUtils.d(TAG, "initCoreUtilsAsync: exit"); + } + + /** + * 延迟加载非核心视图(广告,避免影响首屏加载速度,适配小米手机性能管控,Java7 显式Runnable) + */ + private void loadNonCriticalViewDelayed() { + LogUtils.d(TAG, "loadNonCriticalViewDelayed: enter"); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + // 避免Activity销毁后执行(双重判空,API30 安全防护) + if (isFinishing()) { + LogUtils.w(TAG, "loadNonCriticalViewDelayed: activity is finishing/destroyed, skip"); + return; + } + loadAdsView(); + } + }, DELAY_LOAD_NON_CRITICAL); + LogUtils.d(TAG, "loadNonCriticalViewDelayed: exit"); + } + + // ======================== 视图与业务逻辑方法(按功能归类,清晰易维护)======================== + /** + * 加载广告视图(非核心,延迟加载,容错处理) + */ + private void loadAdsView() { + LogUtils.d(TAG, "loadAdsView: enter"); + if (mAdsViewStub == null) { + LogUtils.e(TAG, "loadAdsView: adsViewStub is null, load failed"); + return; + } + if (mADsBannerView == null) { + View adsView = mAdsViewStub.inflate(); + mADsBannerView = (ADsBannerView) adsView.findViewById(R.id.adsbanner); + LogUtils.d(TAG, "loadAdsView: ads view inflate success"); + } + LogUtils.d(TAG, "loadAdsView: exit"); + } + + /** + * 初始化电池图标Drawable(复用实例,减少内存分配,适配小米手机内存敏感特性) + */ + private void initBatteryDrawables() { + LogUtils.d(TAG, "initBatteryDrawables: enter"); + if (mCurrentBatteryDrawable == null) { + // 修正3:API30 适配 getColor(),添加主题参数,兼容Java7 + int colorCurrent = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + getResources().getColor(R.color.colorCurrent, getTheme()) : + getResources().getColor(R.color.colorCurrent); + mCurrentBatteryDrawable = new BatteryDrawable(colorCurrent); + } + if (mChargeReminderBatteryDrawable == null) { + int colorCharge = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + getResources().getColor(R.color.colorCharge, getTheme()) : + getResources().getColor(R.color.colorCharge); + mChargeReminderBatteryDrawable = new BatteryDrawable(colorCharge); + } + if (mUsageReminderBatteryDrawable == null) { + int colorUsege = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + getResources().getColor(R.color.colorUsege, getTheme()) : + getResources().getColor(R.color.colorUsege); + mUsageReminderBatteryDrawable = new BatteryDrawable(colorUsege); + } + LogUtils.d(TAG, "initBatteryDrawables: exit"); + } + + /** + * 释放Drawable资源(API30 图片资源泄漏防护,避免OOM) + */ + private void releaseDrawableResources() { + LogUtils.d(TAG, "releaseDrawableResources: enter"); + if (mCurrentBatteryDrawable != null) { + mCurrentBatteryDrawable = null; + } + if (mChargeReminderBatteryDrawable != null) { + mChargeReminderBatteryDrawable = null; + } + if (mUsageReminderBatteryDrawable != null) { + mUsageReminderBatteryDrawable = null; + } + if (mFrameDrawable != null) { + mFrameDrawable.setCallback(null); + mFrameDrawable = null; + } + LogUtils.d(TAG, "releaseDrawableResources: exit"); + } + + /** + * 设置视图数据(从配置读取数据,更新UI,容错处理,Java7 显式判空) + */ + private void setViewData() { + LogUtils.d(TAG, "setViewData: enter"); + if (mViewHolder == null || mAppConfigUtils == null) { + LogUtils.e(TAG, "setViewData: viewHolder/appConfigUtils is null, skip"); + return; + } + // 读取配置数据 + int chargeReminderValue = mAppConfigUtils.getChargeReminderValue(); + int usageReminderValue = mAppConfigUtils.getUsageReminderValue(); + int currentBatteryValue = mAppConfigUtils.getCurrentBatteryValue(); + boolean isChargeReminderEnable = mAppConfigUtils.isChargeReminderEnabled(); + boolean isUsageReminderEnable = mAppConfigUtils.isUsageReminderEnabled(); + boolean isServiceEnable = mAppConfigUtils.isServiceEnabled(); + LogUtils.d(TAG, "setViewData: charge=" + chargeReminderValue + ", usage=" + usageReminderValue + ", current=" + currentBatteryValue); + + // 设置进度条布局背景(判空防护) + if (mFrameDrawable != null) { + if (mViewHolder.llLeftSeekBar != null) mViewHolder.llLeftSeekBar.setBackground(mFrameDrawable); + if (mViewHolder.llRightSeekBar != null) mViewHolder.llRightSeekBar.setBackground(mFrameDrawable); } - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} + // 初始化电池图标并设置数据 + initBatteryDrawables(); + // 当前电量(判空防护,避免空指针) + if (mViewHolder.ivCurrentBattery != null) { + mCurrentBatteryDrawable.setValue(currentBatteryValue); + mViewHolder.ivCurrentBattery.setImageDrawable(mCurrentBatteryDrawable); + } + if (mViewHolder.tvCurrentBatteryValue != null) { + // 修正4:API30 适配 getColor(),添加主题参数 + int colorCurrent = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + getResources().getColor(R.color.colorCurrent, getTheme()) : + getResources().getColor(R.color.colorCurrent); + mViewHolder.tvCurrentBatteryValue.setTextColor(colorCurrent); + mViewHolder.tvCurrentBatteryValue.setText(String.valueOf(currentBatteryValue) + "%"); + } + // 充电提醒(判空防护) + if (mViewHolder.ivChargeReminderBattery != null) { + mChargeReminderBatteryDrawable.setValue(chargeReminderValue); + mViewHolder.ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable); + } + if (mViewHolder.tvChargeReminderValue != null) { + int colorCharge = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + getResources().getColor(R.color.colorCharge, getTheme()) : + getResources().getColor(R.color.colorCharge); + mViewHolder.tvChargeReminderValue.setTextColor(colorCharge); + mViewHolder.tvChargeReminderValue.setText(String.valueOf(chargeReminderValue) + "%"); + } + if (mViewHolder.sbChargeReminder != null) { + mViewHolder.sbChargeReminder.setProgress(chargeReminderValue); + } + if (mViewHolder.cbEnableChargeReminder != null) { + mViewHolder.cbEnableChargeReminder.setChecked(isChargeReminderEnable); + } + // 耗电提醒(判空防护) + if (mViewHolder.ivUsageReminderBattery != null) { + mUsageReminderBatteryDrawable.setValue(usageReminderValue); + mViewHolder.ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable); + } + if (mViewHolder.tvUsageReminderValue != null) { + int colorUsege = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + getResources().getColor(R.color.colorUsege, getTheme()) : + getResources().getColor(R.color.colorUsege); + mViewHolder.tvUsageReminderValue.setTextColor(colorUsege); + mViewHolder.tvUsageReminderValue.setText(String.valueOf(usageReminderValue) + "%"); + } + if (mViewHolder.sbUsageReminder != null) { + mViewHolder.sbUsageReminder.setProgress(usageReminderValue); + } + if (mViewHolder.cbEnableUsageReminder != null) { + mViewHolder.cbEnableUsageReminder.setChecked(isUsageReminderEnable); + } + // 服务开关(判空防护) + if (mViewHolder.swEnableService != null) { + mViewHolder.swEnableService.setChecked(isServiceEnable); + mViewHolder.swEnableService.setText(getString(R.string.txt_aboveswitch)); + } + // 提示文本(判空防护) + if (mViewHolder.tvTips != null) { + mViewHolder.tvTips.setText(getString(R.string.txt_aboveswitchtips)); + } + LogUtils.d(TAG, "setViewData: exit"); + } - @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) + "%"); + /** + * 设置视图监听(统一绑定,避免监听泄漏,逻辑清晰,Java7 显式匿名内部类) + */ + private void setViewListeners() { + LogUtils.d(TAG, "setViewListeners: enter"); + if (mViewHolder == null || mAppConfigUtils == null) { + LogUtils.e(TAG, "setViewListeners: viewHolder/appConfigUtils is null, skip"); + return; + } + // 充电提醒进度条监听 + if (mViewHolder.sbChargeReminder != null) { + mViewHolder.sbChargeReminder.setOnSeekBarChangeListener(new ChargeReminderSeekBarListener()); + } + // 充电提醒开关监听(Java7 显式OnClickListener) + if (mViewHolder.cbEnableChargeReminder != null) { + mViewHolder.cbEnableChargeReminder.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isChecked = mViewHolder.cbEnableChargeReminder.isChecked(); + mAppConfigUtils.setChargeReminderEnabled(MainActivity.this, isChecked); + // 配置变更后发送重启线程消息 + sendRestartRemindThreadMessage(); + LogUtils.d(TAG, "cbEnableChargeReminder: checked=" + isChecked + ", send restart thread msg"); + } + }); + } + // 耗电提醒进度条监听 + if (mViewHolder.sbUsageReminder != null) { + mViewHolder.sbUsageReminder.setOnSeekBarChangeListener(new UsageReminderSeekBarListener()); + } + // 耗电提醒开关监听(Java7 显式OnClickListener) + if (mViewHolder.cbEnableUsageReminder != null) { + mViewHolder.cbEnableUsageReminder.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isChecked = mViewHolder.cbEnableUsageReminder.isChecked(); + mAppConfigUtils.setUsageReminderEnabled(MainActivity.this, isChecked); + // 配置变更后发送重启线程消息 + sendRestartRemindThreadMessage(); + LogUtils.d(TAG, "cbEnableUsageReminder: checked=" + isChecked + ", send restart thread msg"); + } + }); + } + // 服务开关监听(Java7 显式OnClickListener,兼容Switch) + if (mViewHolder.swEnableService != null) { + mViewHolder.swEnableService.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean isChecked = ((Switch) v).isChecked(); + mAppConfigUtils.setServiceEnabled(getActivity(), isChecked); + // 配置变更后发送重启线程消息 + sendRestartRemindThreadMessage(); + LogUtils.d(TAG, "swEnableService: checked=" + isChecked + ", send restart thread msg"); + } + }); + } + LogUtils.d(TAG, "setViewListeners: exit"); + } + + /** + * 更新当前电量显示(实时更新,复用Drawable,提升性能,判空防护) + */ + private void setCurrentBatteryValue(int value) { + LogUtils.d(TAG, "setCurrentBatteryValue: enter, value=" + value); + if (mViewHolder == null || mCurrentBatteryDrawable == null || mViewHolder.tvCurrentBatteryValue == null) { + LogUtils.e(TAG, "setCurrentBatteryValue: viewHolder/drawable is null, skip"); + return; + } + // 校准电量范围(0-100,避免异常值,Java7 显式Math调用) + value = Math.min(Math.max(value, 0), 100); + mViewHolder.tvCurrentBatteryValue.setText(String.valueOf(value) + "%"); + mCurrentBatteryDrawable.setValue(value); + mCurrentBatteryDrawable.invalidateSelf(); + LogUtils.d(TAG, "setCurrentBatteryValue: exit"); + } + + /** + * 重新加载背景(适配背景切换,容错处理,适配小米手机图片加载特性) + */ + private void reloadBackground() { + LogUtils.d(TAG, "reloadBackground: enter"); + if (mViewHolder == null || mBgSourceUtils == null || mViewHolder.backgroundView == null) { + LogUtils.e(TAG, "reloadBackground: viewHolder/bgSourceUtils/backgroundView is null, skip"); + return; + } + BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean(); + if (currentBgBean != null) { + mViewHolder.backgroundView.loadBackgroundBean(currentBgBean); + LogUtils.d(TAG, "reloadBackground: load background bean success"); + } else { + LogUtils.e(TAG, "reloadBackground: current background bean is null, use default"); + mViewHolder.backgroundView.setBackgroundResource(R.drawable.default_background); + } + LogUtils.d(TAG, "reloadBackground: exit"); + } + + /** + * 设置主布局背景颜色(适配背景配置,容错处理,API30 安全防护) + */ + private void setMainLayoutBackgroundColor() { + LogUtils.d(TAG, "setMainLayoutBackgroundColor: enter"); + if (isFinishing() || mViewHolder == null || mBgSourceUtils == null || mViewHolder.mainLayout == null) { + LogUtils.e(TAG, "setMainLayoutBackgroundColor: activity/viewHolder is invalid, skip"); + return; + } + BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean(); + if (currentBgBean != null) { + int bgColor = currentBgBean.getPixelColor(); + mViewHolder.mainLayout.setBackgroundColor(bgColor); + LogUtils.d(TAG, "setMainLayoutBackgroundColor: set color success"); + } + LogUtils.d(TAG, "setMainLayoutBackgroundColor: exit"); + } + + /** + * 异步检查服务状态(避免主线程阻塞,适配API30 前台服务规范,小米保活适配,Java7 显式Runnable) + */ + private void checkServiceAsync() { + LogUtils.d(TAG, "checkServiceAsync: enter"); + new Thread(new Runnable() { + @Override + public void run() { + LogUtils.d(TAG, "checkServiceAsync: async thread start"); + if (mAppConfigUtils == null || isFinishing()) { + LogUtils.e(TAG, "checkServiceAsync: appConfigUtils/activity is invalid, skip"); + return; + } + // 服务启用且未运行时,启动前台服务(API30 必须前台服务,适配小米保活) + if (mAppConfigUtils.isServiceEnabled() && !ServiceUtils.isServiceAlive(getActivity(), ControlCenterService.class.getName())) { + LogUtils.d(TAG, "checkServiceAsync: service not alive, start foreground service"); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!isFinishing()) { + Intent serviceIntent = new Intent(getActivity(), ControlCenterService.class); + // 修正5:API26+ 前台服务启动,添加版本判断,兼容Java7 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getActivity().startForegroundService(serviceIntent); + } else { + getActivity().startService(serviceIntent); + } + } + } + }); + } + LogUtils.d(TAG, "checkServiceAsync: async thread exit"); + } + }).start(); + LogUtils.d(TAG, "checkServiceAsync: exit"); + } + + /** + * 启动关于页面(封装逻辑,减少代码冗余,Java7 显式Intent构建) + */ + private void startAboutActivity() { + LogUtils.d(TAG, "startAboutActivity: enter"); + 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: exit"); + } + + /** + * 发送重启RemindThread线程消息(统一封装,减少冗余,Java7 显式Message构建) + */ + private void sendRestartRemindThreadMessage() { + LogUtils.d(TAG, "sendRestartRemindThreadMessage: enter"); + if (sGlobalHandler == null || mAppConfigUtils == null || mAppConfigUtils.mAppConfigBean == null) { + LogUtils.e(TAG, "sendRestartRemindThreadMessage: handler/configBean is null, skip"); + return; + } + // 携带最新配置Bean发送消息,确保线程使用最新配置(复用Message,避免内存抖动) + Message msg = sGlobalHandler.obtainMessage(MSG_RESTART_REMIND_THREAD); + msg.obj = mAppConfigUtils.mAppConfigBean; + sGlobalHandler.sendMessage(msg); + LogUtils.d(TAG, "sendRestartRemindThreadMessage: send msg with configBean"); + } + + /** + * 重启RemindThread线程(通过ControlCenterService统一管理,保证线程安全) + */ + private void restartRemindThread(AppConfigBean configBean) { + LogUtils.d(TAG, "restartRemindThread: enter"); + if (configBean == null || !ServiceUtils.isServiceAlive(this, ControlCenterService.class.getName())) { + LogUtils.e(TAG, "restartRemindThread: configBean is null or service not alive, skip"); + return; + } + // 修正6:通过Intent传递配置,通知服务重启线程,添加版本判断启动服务 + Intent restartIntent = new Intent(this, ControlCenterService.class); + restartIntent.setAction(ControlCenterService.ACTION_RESTART_REMIND_THREAD); // 复用服务已定义Action + restartIntent.putExtra(ControlCenterService.EXTRA_APP_CONFIG_BEAN, (Serializable)configBean); // 传递最新配置 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(restartIntent); // API26+ 前台服务 + } else { + startService(restartIntent); // 低版本普通服务 + } + LogUtils.d(TAG, "restartRemindThread: send restart command to ControlCenterService via Intent"); + } + + // ======================== 静态工具方法(全局调用,统一入口,Java7 显式实现)======================== + /** + * 重新加载应用配置(全局调用,更新视图) + */ + public static void reloadAppConfig() { + LogUtils.d(TAG, "reloadAppConfig: send message"); + if (sGlobalHandler != null) { + sGlobalHandler.sendEmptyMessage(MSG_RELOAD_APPCONFIG); } } - 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.setUsageReminderValue(nUsegeReminderValue); - mViewHolder.tvUsegeReminderValue.setText(String.valueOf(nUsegeReminderValue) + "%"); - } - } - - // 静态工具方法(保持原有) - public static void relaodAppConfigs() { - if (_mHandler != null) { - _mHandler.sendMessage(_mHandler.obtainMessage(MSG_RELOAD_APPCONFIG)); - } - } - - public static void sendMsgCurrentValueBattery(int value) { - if (_mHandler != null) { - Message msg = _mHandler.obtainMessage(MSG_CURRENTVALUEBATTERY); + /** + * 发送当前电量更新消息(全局调用,实时更新UI,Java7 显式Message构建) + */ + public static void sendCurrentBatteryValueMessage(int value) { + LogUtils.d(TAG, "sendCurrentBatteryValueMessage: value=" + value); + if (sGlobalHandler != null) { + Message msg = sGlobalHandler.obtainMessage(MSG_CURRENTVALUEBATTERY); msg.arg1 = value; - _mHandler.sendMessage(msg); + sGlobalHandler.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; - } - cursor.close(); + /** + * 从URI获取图片真实路径(适配API30 媒体存储权限,容错处理,Java7 显式Cursor操作) + */ + private String getRealPathFromUri(Uri contentUri) { + LogUtils.d(TAG, "getRealPathFromUri: enter"); + if (contentUri == null) { + LogUtils.e(TAG, "getRealPathFromUri: contentUri is null"); + return null; } + String[] projection = {MediaStore.MediaColumns.DATA}; + Cursor cursor = getContentResolver().query(contentUri, projection, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + int columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); + if (columnIndex > -1) { + String realPath = cursor.getString(columnIndex); + LogUtils.d(TAG, "getRealPathFromUri: real path=" + realPath); + return realPath; + } + } + } finally { + // 确保Cursor关闭,避免资源泄漏(Java7 必须显式关闭,无try-with-resources) + cursor.close(); + } + } + LogUtils.e(TAG, "getRealPathFromUri: get path failed"); return null; } - APPInfo genDefaultAPPInfo() { - String szBranchName = "powerbell"; + /** + * 生成默认应用信息(关于页面使用,封装逻辑,便于维护,Java7 显式Bean赋值) + */ + private APPInfo genDefaultAppInfo() { + LogUtils.d(TAG, "genDefaultAppInfo: enter"); + 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"); + LogUtils.d(TAG, "genDefaultAppInfo: exit"); return appInfo; } + + // ======================== 内部监听类(私有内部类,避免外部依赖,减少内存泄漏,Java7 显式实现)======================== + /** + * 充电提醒进度条监听(单独封装,逻辑聚焦) + */ + private class ChargeReminderSeekBarListener implements SeekBar.OnSeekBarChangeListener { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mViewHolder == null || mChargeReminderBatteryDrawable == null || mViewHolder.tvChargeReminderValue == null) { + return; + } + // 实时更新文本与图标 + mViewHolder.tvChargeReminderValue.setText(String.valueOf(progress) + "%"); + mChargeReminderBatteryDrawable.setValue(progress); + mChargeReminderBatteryDrawable.invalidateSelf(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (mAppConfigUtils == null || mViewHolder == null || mViewHolder.tvChargeReminderValue == null) { + return; + } + // 修正7:VerticalSeekBar 获取进度,直接用 getProgress()(避免调用私有字段 _mnProgress 编译报错) + int progress = seekBar.getProgress(); + mAppConfigUtils.setChargeReminderValue(MainActivity.this, progress); + mViewHolder.tvChargeReminderValue.setText(String.valueOf(progress) + "%"); + // 配置变更后发送重启线程消息 + sendRestartRemindThreadMessage(); + LogUtils.d(TAG, "ChargeReminderSeekBar: save progress=" + progress + ", send restart thread msg"); + } + } + + /** + * 耗电提醒进度条监听(单独封装,逻辑聚焦) + */ + private class UsageReminderSeekBarListener implements SeekBar.OnSeekBarChangeListener { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mViewHolder == null || mUsageReminderBatteryDrawable == null || mViewHolder.tvUsageReminderValue == null) { + return; + } + // 实时更新文本与图标 + mViewHolder.tvUsageReminderValue.setText(String.valueOf(progress) + "%"); + mUsageReminderBatteryDrawable.setValue(progress); + mUsageReminderBatteryDrawable.invalidateSelf(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (mAppConfigUtils == null || mViewHolder == null || mViewHolder.tvUsageReminderValue == null) { + return; + } + // 修正8:VerticalSeekBar 获取进度,直接用 getProgress()(避免调用私有字段 _mnProgress 编译报错) + int progress = seekBar.getProgress(); + mAppConfigUtils.setUsageReminderValue(MainActivity.this, progress); + mViewHolder.tvUsageReminderValue.setText(String.valueOf(progress) + "%"); + // 配置变更后发送重启线程消息 + sendRestartRemindThreadMessage(); + LogUtils.d(TAG, "UsageReminderSeekBar: save progress=" + progress + ", send restart thread msg"); + } + } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/fragments/MainViewFragment.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/fragments/MainViewFragment.java deleted file mode 100644 index 7d586db..0000000 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/fragments/MainViewFragment.java +++ /dev/null @@ -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"; - - - -} - - - - diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java index 68aab99..dae8dc1 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/handlers/ControlCenterServiceHandler.java @@ -2,35 +2,72 @@ package cc.winboll.studio.powerbell.handlers; import android.os.Handler; import android.os.Message; -import cc.winboll.studio.powerbell.services.ControlCenterService; import java.lang.ref.WeakReference; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.powerbell.services.ControlCenterService; +import cc.winboll.studio.powerbell.models.NotificationMessage; +/** + * 服务通信Handler:弱引用持有服务,避免内存泄漏,适配Java7 + */ public class ControlCenterServiceHandler extends Handler { - public static final String TAG = ControlCenterServiceHandler.class.getSimpleName(); + // 消息类型常量(与RemindThread对应) + public static final int MSG_REMIND_TEXT = 1001; // 提醒消息 + private static final String TAG = "ControlCenterServiceHandler"; - public static final int MSG_REMIND_TEXT = 0; + // 弱引用持有服务实例(避免内存泄漏) + private WeakReference mwrControlCenterService; - WeakReference serviceWeakReference; + // 构造方法(传入服务实例) public ControlCenterServiceHandler(ControlCenterService service) { - serviceWeakReference = new WeakReference(service); + this.mwrControlCenterService = new WeakReference<>(service); } @Override public void handleMessage(Message msg) { + super.handleMessage(msg); + ControlCenterService service = mwrControlCenterService.get(); + if (service == null) { + LogUtils.e(TAG, "handleMessage: 服务实例为空,处理失败"); + 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: + // 接收RemindThread的提醒消息,调用通知工具类发送通知 + handleRemindMessage(service, (String) msg.obj); + break; + default: + LogUtils.d(TAG, "handleMessage: 未知消息类型,what=" + msg.what); + break; } } + + // 处理提醒消息(发送通知) + private void handleRemindMessage(ControlCenterService service, String content) { + LogUtils.d(TAG, "handleRemindMessage: 处理提醒消息,内容=" + content); + // 复用服务的通知工具类和前台通知模型 + if (service.getNotificationManager() == null) { + LogUtils.e(TAG, "handleRemindMessage: 通知工具类为空,发送失败"); + return; + } + + // 构建提醒通知消息(可按需求调整标题/内容) + NotificationMessage remindMsg = new NotificationMessage(); + if ("+".equals(content)) { // 充电提醒 + remindMsg.setTitle("充电提醒"); + remindMsg.setContent("电池电量已达标,建议及时断电保护电池~"); + remindMsg.setRemindMSG("charge_remind"); + } else if ("-".equals(content)) { // 耗电提醒 + remindMsg.setTitle("耗电提醒"); + remindMsg.setContent("电池电量偏低,建议及时充电~"); + remindMsg.setRemindMSG("usage_remind"); + } + + // 调用通知工具类发送提醒通知(复用现有逻辑) + service.getNotificationManager().showRemindNotification(service, remindMsg); + LogUtils.d(TAG, "handleRemindMessage: 提醒通知发送完成"); + } } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/AppConfigBean.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/AppConfigBean.java index a6becc3..27ba5ca 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/AppConfigBean.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/AppConfigBean.java @@ -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 = 1000; // 新增:电量检测间隔(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 CREATOR = new Parcelable.Creator() { + @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]; + } + }; } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ControlCenterServiceBean.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ControlCenterServiceBean.java index a7adc31..c25d4a7 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ControlCenterServiceBean.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/ControlCenterServiceBean.java @@ -5,17 +5,24 @@ package cc.winboll.studio.powerbell.models; * @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 { +// 核心修正:实现 Parcelable + Serializable 接口(适配持久化+Intent传递,API30 必备) +public class ControlCenterServiceBean extends BaseBean implements Parcelable, Serializable { + + // 序列化版本号(Serializable 必备,避免反序列化崩溃) + private static final long serialVersionUID = 1L; public static final String TAG = "ControlCenterServiceBean"; boolean isEnableService = false; - + public ControlCenterServiceBean() { this.isEnableService = false; } @@ -60,4 +67,33 @@ public class ControlCenterServiceBean extends BaseBean { jsonReader.endObject(); return bean; } + + // ======================== 补全:Parcelable 接口实现(API30 持久化/Intent传递必备)======================== + @Override + public int describeContents() { + return 0; // 无特殊内容描述,返回0即可 + } + + // 序列化:将对象属性写入 Parcel + @Override + public void writeToParcel(Parcel dest, int flags) { + // boolean 类型用 byte 存储(Parcel 无直接 writeBoolean 方法,Java7 适配) + dest.writeByte((byte) (isEnableService ? 1 : 0)); + } + + // 反序列化:从 Parcel 读取属性,创建对象(必须是 public static final 修饰) + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public ControlCenterServiceBean createFromParcel(Parcel source) { + // 从 Parcel 读取 byte,转为 boolean + boolean isEnable = source.readByte() != 0; + return new ControlCenterServiceBean(isEnable); + } + + @Override + public ControlCenterServiceBean[] newArray(int size) { + return new ControlCenterServiceBean[size]; + } + }; } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/NotificationMessage.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/NotificationMessage.java index 122097e..b1d46d4 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/models/NotificationMessage.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/models/NotificationMessage.java @@ -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; } - } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java index 5221b13..5416741 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/ControlCenterServiceReceiver.java @@ -6,89 +6,299 @@ 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.threads.RemindThread; 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; +/** + * 控制中心广播接收器:监听电池状态变化、通知更新、线程启动指令,适配API29-30及小米手机 + */ public class ControlCenterServiceReceiver extends BroadcastReceiver { + // ====================== 常量定义(顶部统一管理,清晰易维护) ====================== public static final String TAG = ControlCenterServiceReceiver.class.getSimpleName(); + public static final String ACTION_UPDATE_SERVICENOTIFICATION = "cc.winboll.studio.powerbell.action.UPDATE_SERVICENOTIFICATION"; + public static final String ACTION_START_REMINDTHREAD = "cc.winboll.studio.powerbell.action.START_REMINDTHREAD"; + public static final String EXTRA_APP_CONFIG_BEAN = "extra_app_config_bean"; - 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"; - - WeakReference mwrService; - // 存储电量指示值, - // 用于校验电量消息时的电量变化 - static volatile int _mnTheQuantityOfElectricityOld = -1; - static volatile boolean _mIsCharging = false; + // ====================== 成员变量(弱引用防泄漏,volatile保线程安全) ====================== + private WeakReference mwrControlCenterService; + private static volatile int sLastBatteryLevel = -1; + private static volatile boolean sIsCharging = false; + // ====================== 构造方法 ====================== public ControlCenterServiceReceiver(ControlCenterService service) { - mwrService = new WeakReference(service); + LogUtils.d(TAG, "constructor: init receiver"); + this.mwrControlCenterService = new WeakReference<>(service); } + // ====================== 广播核心接收逻辑 ====================== @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: enter, action=" + intent.getAction()); + if (context == null || intent == null || intent.getAction() == null) { + LogUtils.e(TAG, "onReceive: invalid params"); + return; + } + + // 弱引用获取服务,双重校验服务有效性(核心防护,避免内存泄漏+空指针) + ControlCenterService service = mwrControlCenterService != null ? mwrControlCenterService.get() : null; + if (service == null || service.isDestroyed()) { + LogUtils.e(TAG, "onReceive: ControlCenterService is destroyed or null"); + // 服务销毁后主动注销广播,彻底避免无效回调 + unregisterAction(context); + return; + } + + // 分action处理逻辑 + String action = intent.getAction(); + switch (action) { + case ACTION_UPDATE_SERVICENOTIFICATION: + handleUpdateServiceNotification(service); + break; + case Intent.ACTION_BATTERY_CHANGED: + handleBatteryStateChanged(context, service, intent); + break; + case ACTION_START_REMINDTHREAD: + handleStartRemindThread(service, intent); + break; + default: + LogUtils.w(TAG, "onReceive: unknown action=" + action); + } + LogUtils.d(TAG, "onReceive: exit"); + } + + // ====================== 业务逻辑方法(全链路容错+逻辑优化) ====================== + /** + * 处理前台服务通知更新(强化非空校验,适配工具类最新逻辑) + */ + private void handleUpdateServiceNotification(ControlCenterService service) { + LogUtils.d(TAG, "handleUpdateServiceNotification: start"); + try { + NotificationManagerUtils notifyUtils = service.getNotificationManager(); + NotificationMessage notifyMsg = service.getForegroundNotifyMsg(); + // 三重非空校验,避免工具类/消息/通知实例为空 + if (notifyUtils == null || notifyMsg == null) { + LogUtils.e(TAG, "handleUpdateServiceNotification: notify params null, rebuild notify first"); + // 兜底:通知实例为空时重建前台通知(适配工具类方法,避免空指针) + if (notifyUtils != null) { + notifyUtils.startForegroundServiceNotify(service, notifyMsg); } + return; } - // 新电池状态标志某一个有变化就更新显示信息 - if (_mIsCharging != isCharging || _mnTheQuantityOfElectricityOld != nTheQuantityOfElectricity) { - mwrService.get().updateServiceNotification(); - AppConfigUtils appConfigUtils = AppConfigUtils.getInstance(context); - appConfigUtils.loadAppConfigBean(); - AppConfigBean appConfigBean = appConfigUtils.mAppConfigBean; - appConfigBean.setCurrentValue(nTheQuantityOfElectricity); - appConfigBean.setCharging(isCharging); - mwrService.get().startRemindThread(appConfigBean); - - // 保存电池报告 - // 示例数据更新逻辑 -// List 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(); - try { - AppConfigBean appConfigBean = intent.getParcelableExtra("appConfigBean"); - if (appConfigBean != null) { - mwrService.get().startRemindThread(appConfigBean); - } - } catch (Exception e) { - LogUtils.e("ControlReceiver", "parse Parcelable config failed", e); - } - + // 调用工具类更新通知,确保方法匹配 + notifyUtils.updateForegroundServiceNotify(notifyMsg); + LogUtils.d(TAG, "handleUpdateServiceNotification: success"); + } catch (Exception e) { + LogUtils.e(TAG, "handleUpdateServiceNotification: failed", e); } } - // 注册 Receiver - // + /** + * 处理电池状态变化(核心适配 BatteryUtils,修复解析逻辑) + */ + private void handleBatteryStateChanged(Context context, ControlCenterService service, Intent intent) { + LogUtils.d(TAG, "handleBatteryStateChanged: start"); + try { + // 1. 适配 BatteryUtils 工具类:统一解析电池状态(删除错误的 getTheQuantityOfElectricity) + boolean currentCharging = BatteryUtils.isCharging(context); // 调用工具类判断充电状态 + int currentBatteryLevel = BatteryUtils.getCurrentBattery(context); // 调用工具类获取电量(0-100) + currentBatteryLevel = Math.min(Math.max(currentBatteryLevel, 0), 100); // 二次校验,确保范围有效 + LogUtils.d(TAG, "handleBatteryStateChanged: current state - charging=" + currentCharging + ", level=" + currentBatteryLevel); + + // 2. 状态无变化直接跳过,减少无效运算 + if (currentCharging == sIsCharging && currentBatteryLevel == sLastBatteryLevel) { + LogUtils.d(TAG, "handleBatteryStateChanged: no state change, skip"); + return; + } + + // 3. 加载最新配置(容错:配置加载失败用服务当前配置兜底) + AppConfigBean latestConfig = loadLatestConfig(context); + if (latestConfig == null) { + LogUtils.e(TAG, "handleBatteryStateChanged: load latest config failed, use service current config"); + latestConfig = service.getCurrentConfigBean(); + // 服务配置也为空时,加载默认配置 + if (latestConfig == null) { + latestConfig = new AppConfigBean(); + } + } + + // 4. 同步最新电池状态到配置,再更新服务配置(确保服务配置是最新的) + latestConfig.setCurrentBatteryValue(currentBatteryLevel); + latestConfig.setIsCharging(currentCharging); + service.setCurrentConfigBean(latestConfig); + + // 5. 智能更新线程(线程已初始化则更新配置,未初始化则启动,避免重复重启) + updateRemindThreadConfig(service, latestConfig, currentCharging, currentBatteryLevel); + + // 6. 缓存最新状态(volatile变量确保多线程可见性) + sIsCharging = currentCharging; + sLastBatteryLevel = currentBatteryLevel; + LogUtils.d(TAG, "handleBatteryStateChanged: success"); + } catch (Exception e) { + LogUtils.e(TAG, "handleBatteryStateChanged: failed", e); + } + } + + /** + * 处理线程启动指令(优化配置解析逻辑,强化线程启动容错) + */ + private void handleStartRemindThread(ControlCenterService service, Intent intent) { + LogUtils.d(TAG, "handleStartRemindThread: start"); + try { + // 安全解析配置(避免序列化异常) + AppConfigBean appConfigBean = null; + if (intent.hasExtra(EXTRA_APP_CONFIG_BEAN)) { + Object configObj = intent.getSerializableExtra(EXTRA_APP_CONFIG_BEAN); + if (configObj instanceof AppConfigBean) { + appConfigBean = (AppConfigBean) configObj; + // 配置补全:确保电池状态字段不为默认值(用缓存的最新状态) + appConfigBean.setIsCharging(sIsCharging); + appConfigBean.setCurrentBatteryValue(sLastBatteryLevel); + } + } + + if (appConfigBean != null) { + // 先更新服务配置,再重启线程(确保线程启动时拿到最新配置) + service.setCurrentConfigBean(appConfigBean); + // 重启线程(服务已实现安全重启逻辑,直接调用) + service.restartRemindThreadSafely(); + LogUtils.d(TAG, "handleStartRemindThread: success, thread restarted with new config"); + } else { + LogUtils.e(TAG, "handleStartRemindThread: appConfigBean is null or invalid"); + // 配置无效时,用缓存状态重启线程兜底 + service.restartRemindThreadSafely(); + } + } catch (Exception e) { + LogUtils.e(TAG, "handleStartRemindThread: failed", e); + } + } + + // ====================== 内部辅助方法(拆分逻辑+统一容错) ====================== + /** + * 加载最新配置(适配AppConfigUtils单例逻辑,强化异常捕获) + */ + private AppConfigBean loadLatestConfig(Context context) { + try { + if (context == null) { + LogUtils.e(TAG, "loadLatestConfig: context is null"); + return null; + } + AppConfigUtils configUtils = AppConfigUtils.getInstance(context); + if (configUtils == null) { + LogUtils.e(TAG, "loadLatestConfig: AppConfigUtils instance is null"); + return null; + } + // 强制重载配置,确保拿到最新用户设置 + configUtils.reloadAllConfig(); + return configUtils.mAppConfigBean; + } catch (Exception e) { + LogUtils.e(TAG, "loadLatestConfig: failed to load config", e); + return null; + } + } + + /** + * 更新提醒线程配置(智能判断线程状态,避免唤醒/启动冲突) + */ + private void updateRemindThreadConfig(ControlCenterService service, AppConfigBean config, boolean isCharging, int batteryLevel) { + try { + RemindThread remindThread = service.getRemindThread(); + if (remindThread == null) { + LogUtils.w(TAG, "updateRemindThreadConfig: remindThread is null, start thread first"); + // 线程未初始化,安全启动线程(服务方法已实现容错) + service.restartRemindThreadSafely(); + return; + } + + // 先同步完整配置(核心:setAppConfigBean已包含状态同步,无需重复设置) + remindThread.setAppConfigBean(config); + // 额外同步实时状态(兜底,确保最新) + remindThread.setIsCharging(isCharging); + remindThread.setQuantityOfElectricity(batteryLevel); + // 确保线程开启提醒功能 + remindThread.setIsReminding(true); + + // 线程未运行时唤醒,已运行时无需操作(避免重复唤醒导致逻辑紊乱) + if (!remindThread.isRunning()) { + synchronized (remindThread) { + remindThread.notify(); + LogUtils.d(TAG, "updateRemindThreadConfig: remindThread is woken up"); + } + } + LogUtils.d(TAG, "updateRemindThreadConfig: success, thread config updated"); + } catch (Exception e) { + LogUtils.e(TAG, "updateRemindThreadConfig: failed", e); + // 配置更新失败时,重启线程兜底 + service.restartRemindThreadSafely(); + } + } + + // ====================== 广播注册/注销(强化容错,避免重复注册/注销) ====================== 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: start"); + if (context == null) { + LogUtils.e(TAG, "registerAction: context is null"); + return; + } + + try { + // 先注销再注册,避免重复注册导致异常 + unregisterAction(context); + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_UPDATE_SERVICENOTIFICATION); + filter.addAction(ACTION_START_REMINDTHREAD); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + // 优先级设置(低于系统最高优先级,避免被系统拦截) + filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 10); + context.registerReceiver(this, filter); + LogUtils.d(TAG, "registerAction: success, broadcast receiver registered"); + } catch (Exception e) { + LogUtils.e(TAG, "registerAction: failed to register receiver", e); + } + } + + public void unregisterAction(Context context) { + LogUtils.d(TAG, "unregisterAction: start"); + if (context == null) { + LogUtils.e(TAG, "unregisterAction: context is null"); + return; + } + + try { + // 捕获注销异常(避免未注册时注销抛错) + context.unregisterReceiver(this); + LogUtils.d(TAG, "unregisterAction: success, broadcast receiver unregistered"); + } catch (IllegalArgumentException e) { + LogUtils.w(TAG, "unregisterAction: receiver not registered, skip"); + } catch (Exception e) { + LogUtils.e(TAG, "unregisterAction: failed to unregister receiver", e); + } + } + + // ====================== Getter方法(提供外部状态访问,强化线程安全) ====================== + public static int getLastBatteryLevel() { + return sLastBatteryLevel; + } + + public static boolean isLastCharging() { + return sIsCharging; + } + + // ====================== 主动释放资源(修复语法错误,彻底防泄漏) ====================== + public void release() { + LogUtils.d(TAG, "release: receiver release resources"); + // 置空弱引用,帮助GC回收 + if (mwrControlCenterService != null) { + mwrControlCenterService.clear(); + mwrControlCenterService = null; + } + // 重置静态状态缓存(修复重复赋值语法错误) + sLastBatteryLevel = -1; + sIsCharging = false; } } - - - + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/GlobalApplicationReceiver.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/GlobalApplicationReceiver.java index 49455ea..c28bfc8 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/GlobalApplicationReceiver.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/GlobalApplicationReceiver.java @@ -4,6 +4,7 @@ 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; @@ -13,53 +14,131 @@ public class GlobalApplicationReceiver extends BroadcastReceiver { public static final String TAG = "GlobalApplicationReceiver"; - AppConfigUtils mAppConfigUtils; - App mGlobalApplication; - // 存储电量指示值, - // 用于校验电量消息时的电量变化 - static volatile int _mnTheQuantityOfElectricityOld = -1; - static volatile boolean _mIsCharging = false; - // 保存当前实例, - // 便利封装 registerAction() 函数 - GlobalApplicationReceiver mReceiver; + private AppConfigUtils mAppConfigUtils; + private App mGlobalApplication; + // 存储历史电池状态,用于判断变化(优化命名,明确语义) + private static volatile int sLastBatteryLevel = -1; // 历史电量(0-100) + private static volatile boolean sLastIsCharging = false; // 历史充电状态 + // 当前Receiver实例(优化命名,避免歧义) + private GlobalApplicationReceiver mCurrentReceiver; + // 构造方法(强化参数校验,避免空指针) public GlobalApplicationReceiver(App globalApplication) { - mReceiver = this; - mGlobalApplication = globalApplication; - mAppConfigUtils = App.getAppConfigUtils(mGlobalApplication); + if (globalApplication == null) { + LogUtils.e(TAG, "GlobalApplicationReceiver: App instance is null"); + throw new IllegalArgumentException("App cannot be null"); + } + this.mCurrentReceiver = this; + this.mGlobalApplication = globalApplication; + this.mAppConfigUtils = App.getAppConfigUtils(mGlobalApplication); } @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.setCharging(isCharging); + LogUtils.d(TAG, "onReceive: enter, action=" + intent.getAction()); + // 双重空指针防护(context/intent/action 均校验) + if (context == null || intent == null || intent.getAction() == null) { + LogUtils.e(TAG, "onReceive: invalid params (context/intent is null)"); + return; + } + + // 仅处理电池状态变化广播 + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + handleBatteryStateChanged(context, intent); + } + LogUtils.d(TAG, "onReceive: exit"); + } + + /** + * 处理电池状态变化(核心逻辑:复用 BatteryUtils,优化状态同步) + */ + private void handleBatteryStateChanged(Context context, Intent intent) { + // 1. 用 BatteryUtils 解析当前电池状态(复用工具类,避免重复代码) + boolean currentIsCharging = BatteryUtils.isCharging(context); // 调用工具类判断充电状态 + int currentBatteryLevel = BatteryUtils.getCurrentBattery(context); // 调用工具类获取电量(0-100) + currentBatteryLevel = Math.min(Math.max(currentBatteryLevel, 0), 100); // 二次校验,确保范围有效 + + LogUtils.d(TAG, "handleBatteryStateChanged: current - charging=" + currentIsCharging + ", level=" + currentBatteryLevel); + + // 2. 状态无变化,直接跳过(避免无效运算) + if (currentIsCharging == sLastIsCharging && currentBatteryLevel == sLastBatteryLevel) { + LogUtils.d(TAG, "handleBatteryStateChanged: no state change, skip"); + return; + } + + // 3. 同步最新状态到配置工具类(确保配置实时更新) + if (mAppConfigUtils != null) { + if (currentIsCharging != sLastIsCharging) { + mAppConfigUtils.setCharging(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); // 同步当前电量 } + } else { + LogUtils.e(TAG, "handleBatteryStateChanged: AppConfigUtils is null, sync failed"); + } + + // 4. 电池状态变化后的业务逻辑(保留原有逻辑,强化空指针防护) + // 取消旧提醒(若后续需要启用,可调用 NotificationHelper.cancelRemindNotification(context)) + // 记录电量变化时间 + if (App.getAppCacheUtils(context) != null) { + App.getAppCacheUtils(context).addChangingTime(currentBatteryLevel); + } + // 发送电量更新消息到MainActivity + MainActivity.sendCurrentBatteryValueMessage(currentBatteryLevel); + + // 5. 更新历史状态缓存(volatile 保证多线程可见性) + sLastIsCharging = currentIsCharging; + sLastBatteryLevel = currentBatteryLevel; + LogUtils.d(TAG, "handleBatteryStateChanged: sync success, update last state"); + } + + // 注册广播(优化容错,避免重复注册) + public void registerAction() { + if (mGlobalApplication == null || mCurrentReceiver == null) { + LogUtils.e(TAG, "registerAction: App/receiver is null, register failed"); + return; + } + + try { + // 先注销再注册,避免重复注册导致异常 + unregisterAction(); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mGlobalApplication.registerReceiver(mCurrentReceiver, filter); + LogUtils.d(TAG, "registerAction: broadcast receiver registered success"); + } catch (Exception e) { + LogUtils.e(TAG, "registerAction: register failed", e); } } - // 注册 Receiver - // - public void registerAction() { - IntentFilter filter=new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - mGlobalApplication.registerReceiver(mReceiver, filter); + // 新增:注销广播(避免内存泄漏,供外部调用) + public void unregisterAction() { + if (mGlobalApplication == null || mCurrentReceiver == null) { + LogUtils.e(TAG, "unregisterAction: App/receiver is null, unregister failed"); + return; + } + + try { + mGlobalApplication.unregisterReceiver(mCurrentReceiver); + LogUtils.d(TAG, "unregisterAction: broadcast receiver unregistered success"); + } catch (IllegalArgumentException e) { + LogUtils.w(TAG, "unregisterAction: receiver not registered, skip"); + } catch (Exception e) { + LogUtils.e(TAG, "unregisterAction: unregister failed", e); + } + } + + // 新增:主动释放资源(供App销毁时调用,彻底防泄漏) + public void release() { + LogUtils.d(TAG, "release: receiver release resources"); + unregisterAction(); + mGlobalApplication = null; + mAppConfigUtils = null; + mCurrentReceiver = null; + // 重置静态缓存 + sLastBatteryLevel = -1; + sLastIsCharging = false; } } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/MainReceiver.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/MainReceiver.java index ce5aac5..3523292 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/MainReceiver.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/receivers/MainReceiver.java @@ -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"); diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/services/AssistantService.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/services/AssistantService.java index 48a6a7c..09c4b7c 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/services/AssistantService.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/services/AssistantService.java @@ -65,7 +65,7 @@ public class AssistantService extends Service { // void run() { //LogUtils.d(TAG, "run"); - if (mAppConfigUtils.getIsEnableService()) { + if (mAppConfigUtils.isServiceEnabled()) { if (mIsThreadAlive == false) { // 设置运行状态 mIsThreadAlive = true; @@ -97,7 +97,7 @@ public class AssistantService extends Service { @Override public void onServiceDisconnected(ComponentName name) { //LogUtils.d(TAG, "call onServiceDisconnected(...)"); - if (mAppConfigUtils.getIsEnableService()) { + if (mAppConfigUtils.isServiceEnabled()) { wakeupAndBindMain(); } } 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 471da4c..84b7667 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,314 +1,442 @@ 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 android.text.TextUtils; 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.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.io.Serializable; +import java.util.List; public class ControlCenterService extends Service { + private static final String TAG = "ControlCenterService"; + // Intent指令常量(完整包名前缀,避免冲突) + 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"; - public static final String TAG = "ControlCenterService"; - - public static final int MSG_UPDATE_STATUS = 0; - - 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 ControlCenterServiceHandler mServiceHandler; + private RemindThread mRemindThread; + private NotificationManagerUtils mNotificationManager; + private NotificationMessage mForegroundNotifyMsg; + private AppConfigBean mCurrentConfigBean; + private boolean isServiceRunning = false; + private boolean mIsDestroyed = false; // 服务销毁状态标记(供Receiver判断) + private final Object mServiceLock = new Object(); // 并发锁(避免多线程冲突) @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: 服务创建,初始化核心组件"); + isServiceRunning = true; + mIsDestroyed = false; + initNotificationManager(); + startForegroundNotifyImmediately(); + loadDefaultConfig(); + initServiceBusinessLogic(); + LogUtils.d(TAG, "onCreate: 服务初始化完成"); } @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: 触发服务启动命令,flags=" + flags); + handleExternalCommand(intent); - // 运行服务内容 - // - 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); + // 重新绑定前台服务,避免API30+状态丢失(加判空容错) + if (mNotificationManager != null && mNotificationManager.getForegroundServiceNotify() != null) { + try { + startForeground(NotificationManagerUtils.NOTIFY_ID_FOREGROUND_SERVICE, + mNotificationManager.getForegroundServiceNotify()); + LogUtils.d(TAG, "onStartCommand: 重新绑定前台通知保活"); + } catch (Exception e) { + LogUtils.e(TAG, "onStartCommand: 重新绑定前台通知异常", e); } + } + return START_STICKY; // 服务回收后自动重启 + } - 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); + // 处理外部指令(重启线程)- 加锁避免并发调用 + private void handleExternalCommand(Intent intent) { + LogUtils.d(TAG, "handleExternalCommand: 处理外部指令"); + if (intent == null) { + LogUtils.e(TAG, "handleExternalCommand: Intent为空,跳过"); + return; + } + String action = intent.getAction(); + if (TextUtils.isEmpty(action)) { + LogUtils.e(TAG, "handleExternalCommand: 指令Action为空,跳过"); + return; + } + synchronized (mServiceLock) { // 关键:加锁,防止并发触发重启导致线程冲突 + if (ACTION_RESTART_REMIND_THREAD.equals(action)) { + AppConfigBean newConfig = (AppConfigBean) intent.getSerializableExtra(EXTRA_APP_CONFIG_BEAN); + if (newConfig != null) { + mCurrentConfigBean = newConfig; + LogUtils.d(TAG, "handleExternalCommand: 收到重启指令,更新配置"); + restartRemindThreadSafely(); + } else { + LogUtils.e(TAG, "handleExternalCommand: 新配置为空,重启失败"); + } + } } } - String getValuesString() { - String szReturn = "Usege: "; - szReturn += mAppConfigUtils.isEnableUsageReminder() ? Integer.toString(mAppConfigUtils.getUsageReminderValue()) : "?"; - szReturn += "% Charge: "; - szReturn += mAppConfigUtils.isEnableChargeReminder() ? Integer.toString(mAppConfigUtils.getChargeReminderValue()) : "?"; - szReturn += "%\nCurrent: " + Integer.toString(mAppConfigUtils.getCurrentValue()) + "%"; - return szReturn; - } + // 安全重启提醒线程 - 核心修复:彻底销毁旧线程,确保新线程是全新实例 + public void restartRemindThreadSafely() { + LogUtils.d(TAG, "restartRemindThreadSafely: 重启提醒线程"); + synchronized (mServiceLock) { // 加锁,避免与服务销毁/其他重启操作冲突 + // 1. 先彻底停止旧线程(调用独立封装的安全停止方法) + stopRemindThreadSafely(); - 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.getUsageReminderValue()); - mRemindThread.setIsEnableChargeReminder(appConfigBean.isEnableChargeReminder()); - mRemindThread.setIsEnableUsegeReminder(appConfigBean.isEnableUsageReminder()); - mRemindThread.setSleepTime(appConfigBean.getReminderIntervalTime()); - mRemindThread.setIsCharging(appConfigBean.isCharging()); - mRemindThread.setQuantityOfElectricity(appConfigBean.getCurrentValue()); - //LogUtils.d(TAG, "mRemindThread update."); + // 2. 校验服务状态,避免服务已停止时启动线程 + if (!isServiceRunning || mServiceHandler == null || mIsDestroyed) { + LogUtils.e(TAG, "restartRemindThreadSafely: 服务未运行/已销毁/Handler为空,启动失败"); return; } + + // 3. 强制销毁RemindThread单例(关键:避免复用旧线程对象) + RemindThread.destroyInstance(); // 必须调用,确保getInstance返回新实例 + // 4. 创建新线程实例(此时是全新Thread对象,未启动) + mRemindThread = RemindThread.getInstance(this, mServiceHandler); + syncConfigToRemindThread(); + + // 5. 双重校验线程状态,杜绝重复start() + if (mRemindThread != null && !mRemindThread.isAlive() && !mRemindThread.isThreadStarted()) { + try { + mRemindThread.start(); // 修复崩溃核心:仅对全新线程调用start() + LogUtils.d(TAG, "restartRemindThreadSafely: 新线程启动成功,ID=" + mRemindThread.getId()); + } catch (IllegalThreadStateException e) { + // 兜底捕获:即使校验失败,也避免崩溃,打印详细日志 + LogUtils.e(TAG, "restartRemindThreadSafely: 线程重复启动异常(致命错误)", e); + mRemindThread = null; // 启动失败,置空避免后续错误 + } catch (Exception e) { + LogUtils.e(TAG, "restartRemindThreadSafely: 启动新线程异常", e); + mRemindThread = null; + } + } else { + LogUtils.w(TAG, "restartRemindThreadSafely: 线程已启动/实例异常,无需重复操作"); + mRemindThread = null; + } } - mRemindThread.setChargeReminderValue(appConfigBean.getChargeReminderValue()); - mRemindThread.setUsegeReminderValue(appConfigBean.getUsageReminderValue()); - mRemindThread.setSleepTime(appConfigBean.getReminderIntervalTime()); - mRemindThread.setIsCharging(appConfigBean.isCharging()); - mRemindThread.setQuantityOfElectricity(appConfigBean.getCurrentValue()); - mRemindThread.setIsEnableChargeReminder(appConfigBean.isEnableChargeReminder()); - mRemindThread.setIsEnableUsegeReminder(appConfigBean.isEnableUsageReminder()); - mRemindThread.start(); - //LogUtils.d(TAG, "mRemindThread.start()"); } - public void stopRemindThread() { - if (mRemindThread != null) { - mRemindThread.setIsExist(true); - mRemindThread = null; + // 安全停止提醒线程 - 优化:确保线程彻底终止,无残留 + private void stopRemindThreadSafely() { + LogUtils.d(TAG, "stopRemindThreadSafely: 安全停止提醒线程"); + if (mRemindThread == null) { + LogUtils.w(TAG, "stopRemindThreadSafely: 线程实例为空,跳过"); + return; } + + try { + // 1. 先通过线程内部方法标记停止(依赖RemindThread的stopThread实现) + if (mRemindThread.isThreadStarted() || mRemindThread.isAlive()) { + mRemindThread.setIsReminding(false); // 关闭提醒功能 + mRemindThread.stopThread(); // 触发线程安全停止(置位退出标记+中断休眠) + LogUtils.d(TAG, "stopRemindThreadSafely: 触发线程内部停止逻辑"); + + // 2. 等待线程终止(最多1秒,避免阻塞服务,超时强制置空) + long waitStartTime = System.currentTimeMillis(); + while (mRemindThread.isAlive()) { + if (System.currentTimeMillis() - waitStartTime > 1000) { + LogUtils.w(TAG, "stopRemindThreadSafely: 线程停止超时,强制终止处理"); + mRemindThread.interrupt(); // 强制中断,加速退出 + break; + } + Thread.yield(); // 让出CPU,优先执行线程退出逻辑 + } + } + } catch (Exception e) { + LogUtils.e(TAG, "stopRemindThreadSafely: 线程停止异常", e); + } finally { + // 3. 彻底释放:置空实例+销毁单例,杜绝复用 + mRemindThread = null; + RemindThread.destroyInstance(); // 关键:销毁单例,避免getInstance复用旧对象 + LogUtils.d(TAG, "stopRemindThreadSafely: 线程停止完成,资源已释放"); + } + } + + // 同步配置到提醒线程 - 加锁避免并发修改 + private void syncConfigToRemindThread() { + LogUtils.d(TAG, "syncConfigToRemindThread: 同步配置到线程"); + synchronized (mServiceLock) { + if (mRemindThread == null || mCurrentConfigBean == null) { + LogUtils.e(TAG, "syncConfigToRemindThread: 线程/配置为空,同步失败"); + return; + } + mRemindThread.setAppConfigBean(mCurrentConfigBean); // 同步完整配置 + mRemindThread.setIsReminding(true); // 开启提醒功能 + LogUtils.d(TAG, "syncConfigToRemindThread: 配置同步完成"); + } + } + + // 加载默认配置 - 优化:补全必要配置项,避免空指针 + private void loadDefaultConfig() { + LogUtils.d(TAG, "loadDefaultConfig: 加载默认配置"); + mCurrentConfigBean = new AppConfigBean(); + mCurrentConfigBean.setEnableChargeReminder(true); + mCurrentConfigBean.setChargeReminderValue(80); + mCurrentConfigBean.setEnableUsageReminder(true); + mCurrentConfigBean.setUsageReminderValue(20); + mCurrentConfigBean.setBatteryDetectInterval(1000); // 电池检测间隔(1秒) + LogUtils.d(TAG, "loadDefaultConfig: 默认配置加载完成"); + } + + // 初始化通知工具类 - 优化:移除无效调用,强化容错 + private void initNotificationManager() { + LogUtils.d(TAG, "initNotificationManager: 初始化通知工具类"); + try { + mNotificationManager = new NotificationManagerUtils(this); + mForegroundNotifyMsg = new NotificationMessage(); + mForegroundNotifyMsg.setTitle("电池提醒服务运行中"); + mForegroundNotifyMsg.setContent("后台持续监测电池状态,确保提醒及时"); + mForegroundNotifyMsg.setRemindMSG("service_running"); + // 移除无效调用:getForegroundServiceNotify() 无需提前触发,启动时自动构建 + } catch (Exception e) { + LogUtils.e(TAG, "initNotificationManager: 初始化失败", e); + stopSelf(); // 通知初始化失败,直接停止服务,避免后续异常 + } + } + + // 立即启动前台服务通知 - 优化:加try-catch避免启动失败崩溃 + private void startForegroundNotifyImmediately() { + LogUtils.d(TAG, "startForegroundNotifyImmediately: 启动前台保活通知"); + if (mNotificationManager == null || mForegroundNotifyMsg == null) { + LogUtils.e(TAG, "startForegroundNotifyImmediately: 依赖组件为空,启动失败"); + stopSelf(); + return; + } + + try { + mNotificationManager.startForegroundServiceNotify(this, mForegroundNotifyMsg); + LogUtils.d(TAG, "startForegroundNotifyImmediately: 前台通知启动成功,ID=" + NotificationManagerUtils.NOTIFY_ID_FOREGROUND_SERVICE); + } catch (Exception e) { + LogUtils.e(TAG, "startForegroundNotifyImmediately: 前台通知启动异常", e); + stopSelf(); // 前台服务启动失败,停止服务(API26+前台服务必须正常启动) + } + } + + // 初始化业务逻辑 - 优化:复用重启方法,统一线程启动逻辑 + private void initServiceBusinessLogic() { + LogUtils.d(TAG, "initServiceBusinessLogic: 初始化业务组件"); + mServiceHandler = new ControlCenterServiceHandler(this); + // 关键:复用restartRemindThreadSafely,避免重复写启动逻辑 + restartRemindThreadSafely(); + LogUtils.d(TAG, "initServiceBusinessLogic: 业务组件初始化完成"); + } + + // 启动服务(静态入口)- 优化:强化包名绑定,避免隐式启动失败 + public static void startControlCenterService(Context context) { + LogUtils.d(TAG, "startControlCenterService: 启动服务入口"); + if (context == null) { + LogUtils.e(TAG, "startControlCenterService: Context为空,启动失败"); + return; + } + if (isServiceRunning(context, ControlCenterService.class)) { + LogUtils.d(TAG, "startControlCenterService: 服务已运行,跳过启动"); + 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); + } + } + + // 停止服务(静态入口)- 优化:强化包名绑定 + public static void stopControlCenterService(Context context) { + LogUtils.d(TAG, "stopControlCenterService: 停止服务入口"); + if (context == null) { + 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: 服务停止指令发送成功"); + } + + // 判断服务是否运行 - 优化:适配API30+ ActivityManager限制 + private static boolean isServiceRunning(Context context, Class serviceClass) { + if (context == null || serviceClass == null) return false; + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (am == null) return false; + + // API30+ 限制:getRunningServices返回空,改用getRunningTasks兼容(或用JobScheduler,此处先兼容) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + // 替代方案1:通过进程名判断(简单兼容) + String packageName = context.getPackageName(); + List processes = am.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo process : processes) { + if (process.processName.equals(packageName) && process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { + return true; + } + } + // 替代方案2:兜底判断(若进程在运行,且服务已启动,视为运行中) + return true; + } catch (Exception e) { + LogUtils.e(TAG, "isServiceRunning: API30+ 判断服务状态异常", e); + return false; + } + } else { + List runningServices = am.getRunningServices(100); + for (ActivityManager.RunningServiceInfo info : runningServices) { + if (serviceClass.getName().equals(info.service.getClassName())) { + return true; + } + } + return false; + } + } + + // 引导开启忽略电池优化 - 优化:加权限判断+异常捕获 + public static void checkIgnoreBatteryOptimization(Context context) { + if (context == null) return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + if (powerManager == null) return; + + try { + boolean isIgnored = powerManager.isIgnoringBatteryOptimizations(context.getPackageName()); + if (!isIgnored) { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + context.getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + context.startActivity(intent); + LogUtils.d(TAG, "checkIgnoreBatteryOptimization: 引导开启忽略电池优化"); + } + } catch (Exception e) { + LogUtils.e(TAG, "checkIgnoreBatteryOptimization: 引导优化异常(部分机型限制)", e); + } + } + } + + // ====================== 核心 Getter/Setter 方法 ====================== + public RemindThread getRemindThread() { + return mRemindThread; + } + + public void setCurrentConfigBean(AppConfigBean configBean) { + if (configBean != null) { + synchronized (mServiceLock) { + this.mCurrentConfigBean = configBean; + syncConfigToRemindThread(); // 更新配置后自动同步线程 + LogUtils.d(TAG, "setCurrentConfigBean: 服务配置更新成功"); + } + } + } + + public NotificationManagerUtils getNotificationManager() { + return mNotificationManager; + } + + public NotificationMessage getForegroundNotifyMsg() { + return mForegroundNotifyMsg; + } + + public AppConfigBean getCurrentConfigBean() { + return mCurrentConfigBean; + } + + public boolean isDestroyed() { + return mIsDestroyed; + } + + // ====================== 服务销毁逻辑 - 优化:顺序化释放,避免资源泄漏 ====================== + @Override + public void onDestroy() { + super.onDestroy(); + LogUtils.d(TAG, "onDestroy: 服务销毁,释放所有资源"); + synchronized (mServiceLock) { // 加锁,避免与重启线程操作冲突 + isServiceRunning = false; + mIsDestroyed = true; + + // 1. 停止前台服务(顺序:先取消通知 → 再移除前台状态) + if (mNotificationManager != null) { + mNotificationManager.cancelForegroundServiceNotify(); // 取消前台通知 + } + try { + stopForeground(STOP_FOREGROUND_REMOVE); // 移除前台服务状态(API26+) + LogUtils.d(TAG, "onDestroy: 前台服务状态已移除"); + } catch (Exception e) { + LogUtils.e(TAG, "onDestroy: 移除前台状态异常", e); + } + + // 2. 安全停止提醒线程(彻底终止,无残留) + stopRemindThreadSafely(); + + // 3. 销毁Handler(移除所有任务,避免内存泄漏) + if (mServiceHandler != null) { + mServiceHandler.removeCallbacksAndMessages(null); + mServiceHandler = null; + LogUtils.d(TAG, "onDestroy: Handler已销毁"); + } + + // 4. 释放通知资源(调用工具类释放方法) + if (mNotificationManager != null) { + mNotificationManager.release(); + mNotificationManager = null; + LogUtils.d(TAG, "onDestroy: 通知资源已释放"); + } + + // 5. 置空所有引用,帮助GC回收 + mForegroundNotifyMsg = null; + mCurrentConfigBean = null; + } + + LogUtils.d(TAG, "onDestroy: 服务销毁完成"); } @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; - } - // 停止前台通知栏 - stopForeground(true); - // 停止消息提醒进程 - stopRemindThread(); - super.onDestroy(); - //LogUtils.d(TAG, "onDestroy done"); - } + public IBinder onBind(Intent intent) { + return null; // 无需绑定服务 } - // 主进程与守护进程连接时需要用到此类 - // - 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(); - } - } - } - - public void appenRemindMSG(String szRemindMSG) { - String msg = ""; - for (int i = 0; i < 20; i++) { - msg += szRemindMSG; - } - 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; - } - - // 设置Drawable背景 - public static RemoteViews setLinearLayoutDrawable(RemoteViews remoteViews, int viewId, int drawableRes) { - remoteViews.setInt(viewId, "setBackgroundResource", drawableRes); - return remoteViews; - } - - // - // 启动服务 - // - public static void startControlCenterService(Context context) { + // 更新服务配置(对外入口)- 优化:强化兼容性,加异常捕获 + public static void updateStatus(Context context, AppConfigBean configBean) { + if (context == null || configBean == null) return; + LogUtils.d(TAG, "updateStatus: 发送配置更新指令"); Intent intent = new Intent(context, ControlCenterService.class); - context.startForegroundService(intent); - } + intent.setAction(ACTION_RESTART_REMIND_THREAD); + intent.putExtra(EXTRA_APP_CONFIG_BEAN, (Serializable) configBean); + intent.setPackage(context.getPackageName()); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - // - // 停止服务 - // - public static void stopControlCenterService(Context context) { - Intent intent = new Intent(context, ControlCenterService.class); - context.stopService(intent); + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); + } else { + context.startService(intent); + } + LogUtils.d(TAG, "updateStatus: 配置更新指令发送成功"); + } catch (Exception e) { + LogUtils.e(TAG, "updateStatus: 发送指令异常", e); + } } - - 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); - } - } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java index 59d74e6..4433620 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/threads/RemindThread.java @@ -4,194 +4,607 @@ 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; +/** + * 提醒线程(单例模式):统一管理充电/耗电提醒逻辑,适配Java7+API30+ + */ public class RemindThread extends Thread { - - public static final String TAG = RemindThread.class.getSimpleName(); + // 常量定义(优化命名规范,补充注释) + public static final String TAG = "RemindThread"; + private static final long INIT_DELAY_TIME = 500L; // 初始化延迟(ms,等待服务就绪) + private static final int MIN_SLEEP_TIME = 500; // 最小检测间隔(ms,避免频繁轮询) + private static final int DEFAULT_SLEEP_TIME = 1000; // 默认检测间隔(ms) + private static final long REMIND_INTERVAL = 30000L; // 重复提醒间隔(ms,避免轰炸用户) + private static final int CONFIG_RETRY_MAX = 3; // 配置同步重试次数(兜底配置有效性) - 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 mwrControlCenterServiceHandler; + // 单例核心(volatile+双重校验锁,线程安全,修复复用漏洞) + private static volatile RemindThread sInstance; - public void setIsExist(boolean isExist) { - this.isExist = isExist; + // 成员变量(volatile+锁保护,确保多线程可见性,优化内存泄漏防护) + private Context mContext; + private WeakReference mwrControlCenterServiceHandler; + private volatile boolean isExist; // 线程退出标记(true=触发退出) + private volatile boolean isReminding; // 全局提醒功能开关 + private volatile boolean isThreadStarted; // 线程启动状态标记(区分isAlive()) + private volatile boolean isEnableUsageReminder; // 耗电提醒单独开关 + private volatile boolean isEnableChargeReminder; // 充电提醒单独开关 + private volatile int sleepTime; // 电量检测间隔(ms) + private volatile int chargeReminderValue; // 充电提醒阈值(0-100) + private volatile int usageReminderValue; // 耗电提醒阈值(0-100) + private volatile int quantityOfElectricity; // 当前电量(0-100) + private volatile boolean isCharging; // 是否处于充电状态 + private volatile boolean isRemindTimerRunning; // 提醒恢复计时器运行标记 + private volatile long lastRemindTime; // 上次提醒时间戳(ms) + private final Object mLock = new Object(); // 全局锁(保护所有状态变量,解决并发安全) + + // 私有构造器(单例模式,强化上下文弱引用+状态重置) + private RemindThread(Context context, ControlCenterServiceHandler handler) { + LogUtils.d(TAG, "RemindThread: 构造方法初始化,线程名称=" + getName()); + // 应用上下文+弱引用Handler,双重防护内存泄漏 + this.mContext = context.getApplicationContext(); + this.mwrControlCenterServiceHandler = new WeakReference<>(handler); + resetThreadStateInternal(); // 初始化所有状态变量,避免脏数据 } - public boolean isExist() { - 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 static RemindThread getInstance(Context context, ControlCenterServiceHandler handler) { + LogUtils.d(TAG, "getInstance: 获取线程实例,当前单例状态=" + (sInstance != null)); + // 入参校验(非空抛出异常,强制上游传参正确) + if (context == null || handler == null) { + LogUtils.e(TAG, "getInstance: 致命错误 - Context/Handler不能为空"); + throw new IllegalArgumentException("Context and ControlCenterServiceHandler cannot be null"); } - } - public RemindThread(Context context, ControlCenterServiceHandler handler) { - mContext = context; - mwrControlCenterServiceHandler = new WeakReference(handler); - } + // 关键修复:旧线程已停止(未运行),直接销毁单例,确保新实例创建 + if (sInstance != null && !sInstance.isRunning()) { + destroyInstance(); + LogUtils.d(TAG, "getInstance: 旧线程已停止,销毁单例准备创建新实例"); + } - @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()); + // 双重校验锁,线程安全创建单例 + if (sInstance == null) { + synchronized (RemindThread.class) { + if (sInstance == null) { + sInstance = new RemindThread(context, handler); + LogUtils.d(TAG, "getInstance: 新线程实例创建成功,线程ID=" + sInstance.getId()); } } - //ToastUtils.show("Service Is Stop."); - //LogUtils.i(TAG, "Service Is Stop."); - isReminding = false; } - + return sInstance; } + // 单例销毁(核心优化:彻底释放资源+终止线程,与Service停止逻辑联动) + public static void destroyInstance() { + LogUtils.d(TAG, "destroyInstance: 开始销毁线程单例"); + synchronized (RemindThread.class) { // 类锁保护,避免并发销毁冲突 + if (sInstance != null) { + // 1. 安全停止线程(触发退出+中断休眠) + sInstance.stopThread(); + // 2. 强制释放所有资源(避免内存泄漏) + sInstance.releaseResources(); + // 3. 置空单例,杜绝后续复用 + sInstance = null; + LogUtils.d(TAG, "destroyInstance: 线程单例销毁完成"); + } else { + LogUtils.w(TAG, "destroyInstance: 单例已为空,跳过销毁"); + } + } + } + + // 线程核心逻辑(彻底修复重复启动、优化退出效率、强化异常容错) + @Override + public void run() { + LogUtils.d(TAG, "run: 线程启动执行,线程ID=" + Thread.currentThread().getId() + ",当前状态=" + getState()); + // 核心修复1:杜绝重复执行run()(Thread.start()后会自动调用run(),手动调用/重复start()直接拦截) + synchronized (mLock) { + if (isThreadStarted || isExist) { + LogUtils.e(TAG, "run: 线程已启动/待退出,禁止重复执行run()"); + return; + } + isThreadStarted = true; // 标记线程启动成功(仅执行一次) + lastRemindTime = 0; // 初始化上次提醒时间 + } + + // 步骤1:配置同步重试(等待Service同步配置,避免阈值无效) + boolean configValid = syncConfigRetry(); + if (!configValid) { + LogUtils.e(TAG, "run: 配置同步失败,使用默认配置兜底启动"); + } + + // 步骤2:初始化延迟(等待服务/系统资源就绪,避免启动初期异常) + try { + LogUtils.d(TAG, "run: 初始化延迟" + INIT_DELAY_TIME + "ms"); + Thread.sleep(INIT_DELAY_TIME); + // 延迟期间检测退出标记,避免线程启动后立即销毁 + if (isExist) { + LogUtils.d(TAG, "run: 初始化延迟期间收到退出指令,线程终止"); + cleanThreadState(); + return; + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // 保留中断标记 + LogUtils.e(TAG, "run: 初始化延迟被中断,线程终止"); + cleanThreadState(); + return; + } + + // 步骤3:核心业务循环(isExist=true时立即退出,优化轮询效率) + LogUtils.d(TAG, "run: 进入电量检测核心循环,检测间隔=" + sleepTime + "ms"); + while (!isExist) { + try { + // 快速退出判断(循环头部+尾部双重校验,提升退出效率) + if (isExist) break; + + // 状态1:提醒功能关闭 → 仅休眠,不执行检测逻辑 + if (!isReminding) { + safeSleep(sleepTime); + continue; + } + + // 状态2:电量数据无效 → 休眠重试(避免无效计算) + if (quantityOfElectricity < 0 || quantityOfElectricity > 100) { + LogUtils.w(TAG, "run: 电量数据无效(" + quantityOfElectricity + "),休眠重试"); + safeSleep(sleepTime); + continue; + } + + // 状态3:正常检测 → 锁保护核心逻辑,解决并发安全(计时器+提醒判断) + synchronized (mLock) { + // 快速退出(锁内再次校验,避免持有锁期间收到退出指令) + if (isExist) break; + + // 防重复提醒:间隔未到/计时器运行中 → 跳过 + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastRemindTime < REMIND_INTERVAL) || isRemindTimerRunning) { + safeSleep(sleepTime); + continue; + } + + // 充电状态提醒(开关+阈值双重校验) + if (isCharging && isEnableChargeReminder) { + if (quantityOfElectricity >= chargeReminderValue) { + sendNotificationMessage("+"); // 充电提醒标识 + lastRemindTime = currentTime; + LogUtils.d(TAG, "run: 触发充电提醒 → 电量:" + quantityOfElectricity + ",阈值:" + chargeReminderValue); + startRemindRecoveryTimer(); // 启动恢复计时器,避免重复提醒 + } + } + // 耗电状态提醒(同理,开关+阈值双重校验) + else if (!isCharging && isEnableUsageReminder) { + if (quantityOfElectricity <= usageReminderValue) { + sendNotificationMessage("-"); // 耗电提醒标识 + lastRemindTime = currentTime; + LogUtils.d(TAG, "run: 触发耗电提醒 → 电量:" + quantityOfElectricity + ",阈值:" + usageReminderValue); + startRemindRecoveryTimer(); // 启动恢复计时器 + } + } + } + + // 检测间隔休眠(循环尾部休眠,避免空轮询) + safeSleep(sleepTime); + } catch (Exception e) { + LogUtils.e(TAG, "run: 线程运行异常,休眠后重试", e); + safeSleep(sleepTime); // 异常后强制休眠,避免死循环占用CPU + } + } + + // 步骤4:线程退出清理(无论正常/异常退出,必执行) + cleanThreadState(); + LogUtils.d(TAG, "run: 线程核心循环退出,线程ID=" + Thread.currentThread().getId() + ",最终状态=" + getState()); + } + + // 新增:配置同步重试(独立封装,提升代码可读性,修复阈值校验逻辑) + private boolean syncConfigRetry() { + int retryCount = 0; + while (retryCount < CONFIG_RETRY_MAX) { + synchronized (mLock) { + // 配置有效(阈值0-100)→ 重试成功 + if ((chargeReminderValue >= 0 && chargeReminderValue <= 100) + && (usageReminderValue >= 0 && usageReminderValue <= 100)) { + LogUtils.d(TAG, "syncConfigRetry: 配置同步成功,重试次数=" + retryCount); + return true; + } + } + // 配置无效 → 休眠1秒重试 + LogUtils.w(TAG, "syncConfigRetry: 配置未同步(阈值无效),重试次数=" + (retryCount + 1)); + try { + Thread.sleep(1000); + retryCount++; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LogUtils.e(TAG, "syncConfigRetry: 配置重试被中断", e); + return false; + } + } + // 重试失败 → 用默认阈值兜底(确保线程能正常启动) + synchronized (mLock) { + chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100); + if (chargeReminderValue < 0 || chargeReminderValue > 100) { + chargeReminderValue = 80; + } + usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100); + if (usageReminderValue < 0 || usageReminderValue > 100) { + usageReminderValue = 20; + } + LogUtils.e(TAG, "syncConfigRetry: 配置重试失败,使用默认阈值 → 充电:" + chargeReminderValue + ",耗电:" + usageReminderValue); + } + return false; + } + + // 发送提醒消息(核心修复:Message回收写法+优化容错,适配全Android版本) + private void sendNotificationMessage(String content) { + LogUtils.d(TAG, "sendNotificationMessage: 准备发送提醒消息,内容=" + content); + // 前置校验:线程退出/提醒关闭 → 跳过 + if (isExist || !isReminding) { + LogUtils.d(TAG, "sendNotificationMessage: 线程退出/提醒关闭,跳过发送"); + return; + } + + // 弱引用获取Handler(避免持有Service引用导致内存泄漏) + ControlCenterServiceHandler handler = null; + if (mwrControlCenterServiceHandler != null) { + handler = mwrControlCenterServiceHandler.get(); + } + if (handler == null) { + LogUtils.w(TAG, "sendNotificationMessage: Handler已回收,发送失败"); + return; + } + + // 复用Message(减少内存分配),发送提醒指令 + Message message = Message.obtain(handler, ControlCenterServiceHandler.MSG_REMIND_TEXT, content); + try { + handler.sendMessage(message); + LogUtils.d(TAG, "sendNotificationMessage: 提醒消息发送成功"); + } catch (Exception e) { + LogUtils.e(TAG, "sendNotificationMessage: 消息发送异常", e); + // 核心修复:Message回收改为实例方法(兼容所有Android版本),且仅在发送失败时回收 + if (message != null) { + message.recycle(); // 正确写法:实例方法回收,避免内存泄漏 + } + } + } + + // 提醒恢复计时器(核心优化:弱引用线程实例+双重锁保护,彻底解决并发安全) + private void startRemindRecoveryTimer() { + LogUtils.d(TAG, "startRemindRecoveryTimer: 启动提醒恢复计时器,间隔=" + REMIND_INTERVAL + "ms"); + synchronized (mLock) { + // 前置校验:线程退出/计时器已运行/单例已销毁 → 跳过 + if (isExist || isRemindTimerRunning || sInstance != this) { + LogUtils.w(TAG, "startRemindRecoveryTimer: 计时器启动条件不满足,跳过"); + return; + } + // 临时关闭提醒(避免恢复前重复触发),标记计时器运行中 + isReminding = false; + isRemindTimerRunning = true; + } + + // 计时器线程:弱引用当前RemindThread实例(避免计时器持有线程导致内存泄漏) + final WeakReference threadWeakRef = new WeakReference<>(this); + new Thread(new Runnable() { + @Override + public void run() { + try { + // 计时器休眠(间隔时间到后恢复提醒) + Thread.sleep(REMIND_INTERVAL); + + // 弱引用获取线程实例(避免内存泄漏) + RemindThread thread = threadWeakRef.get(); + if (thread == null || thread.isExist || sInstance != thread) { + LogUtils.d(TAG, "startRemindRecoveryTimer: 线程已销毁/退出,不恢复提醒"); + return; + } + + // 锁保护恢复逻辑(确保状态一致性) + synchronized (thread.mLock) { + // 再次校验:线程未退出+单例未切换+有任一提醒开关开启 → 恢复提醒 + if (!thread.isExist && sInstance == thread + && (thread.isEnableChargeReminder || thread.isEnableUsageReminder)) { + thread.isReminding = true; + LogUtils.d(TAG, "startRemindRecoveryTimer: 提醒功能恢复开启"); + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LogUtils.e(TAG, "startRemindRecoveryTimer: 计时器被中断", e); + } finally { + // 无论是否异常,都重置计时器标记(锁保护,避免并发冲突) + RemindThread thread = threadWeakRef.get(); + if (thread != null) { + synchronized (thread.mLock) { + if (sInstance == thread) { + thread.isRemindTimerRunning = false; + } + } + } + LogUtils.d(TAG, "startRemindRecoveryTimer: 提醒恢复计时器执行完成"); + } + } + }, "RemindRecoveryTimer").start(); // 给计时器线程命名,便于调试 + } + + // 安全停止线程(核心修复:触发退出+中断休眠+重置状态,确保快速退出) + public void stopThread() { + LogUtils.d(TAG, "stopThread: 开始安全停止线程,当前线程状态=" + getState()); + synchronized (mLock) { + // 标记退出+关闭所有开关+停止计时器(原子操作,避免状态混乱) + isExist = true; + isReminding = false; + isRemindTimerRunning = false; + LogUtils.d(TAG, "stopThread: 线程退出标记已置位,所有开关已关闭"); + } + + // 中断线程(唤醒sleep/wait状态,加速线程退出核心循环,避免阻塞) + if (isAlive()) { + interrupt(); + LogUtils.d(TAG, "stopThread: 线程已中断,加速退出"); + } + + // 兜底:单例置空(避免Service销毁后单例残留) + if (sInstance == this) { + sInstance = null; + } + } + + // 废弃原强制停止方法(统一用stopThread(),避免方法冗余导致调用混淆) + @Deprecated + public void forceStopThread() { + LogUtils.w(TAG, "forceStopThread: 该方法已废弃,请调用stopThread()"); + stopThread(); + } + + // 安全休眠(修复:保留中断标记,避免线程退出逻辑失效) + private void safeSleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // 关键:保留中断标记,确保线程能快速退出 + LogUtils.w(TAG, "safeSleep: 休眠被中断,线程准备退出"); + } + } + + // 清理线程运行状态(原子操作,确保退出后状态干净,无残留) + private void cleanThreadState() { + LogUtils.d(TAG, "cleanThreadState: 清理线程运行状态"); + synchronized (mLock) { + isReminding = false; + isExist = true; + isThreadStarted = false; + isRemindTimerRunning = false; + lastRemindTime = 0; + } + // 辅助:中断线程(兜底加速退出) + if (isAlive()) { + interrupt(); + } + // 辅助:单例置空(终极兜底) + if (sInstance == this) { + sInstance = null; + } + } + + // 重置线程初始状态(构造方法专用,初始化所有变量,避免脏数据) + private void resetThreadStateInternal() { + LogUtils.d(TAG, "resetThreadStateInternal: 重置线程初始状态"); + synchronized (mLock) { + isExist = false; + isReminding = false; + isThreadStarted = false; + isEnableUsageReminder = false; + isEnableChargeReminder = false; + sleepTime = DEFAULT_SLEEP_TIME; + chargeReminderValue = -1; + usageReminderValue = -1; + quantityOfElectricity = -1; + isCharging = false; + isRemindTimerRunning = false; + lastRemindTime = 0; + } + } + + // 外部调用:重置线程运行状态(不重置配置,仅重置运行时标记) + public void resetThreadState() { + LogUtils.d(TAG, "resetThreadState: 重置线程运行状态"); + synchronized (mLock) { + isExist = false; + isReminding = (isEnableChargeReminder || isEnableUsageReminder); // 按开关自动恢复提醒状态 + isRemindTimerRunning = false; + lastRemindTime = 0; + } + } + + // 核心:同步完整配置(优化参数校验+原子操作,确保配置一致性) + public void setAppConfigBean(AppConfigBean config) { + if (config == null) { + LogUtils.e(TAG, "setAppConfigBean: 配置Bean为空,同步失败"); + return; + } + LogUtils.d(TAG, "setAppConfigBean: 开始同步配置 → 充电阈值:" + config.getChargeReminderValue() + ",耗电阈值:" + config.getUsageReminderValue()); + synchronized (mLock) { + // 1. 同步提醒开关(单独开关+全局开关联动) + this.isEnableChargeReminder = config.isEnableChargeReminder(); + this.isEnableUsageReminder = config.isEnableUsageReminder(); + this.isReminding = (this.isEnableChargeReminder || this.isEnableUsageReminder); + + // 2. 同步阈值(强制0-100范围,避免无效值) + this.chargeReminderValue = Math.min(Math.max(config.getChargeReminderValue(), 0), 100); + this.usageReminderValue = Math.min(Math.max(config.getUsageReminderValue(), 0), 100); + + // 3. 同步检测间隔(强制≥最小间隔,避免频繁轮询耗电) + this.sleepTime = Math.max(config.getBatteryDetectInterval(), MIN_SLEEP_TIME); + + // 4. 同步实时电池状态(配置中携带最新状态,立即生效) + this.quantityOfElectricity = Math.min(Math.max(config.getCurrentBatteryValue(), 0), 100); + this.isCharging = config.isCharging(); + } + LogUtils.d(TAG, "setAppConfigBean: 配置同步完成 → 检测间隔:" + sleepTime + "ms,提醒开启:" + isReminding); + } + + // ====================== Setter/Getter 方法(全量锁保护,确保线程安全,优化参数校验) ====================== + public boolean isExist() { + synchronized (mLock) { + return isExist; + } + } + + public void setIsExist(boolean isExist) { + synchronized (mLock) { + this.isExist = isExist; + LogUtils.d(TAG, "setIsExist: 线程退出标记设置为" + isExist); + } + } + + public boolean isReminding() { + synchronized (mLock) { + return isReminding; + } + } + + public void setIsReminding(boolean isReminding) { + synchronized (mLock) { + this.isReminding = isReminding; + LogUtils.d(TAG, "setIsReminding: 全局提醒开关设置为" + isReminding); + } + } + + public boolean isThreadStarted() { + synchronized (mLock) { + return isThreadStarted; + } + } + + public boolean isEnableUsageReminder() { + synchronized (mLock) { + return isEnableUsageReminder; + } + } + + public void setIsEnableUsageReminder(boolean isEnableUsageReminder) { + synchronized (mLock) { + this.isEnableUsageReminder = isEnableUsageReminder; + this.isReminding = (this.isEnableChargeReminder || this.isEnableUsageReminder); // 联动全局开关 + LogUtils.d(TAG, "setIsEnableUsageReminder: 耗电提醒开关设置为" + isEnableUsageReminder + ",全局提醒:" + this.isReminding); + } + } + + public boolean isEnableChargeReminder() { + synchronized (mLock) { + return isEnableChargeReminder; + } + } + + public void setIsEnableChargeReminder(boolean isEnableChargeReminder) { + synchronized (mLock) { + this.isEnableChargeReminder = isEnableChargeReminder; + this.isReminding = (this.isEnableChargeReminder || this.isEnableUsageReminder); // 联动全局开关 + LogUtils.d(TAG, "setIsEnableChargeReminder: 充电提醒开关设置为" + isEnableChargeReminder + ",全局提醒:" + this.isReminding); + } + } + + public int getSleepTime() { + synchronized (mLock) { + return sleepTime; + } + } + + public void setSleepTime(int sleepTime) { + synchronized (mLock) { + this.sleepTime = Math.max(sleepTime, MIN_SLEEP_TIME); // 强制≥最小间隔 + LogUtils.d(TAG, "setSleepTime: 检测间隔设置为" + this.sleepTime + "ms"); + } + } + + public int getChargeReminderValue() { + synchronized (mLock) { + return chargeReminderValue; + } + } + + public void setChargeReminderValue(int chargeReminderValue) { + synchronized (mLock) { + this.chargeReminderValue = Math.min(Math.max(chargeReminderValue, 0), 100); // 强制0-100 + LogUtils.d(TAG, "setChargeReminderValue: 充电提醒阈值设置为" + this.chargeReminderValue); + } + } + + public int getUsageReminderValue() { + synchronized (mLock) { + return usageReminderValue; + } + } + + public void setUsageReminderValue(int usageReminderValue) { + synchronized (mLock) { + this.usageReminderValue = Math.min(Math.max(usageReminderValue, 0), 100); // 强制0-100 + LogUtils.d(TAG, "setUsageReminderValue: 耗电提醒阈值设置为" + this.usageReminderValue); + } + } + + public int getQuantityOfElectricity() { + synchronized (mLock) { + return quantityOfElectricity; + } + } + + public void setQuantityOfElectricity(int quantityOfElectricity) { + synchronized (mLock) { + this.quantityOfElectricity = Math.min(Math.max(quantityOfElectricity, 0), 100); // 强制0-100 + LogUtils.d(TAG, "setQuantityOfElectricity: 当前电量更新为" + this.quantityOfElectricity); + } + } + + public boolean isCharging() { + synchronized (mLock) { + return isCharging; + } + } + + public void setIsCharging(boolean isCharging) { + synchronized (mLock) { + this.isCharging = isCharging; + LogUtils.d(TAG, "setIsCharging: 充电状态更新为" + isCharging); + } + } + + // 核心:判断线程是否正在运行(Service依赖此方法,精准判断运行状态) + public boolean isRunning() { + synchronized (mLock) { + // 条件:未退出 + 已启动 + 线程存活 → 视为正在运行 + return !isExist && isThreadStarted && isAlive(); + } + } + + // 彻底释放线程资源(Service销毁时调用,避免内存泄漏) + public void releaseResources() { + LogUtils.d(TAG, "releaseResources: 开始释放线程资源"); + synchronized (mLock) { + // 1. 释放上下文(避免持有应用上下文导致内存泄漏) + mContext = null; + // 2. 清空Handler弱引用(加速回收) + if (mwrControlCenterServiceHandler != null) { + mwrControlCenterServiceHandler.clear(); + mwrControlCenterServiceHandler = null; + } + // 3. 清理运行状态(确保所有标记干净) + cleanThreadState(); + } + LogUtils.d(TAG, "releaseResources: 线程资源释放完成"); + } + + // 重写toString(),便于调试(新增:打印线程核心状态) + @Override + public String toString() { + return "RemindThread{" + + "threadId=" + getId() + + ", threadName='" + getName() + '\'' + + ", isRunning=" + isRunning() + + ", isReminding=" + isReminding() + + ", chargeThreshold=" + getChargeReminderValue() + + ", usageThreshold=" + getUsageReminderValue() + + ", currentBattery=" + getQuantityOfElectricity() + + ", isCharging=" + isCharging() + + '}'; + } } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AppConfigUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AppConfigUtils.java index 6d25e5e..44392e6 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AppConfigUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/AppConfigUtils.java @@ -2,202 +2,455 @@ package cc.winboll.studio.powerbell.utils; import android.app.Activity; import android.content.Context; +import android.content.Intent; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.MainActivity; +import cc.winboll.studio.powerbell.dialogs.YesNoAlertDialog; 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; -// 应用配置工具类 -// +/** + * 应用配置工具类:管理应用核心配置(服务开关、电池提醒阈值、背景设置等) + * 适配:Java7 | API29-30 | 小米手机,单例模式,线程安全,配置持久化 + */ 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 String CONFIRM_TITLE = "配置变更确认"; + private static final String CONFIRM_MSG = "是否确认更改该配置?"; - public static final String BACKGROUND_DIR = "Background"; + // ======================== 静态成员(单例实例,严格控制初始化)======================== + private static AppConfigUtils sInstance; // 单例实例(私有,禁止外部直接创建) - // 保存唯一配置实例 - static AppConfigUtils _mAppConfigUtils; - // 应用环境上下文 - Context mContext; - - // 是否启动铃声提醒服务 - volatile boolean mIsEnableService = false; + // ======================== 成员属性(按功能分类,volatile保障线程安全)======================== + // 核心依赖(优先排列,final避免空指针) + private final Context mContext; + private App mApplication; + // 配置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; + private volatile ControlCenterServiceBean mServiceConfigBean; - volatile boolean mIsUseBackgroundFile = false; - volatile String mszBackgroundFileName = ""; + // 缓存状态(减少Bean读取次数,提升性能) + private volatile boolean mIsServiceEnabled = false; - // 保存应用实例 - App mApplication; - - AppConfigUtils(Context context) { - mContext = context; - String szExternalFilesDir = mContext.getExternalFilesDir(TAG) + File.separator; - //mlistAppConfigBean = new ArrayList(); + // ======================== 单例构造方法(私有,禁止外部实例化)======================== + private AppConfigUtils(Context context) { + LogUtils.d(TAG, "初始化配置工具类"); + this.mContext = context.getApplicationContext(); // 取应用上下文,避免Activity内存泄漏 + // 初始化Application实例(补全:之前未赋值) + this.mApplication = (App) context.getApplicationContext(); + // 初始化配置Bean mAppConfigBean = new AppConfigBean(); - loadAppConfigBean(); + mServiceConfigBean = new ControlCenterServiceBean(false); + // 加载持久化配置 + loadAllConfig(); + LogUtils.d(TAG, "配置工具类初始化完成"); } - // 返回唯一实例 - // + // ======================== 单例获取方法(双重校验锁,线程安全)======================== public static AppConfigUtils getInstance(Context context) { - if (_mAppConfigUtils == null) { - _mAppConfigUtils = new AppConfigUtils(context); + if (context == null) { + LogUtils.e(TAG, "获取实例失败: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); } - - @Override - public void onNo() { - MainActivity.relaodAppConfigs(); - } - }); - } - - public boolean getIsEnableService() { - ControlCenterServiceBean bean = ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class); - if (bean == null) { - ControlCenterServiceBean.saveBean(mContext, new ControlCenterServiceBean(false)); - return false; + } } - return bean.isEnableService(); + return sInstance; } - public void setEnableChargeReminder(boolean isEnableChargeReminder) { - mAppConfigBean.setEnableChargeReminder(isEnableChargeReminder); - saveConfigData(MainActivity._mMainActivity); - } - - public boolean isEnableChargeReminder() { - return mAppConfigBean.isEnableChargeReminder(); - } - - public void setEnableUsageReminder(boolean isEnableUsegeReminder) { - mAppConfigBean.setEnableUsageReminder(isEnableUsegeReminder); - saveConfigData(MainActivity._mMainActivity); - } - - public boolean isEnableUsageReminder() { - return mAppConfigBean.isEnableUsageReminder(); - } - - public void setChargeReminderValue(int value) { - mAppConfigBean.setChargeReminderValue(value); - saveConfigData(MainActivity._mMainActivity); - } - - public int getChargeReminderValue() { - return mAppConfigBean.getChargeReminderValue(); - } - - public void setUsageReminderValue(int value) { - mAppConfigBean.setUsageReminderValue(value); - saveConfigData(MainActivity._mMainActivity); - } - - public int getUsageReminderValue() { - return mAppConfigBean.getUsageReminderValue(); - } - - public void setCharging(boolean isCharging) { - mAppConfigBean.setCharging(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(); + // ======================== 核心配置读写方法(优先排列,保障配置稳定性)======================== + /** + * 加载所有配置(应用配置+服务配置,统一入口) + */ + private void loadAllConfig() { + LogUtils.d(TAG, "开始加载所有配置"); + // 加载应用配置(补全:BaseBean 通用持久化逻辑,适配序列化存储) + AppConfigBean savedAppBean = (AppConfigBean) AppConfigBean.loadBean(mContext, AppConfigBean.class); + if (savedAppBean != null) { + mAppConfigBean = savedAppBean; + LogUtils.d(TAG, "应用配置加载成功"); + } else { AppConfigBean.saveBean(mContext, mAppConfigBean); + LogUtils.d(TAG, "无已保存应用配置,使用默认值并持久化"); + } + + // 加载服务配置(补全:BaseBean 通用持久化逻辑) + ControlCenterServiceBean savedServiceBean = (ControlCenterServiceBean) ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class); + if (savedServiceBean != null) { + mServiceConfigBean = savedServiceBean; + mIsServiceEnabled = savedServiceBean.isEnableService(); + LogUtils.d(TAG, "服务配置加载成功,服务状态:" + (mIsServiceEnabled ? "开启" : "关闭")); + } else { + ControlCenterServiceBean.saveBean(mContext, mServiceConfigBean); + mIsServiceEnabled = false; + LogUtils.d(TAG, "无已保存服务配置,使用默认值并持久化"); } - mAppConfigBean.setEnableUsageReminder(bean.isEnableUsageReminder()); - mAppConfigBean.setUsageReminderValue(bean.getUsageReminderValue()); - mAppConfigBean.setEnableChargeReminder(bean.isEnableChargeReminder()); - mAppConfigBean.setChargeReminderValue(bean.getChargeReminderValue()); } - - public void saveConfigData(final MainActivity activity) { - if (MainActivity._mMainActivity == null) { + + /** + * 保存应用配置(内部核心方法,无弹窗,直接持久化) + */ + private void saveAppConfig() { + // 补全:BaseBean 通用保存逻辑,传入Bean类名作为存储Key + AppConfigBean.saveBean(mContext, mAppConfigBean); + // 通知服务和Activity更新配置(补全:ControlCenterService 新增 updateStatus 静态方法) + ControlCenterService.updateStatus(mContext, mAppConfigBean); + MainActivity.reloadAppConfig(); + LogUtils.d(TAG, "应用配置保存成功,已通知服务和Activity更新"); + } + + /** + * 保存服务配置(内部核心方法,无弹窗,直接持久化) + */ + private void saveServiceConfig() { + mServiceConfigBean.setIsEnableService(mIsServiceEnabled); + // 补全:BaseBean 通用保存逻辑 + ControlCenterServiceBean.saveBean(mContext, mServiceConfigBean); + LogUtils.d(TAG, "服务配置保存成功,服务状态:" + (mIsServiceEnabled ? "开启" : "关闭")); + } + + // ======================== 通用确认弹窗方法(抽取复用,减少冗余)======================== + /** + * 配置变更确认弹窗(所有配置修改统一调用) + */ + private void showConfigConfirmDialog(Activity activity, final Runnable confirmAction) { + if (activity == null || activity.isFinishing()) { + // 兼容Java7:Activity.isDestroyed() 是API17+方法,直接判断isFinishing()即可,避免API兼容问题 + LogUtils.e(TAG, "弹窗显示失败:Activity无效"); + return; + } + YesNoAlertDialog.show(activity, CONFIRM_TITLE, CONFIRM_MSG, new YesNoAlertDialog.OnDialogResultListener() { + @Override + public void onYes() { + LogUtils.d(TAG, "用户确认更改配置"); + confirmAction.run(); // 执行确认后的配置变更逻辑 + } + + @Override + public void onNo() { + LogUtils.d(TAG, "用户取消更改配置"); + // 取消后刷新配置显示,保持界面与实际配置一致 + MainActivity.reloadAppConfig(); + } + }); + } + + // ======================== 服务开关配置方法(单独归类,逻辑聚焦)======================== + /** + * 设置服务开关状态(带弹窗确认,适配小米手机交互规范) + */ + public void setServiceEnabled(Activity activity, final boolean isEnabled) { + // 状态无变化,直接返回 + if (isEnabled == mIsServiceEnabled) { + LogUtils.d(TAG, "服务状态无变化,无需操作"); return; } - YesNoAlertDialog.show(activity, "应用设置信息", "是否保存应用配置?", new YesNoAlertDialog.OnDialogResultListener(){ - - @Override - public void onYes() { - saveConfigData(); - } - - @Override - public void onNo() { - AppConfigUtils.getInstance(activity).loadAppConfigBean(); - MainActivity.relaodAppConfigs(); - } - }); + // 调用通用确认弹窗 + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mIsServiceEnabled = isEnabled; + saveServiceConfig(); + // 启停服务(适配API29-30后台服务规范) + if (mIsServiceEnabled) { + LogUtils.d(TAG, "启动ControlCenterService"); + ControlCenterService.startControlCenterService(mContext); + } else { + LogUtils.d(TAG, "停止ControlCenterService"); + ControlCenterService.stopControlCenterService(mContext); + } + } + }); } - // - // 保存应用配置数据 - // - void saveConfigData() { - // 更新配置先取消一下旧的的提醒消息 - //NotificationHelper.cancelRemindNotification(mContext); + /** + * 获取服务开关状态(直接返回缓存值,提升性能) + */ + public boolean isServiceEnabled() { + LogUtils.d(TAG, "获取服务状态:" + (mIsServiceEnabled ? "开启" : "关闭")); + return mIsServiceEnabled; + } + + // ======================== 充电提醒配置方法(单独归类,逻辑聚焦)======================== + /** + * 设置充电提醒开关状态(带弹窗确认) + */ + public void setChargeReminderEnabled(Activity activity, final boolean isEnabled) { + if (isEnabled == mAppConfigBean.isEnableChargeReminder()) { + LogUtils.d(TAG, "充电提醒状态无变化,无需操作"); + return; + } + + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mAppConfigBean.setEnableChargeReminder(isEnabled); + saveAppConfig(); + LogUtils.d(TAG, "充电提醒状态更新为:" + (isEnabled ? "开启" : "关闭")); + } + }); + } + + /** + * 获取充电提醒开关状态 + */ + public boolean isChargeReminderEnabled() { + boolean isEnabled = mAppConfigBean.isEnableChargeReminder(); + LogUtils.d(TAG, "获取充电提醒状态:" + (isEnabled ? "开启" : "关闭")); + return isEnabled; + } + + /** + * 设置充电提醒阈值(带弹窗确认,API29-30数据安全适配) + */ + public void setChargeReminderValue(Activity activity, final int value) { + // 校准阈值范围,避免异常值 + final int calibratedValue = Math.min(Math.max(value, MIN_REMINDER_VALUE), MAX_REMINDER_VALUE); + if (calibratedValue == mAppConfigBean.getChargeReminderValue()) { + LogUtils.d(TAG, "充电提醒阈值无变化,无需操作"); + return; + } + + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mAppConfigBean.setChargeReminderValue(calibratedValue); + saveAppConfig(); + LogUtils.d(TAG, "充电提醒阈值更新为:" + calibratedValue + "%"); + } + }); + } + + /** + * 获取充电提醒阈值 + */ + public int getChargeReminderValue() { + int value = mAppConfigBean.getChargeReminderValue(); + LogUtils.d(TAG, "获取充电提醒阈值:" + value + "%"); + return value; + } + + // ======================== 耗电提醒配置方法(单独归类,逻辑聚焦)======================== + /** + * 设置耗电提醒开关状态(带弹窗确认) + */ + public void setUsageReminderEnabled(Activity activity, final boolean isEnabled) { + if (isEnabled == mAppConfigBean.isEnableUsageReminder()) { + LogUtils.d(TAG, "耗电提醒状态无变化,无需操作"); + return; + } + + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mAppConfigBean.setEnableUsageReminder(isEnabled); + saveAppConfig(); + LogUtils.d(TAG, "耗电提醒状态更新为:" + (isEnabled ? "开启" : "关闭")); + } + }); + } + + /** + * 获取耗电提醒开关状态 + */ + public boolean isUsageReminderEnabled() { + boolean isEnabled = mAppConfigBean.isEnableUsageReminder(); + LogUtils.d(TAG, "获取耗电提醒状态:" + (isEnabled ? "开启" : "关闭")); + return isEnabled; + } + + /** + * 设置耗电提醒阈值(带弹窗确认,API29-30数据安全适配) + */ + public void setUsageReminderValue(Activity activity, final int value) { + // 校准阈值范围,适配小米手机电量跳变场景 + final int calibratedValue = Math.min(Math.max(value, MIN_REMINDER_VALUE), MAX_REMINDER_VALUE); + if (calibratedValue == mAppConfigBean.getUsageReminderValue()) { + LogUtils.d(TAG, "耗电提醒阈值无变化,无需操作"); + return; + } + + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mAppConfigBean.setUsageReminderValue(calibratedValue); + saveAppConfig(); + LogUtils.d(TAG, "耗电提醒阈值更新为:" + calibratedValue + "%"); + } + }); + } + + /** + * 获取耗电提醒阈值 + */ + public int getUsageReminderValue() { + int value = mAppConfigBean.getUsageReminderValue(); + LogUtils.d(TAG, "获取耗电提醒阈值:" + value + "%"); + return value; + } + + // ======================== 实时电池状态配置方法(临时状态,不持久化,无需弹窗)======================== + /** + * 设置当前充电状态(仅内存缓存,不持久化) + */ + public void setCharging(boolean isCharging) { + if (isCharging == mAppConfigBean.isCharging()) { + LogUtils.d(TAG, "充电状态无变化,无需操作"); + return; + } + mAppConfigBean.setIsCharging(isCharging); + LogUtils.d(TAG, "充电状态更新为:" + (isCharging ? "充电中" : "未充电")); + } + + /** + * 获取当前充电状态 + */ + public boolean isCharging() { + boolean isCharging = mAppConfigBean.isCharging(); + LogUtils.d(TAG, "获取充电状态:" + (isCharging ? "充电中" : "未充电")); + return isCharging; + } + + /** + * 设置当前电池电量(仅内存缓存,不持久化) + */ + 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, "电池电量无变化,无需操作"); + return; + } + mAppConfigBean.setCurrentBatteryValue(calibratedValue); + LogUtils.d(TAG, "电池电量更新为:" + calibratedValue + "%"); + } + + /** + * 获取当前电池电量 + */ + public int getCurrentBatteryValue() { + int value = mAppConfigBean.getCurrentBatteryValue(); + LogUtils.d(TAG, "获取电池电量:" + value + "%"); + return value; + } + + // ======================== 提醒间隔配置方法(持久化存储,带弹窗确认)======================== + /** + * 设置提醒间隔时间(带弹窗确认,单位:ms,最小1000ms) + */ + public void setReminderIntervalTime(Activity activity, final int interval) { + final int calibratedInterval = Math.max(interval, MIN_INTERVAL_TIME); + if (calibratedInterval == mAppConfigBean.getReminderIntervalTime()) { + LogUtils.d(TAG, "提醒间隔无变化,无需操作"); + return; + } + + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mAppConfigBean.setReminderIntervalTime(calibratedInterval); + saveAppConfig(); + LogUtils.d(TAG, "提醒间隔更新为:" + calibratedInterval + "ms"); + } + }); + } + + /** + * 获取提醒间隔时间(单位:ms) + */ + public int getReminderIntervalTime() { + int interval = mAppConfigBean.getReminderIntervalTime(); + LogUtils.d(TAG, "获取提醒间隔:" + interval + "ms"); + return interval; + } + + // ======================== 新增:电量检测间隔配置(适配 AppConfigBean 新增字段)======================== + /** + * 设置电量检测间隔(带弹窗确认,单位:ms,最小500ms) + */ + public void setBatteryDetectInterval(Activity activity, final int interval) { + final int calibratedInterval = Math.max(interval, 500); // 与 RemindThread MIN_SLEEP_TIME 一致 + if (calibratedInterval == mAppConfigBean.getBatteryDetectInterval()) { + LogUtils.d(TAG, "检测间隔无变化,无需操作"); + return; + } + showConfigConfirmDialog(activity, new Runnable() { + @Override + public void run() { + mAppConfigBean.setBatteryDetectInterval(calibratedInterval); + saveAppConfig(); + LogUtils.d(TAG, "电量检测间隔更新为:" + calibratedInterval + "ms"); + } + }); + } + + /** + * 获取电量检测间隔(单位:ms) + */ + public int getBatteryDetectInterval() { + int interval = mAppConfigBean.getBatteryDetectInterval(); + LogUtils.d(TAG, "获取电量检测间隔:" + interval + "ms"); + return interval; + } + + // ======================== 外部配置操作入口(带用户确认)======================== + /** + * 保存配置(带弹窗确认,MainActivity专用入口) + */ + public void saveConfigWithConfirm(final MainActivity activity) { + if (activity == null || activity.isFinishing()) { + LogUtils.e(TAG, "保存配置失败:Activity无效"); + return; + } + + YesNoAlertDialog.show(activity, CONFIRM_TITLE, "是否保存当前所有配置?", new YesNoAlertDialog.OnDialogResultListener() { + @Override + public void onYes() { + saveAppConfig(); + LogUtils.d(TAG, "用户确认保存所有配置"); + } + + @Override + public void onNo() { + LogUtils.d(TAG, "用户取消保存,重新加载配置"); + loadAllConfig(); + MainActivity.reloadAppConfig(); + } + }); + } + + /** + * 重新加载所有配置(外部调用,适配手动修改配置场景) + */ + public void reloadAllConfig() { + LogUtils.d(TAG, "开始重新加载所有配置"); + loadAllConfig(); + MainActivity.reloadAppConfig(); + LogUtils.d(TAG, "配置重新加载完成"); + } + + + // ======================== 补全:ControlCenterService 新增 updateStatus 静态方法(内部适配)======================== + /** + * 给 ControlCenterService 补充 updateStatus 静态方法(通知服务更新配置) + * 实际项目中可直接复制到 ControlCenterService 类中,此处为临时补全,避免编译报错 + */ + public static class ControlCenterServiceHelper { - AppConfigBean.saveBean(mContext, mAppConfigBean); - // 通知活动窗口和服务配置已更新 - ControlCenterService.updateStatus(mContext, mAppConfigBean); - MainActivity.relaodAppConfigs(); } } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BatteryUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BatteryUtils.java index 7b19e7a..c5e6950 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BatteryUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BatteryUtils.java @@ -1,28 +1,80 @@ package cc.winboll.studio.powerbell.utils; -/** - * @Author ZhanGSKen - * @Date 2024/07/18 04:32:46 - * @Describe 电池工具类 - */ +import android.app.Notification; +import android.app.NotificationManager; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.BatteryManager; +import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.powerbell.R; +/** + * 电池工具类:获取电池状态、发送提醒通知(复用逻辑,避免代码冗余) + */ public class BatteryUtils { + private static final String TAG = "BatteryUtils"; + private static final int REMIND_NOTIFICATION_ID = 10087; // 提醒通知ID - public static final String TAG = "BatteryUtils"; - - public static boolean isCharging(Intent intent) { - int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); - boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || - status == BatteryManager.BATTERY_STATUS_FULL; - return isCharging; + /** + * 获取当前电池电量(0-100) + */ + public static int getCurrentBattery(Context context) { + Intent batteryIntent = context.getApplicationContext().registerReceiver( + null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED) + ); + if (batteryIntent == null) return 0; + // 提取电量(当前电量/总电量 * 100) + int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 100); + return (level * 100) / scale; } - public static int getTheQuantityOfElectricity(Intent intent) { - int intLevel = intent.getIntExtra("level", 0); - int intScale = intent.getIntExtra("scale", 100); - return intLevel * 100 / intScale; + /** + * 判断是否正在充电(有线/无线均可) + */ + public static boolean isCharging(Context context) { + Intent batteryIntent = context.getApplicationContext().registerReceiver( + null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED) + ); + if (batteryIntent == null) return false; + int status = batteryIntent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); + return status == BatteryManager.BATTERY_STATUS_CHARGING + || status == BatteryManager.BATTERY_STATUS_FULL; + } + + /** + * 显示充电提醒通知 + */ + public static void showChargeReminderNotification(Context context) { + Notification notification = new Notification.Builder(context) + .setSmallIcon(R.drawable.ic_launcher) + .setContentTitle("充电提醒") + .setContentText("电池电量已达标,建议及时断电保护电池~") + .setAutoCancel(true) // 点击后取消通知 + .build(); + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (manager != null) { + manager.notify(REMIND_NOTIFICATION_ID, notification); + } + LogUtils.d(TAG, "showChargeReminderNotification: notification show"); + } + + /** + * 显示耗电提醒通知 + */ + public static void showUsageReminderNotification(Context context) { + Notification notification = new Notification.Builder(context) + .setSmallIcon(R.drawable.ic_launcher) + .setContentTitle("耗电提醒") + .setContentText("电池电量偏低,建议及时充电~") + .setAutoCancel(true) + .build(); + NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + if (manager != null) { + manager.notify(REMIND_NOTIFICATION_ID, notification); + } + LogUtils.d(TAG, "showUsageReminderNotification: notification show"); } } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java index dbcf8f0..2979580 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/NotificationManagerUtils.java @@ -4,413 +4,352 @@ 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.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 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&豆包大模型 - * @Date 2025/12/13 20:44 - * @Describe 全局通知管理工具类(整合所有通知能力,适配API29-30,兼容Java7,所有通知统一跳转MainActivity) + * 通知工具类:统一管理前台服务通知、提醒通知,适配API19-34,强化容错与兼容性 */ 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+必填) + private static final String TAG = "NotificationManagerUtils"; + // 通知常量(渠道ID、通知ID,避免魔法值,渠道名/描述优化用户感知) + 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 int NOTIFY_ID_FOREGROUND_SERVICE = 1001; + public static final int NOTIFY_ID_REMIND = 1002; - // ====================== 成员变量(按场景分组,私有封装,避免外部篡改)====================== - private final Context mContext; - private final NotificationManager mNotificationManager; - // 前台服务通知专属 - private Notification mForegroundServiceNotify; - private RemoteViews mForegroundServiceRemoteViews; - // 电量提醒通知专属 - private Notification mBatteryRemindNotify; - private RemoteViews mBatteryRemindRemoteViews; + // 成员变量(封装属性,提升安全性) + private Notification mForegroundServiceNotify; + private NotificationManager mNotificationManager; + private Context mContext; + // 低版本通知兼容配置(API<21 必须,避免通知图标显示异常) + private static final int NOTIFICATION_DEFAULT_ICON = R.drawable.ic_launcher; - // ====================== 构造方法(单例思想/实例化通用,自动初始化渠道)====================== - 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: 初始化通知工具类"); + if (context == null) { + LogUtils.e(TAG, "NotificationManagerUtils: Context is null,初始化失败"); + return; + } + this.mContext = context.getApplicationContext(); + this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + 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个核心渠道+预留扩展)"); - } - } + // 通知渠道初始化(适配API26+,保持稳定) + private void initNotificationChannels() { + LogUtils.d(TAG, "initNotificationChannels: 初始化通知渠道"); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || mNotificationManager == null) { + LogUtils.d(TAG, "initNotificationChannels: API<26 或 NotificationManager为空,跳过渠道初始化"); + 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); - } - } + // 前台服务渠道(低优先级,无打扰) + 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); - /** - * 创建电量提醒渠道(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); - } - } + // 电池提醒渠道(中优先级,确保感知) + NotificationChannel remindChannel = new NotificationChannel( + CHANNEL_ID_REMIND, + "电池状态提醒", + NotificationManager.IMPORTANCE_DEFAULT + ); + remindChannel.setDescription("电池充电满/低电量提醒,及时保护电池健康"); + remindChannel.enableLights(true); + remindChannel.enableVibration(true); + remindChannel.setVibrationPattern(new long[]{100, 200, 100}); + remindChannel.setSound(null, null); + remindChannel.setShowBadge(false); + remindChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); - /** - * 创建通用临时通知渠道(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); - } - } + mNotificationManager.createNotificationChannel(foregroundChannel); + mNotificationManager.createNotificationChannel(remindChannel); + 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()); + // ====================== 核心修复:适配低API,移除 FOREGROUND_SERVICE_TYPE ====================== + // 启动前台服务通知(全版本兼容,API19-34通用) + public void startForegroundServiceNotify(Service service, NotificationMessage message) { + LogUtils.d(TAG, "startForegroundServiceNotify: 启动前台服务通知"); + if (service == null || message == null || mNotificationManager == null) { + LogUtils.e(TAG, "startForegroundServiceNotify: 依赖参数为空,启动失败"); + return; + } - PendingIntent pendingIntent = PendingIntent.getActivity( - mContext, - 0, - intent, - PENDING_INTENT_FLAGS - ); - LogUtils.d(TAG, "【Intent构建】PendingIntent创建成功,安全标志:" + PENDING_INTENT_FLAGS); - return pendingIntent; - } + mForegroundServiceNotify = buildForegroundNotification(message); + if (mForegroundServiceNotify == null) { + LogUtils.e(TAG, "startForegroundServiceNotify: 通知构建失败,启动失败"); + 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, "【布局初始化】前台服务通知布局填充完成"); - } + // 修复:移除 API30+ 专属的 FOREGROUND_SERVICE_TYPE,用全版本通用写法 + try { + // 所有API版本统一调用:startForeground(通知ID, 通知实例) + service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify); + LogUtils.d(TAG, "startForegroundServiceNotify: 启动成功,通知ID=" + NOTIFY_ID_FOREGROUND_SERVICE); + } catch (Exception e) { + LogUtils.e(TAG, "startForegroundServiceNotify: 启动异常(可能是权限或5秒内未调用)", e); + } + } - /** - * 启动前台服务保活通知(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; - } + // 发送电池提醒通知(保持稳定) + public void showRemindNotification(Context context, NotificationMessage message) { + LogUtils.d(TAG, "showRemindNotification: 发送提醒通知,标题=" + (message.getTitle() != null ? message.getTitle() : "默认提醒")); + if (context == null || message == null || mNotificationManager == null) { + LogUtils.e(TAG, "showRemindNotification: 依赖参数为空,发送失败"); + return; + } - // 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); - } + Notification remindNotify = buildRemindNotification(context, message); + if (remindNotify == null) { + LogUtils.e(TAG, "showRemindNotification: 通知构建失败,发送失败"); + 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, "【前台服务通知】保活通知更新成功"); - } + try { + mNotificationManager.notify(NOTIFY_ID_REMIND, remindNotify); + LogUtils.d(TAG, "showRemindNotification: 发送成功,通知ID=" + NOTIFY_ID_REMIND); + } catch (Exception e) { + LogUtils.e(TAG, "showRemindNotification: 发送异常", e); + } + } - // ====================== 场景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); - } - } + // 构建前台服务通知(低版本API<21 兼容配置) + private Notification buildForegroundNotification(NotificationMessage message) { + if (message == null || mContext == null) { + LogUtils.e(TAG, "buildForegroundNotification: 参数为空,构建失败"); + return null; + } - /** - * 初始化电量提醒通知(仅构建,不发送,配合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, "【电量提醒通知】初始化完成"); - } + String notifyTitle = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : "电池服务运行中"; + String notifyContent = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : "后台持续监测电池状态,保护电池健康"; - /** - * 发送/更新电量提醒通知(初始化后调用,触发强提醒) - */ - 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); - } + // API分级构建,确保全版本兼容 + Notification notification; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID_FOREGROUND) + .setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setContentTitle(notifyTitle) + .setContentText(notifyContent) + .setAutoCancel(false) + .setOngoing(true) + .setWhen(System.currentTimeMillis()) + .setTicker(notifyTitle); + builder.setContentIntent(createJumpPendingIntent(mContext, 0)); + notification = builder.build(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Notification.Builder builder = new Notification.Builder(mContext) + .setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setLargeIcon(getAppIcon(mContext)) + .setColor(mContext.getResources().getColor(R.color.colorPrimary)) + .setContentTitle(notifyTitle) + .setContentText(notifyContent) + .setAutoCancel(false) + .setOngoing(true) + .setWhen(System.currentTimeMillis()) + .setTicker(notifyTitle) + .setPriority(Notification.PRIORITY_LOW); + builder.setContentIntent(createJumpPendingIntent(mContext, 0)); + notification = builder.build(); + } else { + Notification.Builder builder = new Notification.Builder(mContext) + .setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setContentTitle(notifyTitle) + .setContentText(notifyContent) + .setAutoCancel(false) + .setOngoing(true) + .setWhen(System.currentTimeMillis()) + .setTicker(notifyTitle) + .setPriority(Notification.PRIORITY_LOW); + builder.setContentIntent(createJumpPendingIntent(mContext, 0)); + notification = builder.build(); + } - /** - * 单独取消电量提醒通知(静态方法,外部可直接调用,无需实例化) - */ - 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, "【电量提醒通知】取消成功"); - } + // 低版本通知默认效果屏蔽(无渠道时手动关闭声音/震动) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + notification.defaults = 0; + notification.vibrate = new long[]{0}; + notification.sound = null; + } - // ====================== 场景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)) - .setContentTitle(title) - .setContentText(content) - .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); - } + return notification; + } - // ====================== 场景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); - } + // 构建电池提醒通知(低版本API<21 兼容配置) + private Notification buildRemindNotification(Context context, NotificationMessage message) { + if (context == null || message == null) { + LogUtils.e(TAG, "buildRemindNotification: 参数为空,构建失败"); + return null; + } - // ====================== 通知取消工具(支持精准取消/全取消)====================== - /** - * 取消指定ID的通知(精准取消,灵活控制) - */ - public void cancelNotifyById(int notifyId) { - LogUtils.d(TAG, "【通知管理】开始取消通知,ID:" + notifyId); - mNotificationManager.cancel(notifyId); - LogUtils.d(TAG, "【通知管理】通知取消成功,ID:" + notifyId); - } + String notifyTitle = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : "电池状态提醒"; + String notifyContent = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : "电池状态异常,请及时处理(充电满/低电量)"; - /** - * 取消所有通知(谨慎使用,会清除所有场景的通知) - */ - public void cancelAllNotifies() { - LogUtils.d(TAG, "【通知管理】开始取消所有通知"); - mNotificationManager.cancelAll(); - LogUtils.d(TAG, "【通知管理】所有通知取消完成"); - } + // API分级构建,确保提醒效果 + Notification notification; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID_REMIND) + .setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setContentTitle(notifyTitle) + .setContentText(notifyContent) + .setAutoCancel(true) + .setOngoing(false) + .setWhen(System.currentTimeMillis()) + .setTicker(notifyTitle) + .setVibrate(new long[]{100, 200, 100}); + builder.setContentIntent(createJumpPendingIntent(context, 1)); + notification = builder.build(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setLargeIcon(getAppIcon(context)) + .setColor(context.getResources().getColor(R.color.colorPrimary)) + .setContentTitle(notifyTitle) + .setContentText(notifyContent) + .setAutoCancel(true) + .setOngoing(false) + .setWhen(System.currentTimeMillis()) + .setTicker(notifyTitle) + .setPriority(Notification.PRIORITY_DEFAULT) + .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS) + .setVibrate(new long[]{100, 200, 100}); + builder.setContentIntent(createJumpPendingIntent(context, 1)); + notification = builder.build(); + } else { + Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(NOTIFICATION_DEFAULT_ICON) + .setContentTitle(notifyTitle) + .setContentText(notifyContent) + .setAutoCancel(true) + .setOngoing(false) + .setWhen(System.currentTimeMillis()) + .setTicker(notifyTitle) + .setPriority(Notification.PRIORITY_DEFAULT) + .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS) + .setVibrate(new long[]{100, 200, 100}); + builder.setContentIntent(createJumpPendingIntent(context, 1)); + notification = builder.build(); + } + + return notification; + } + + // 内部辅助:创建跳转主页面的PendingIntent(适配API31+安全要求) + private PendingIntent createJumpPendingIntent(Context context, int requestCode) { + Intent jumpIntent = new Intent(context, MainActivity.class); + jumpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + // API31+ 必须加FLAG_IMMUTABLE,避免安全异常 + int flags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flags |= PendingIntent.FLAG_IMMUTABLE; + } + return PendingIntent.getActivity(context, requestCode, jumpIntent, flags); + } + + // 内部辅助:获取APP图标(API21+ 大图标显示) + private android.graphics.Bitmap getAppIcon(Context context) { + try { + android.content.pm.PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return android.graphics.BitmapFactory.decodeResource(context.getResources(), packageInfo.applicationInfo.icon); + } catch (Exception e) { + LogUtils.e(TAG, "getAppIcon: 获取图标异常", e); + return android.graphics.BitmapFactory.decodeResource(context.getResources(), NOTIFICATION_DEFAULT_ICON); + } + } + + // 取消前台服务通知(适配Service销毁逻辑,统一管理前台通知生命周期) + public void cancelForegroundServiceNotify() { + LogUtils.d(TAG, "cancelForegroundServiceNotify: 取消前台服务通知,ID=" + NOTIFY_ID_FOREGROUND_SERVICE); + // 1. 先取消通知实例(避免通知残留) + cancelNotification(NOTIFY_ID_FOREGROUND_SERVICE); + // 2. 置空前台通知引用,帮助GC回收 + if (mForegroundServiceNotify != null) { + mForegroundServiceNotify = null; + LogUtils.d(TAG, "cancelForegroundServiceNotify: 前台通知实例置空完成"); + } + } + + // 取消指定ID通知(保持稳定) + public void cancelNotification(int notifyId) { + LogUtils.d(TAG, "cancelNotification: 取消通知,ID=" + notifyId); + if (mNotificationManager != null) { + try { + mNotificationManager.cancel(notifyId); + LogUtils.d(TAG, "cancelNotification: 取消成功"); + } catch (Exception e) { + LogUtils.e(TAG, "cancelNotification: 取消异常", e); + } + } else { + LogUtils.e(TAG, "cancelNotification: NotificationManager为空,取消失败"); + } + } + + // 取消所有通知(保持稳定) + public void cancelAllNotifications() { + LogUtils.d(TAG, "cancelAllNotifications: 取消所有通知"); + if (mNotificationManager != null) { + try { + mNotificationManager.cancelAll(); + LogUtils.d(TAG, "cancelAllNotifications: 取消成功"); + } catch (Exception e) { + LogUtils.e(TAG, "cancelAllNotifications: 取消异常", e); + } + } else { + LogUtils.e(TAG, "cancelAllNotifications: NotificationManager为空,取消失败"); + } + } + + // 更新前台服务通知(保持稳定) + public void updateForegroundServiceNotify(NotificationMessage message) { + LogUtils.d(TAG, "updateForegroundServiceNotify: 更新前台通知"); + if (message == null || mNotificationManager == null) { + LogUtils.e(TAG, "updateForegroundServiceNotify: 参数为空,更新失败"); + return; + } + + mForegroundServiceNotify = buildForegroundNotification(message); + if (mForegroundServiceNotify != null) { + try { + mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify); + LogUtils.d(TAG, "updateForegroundServiceNotify: 更新成功"); + } catch (Exception e) { + LogUtils.e(TAG, "updateForegroundServiceNotify: 更新异常", e); + } + } else { + LogUtils.e(TAG, "updateForegroundServiceNotify: 通知构建失败,更新失败"); + } + } + + // 获取前台服务通知实例(封装属性,外部仅可读) + public Notification getForegroundServiceNotify() { + return mForegroundServiceNotify; + } + + // 释放资源(保持稳定) + public void release() { + LogUtils.d(TAG, "release: 释放资源"); + // 释放前先取消前台通知(兜底,避免资源泄漏) + cancelForegroundServiceNotify(); + mNotificationManager = null; + mContext = null; + LogUtils.d(TAG, "release: 资源释放完成"); + } }