Compare commits
39 Commits
powerbell-
...
powerbell-
| Author | SHA1 | Date | |
|---|---|---|---|
| 10348d2c8d | |||
| 21cdc219c8 | |||
| 3ebf87c642 | |||
| 347a4040cd | |||
| 6fd86a2742 | |||
| 798357aedd | |||
| 9124303fd3 | |||
| 414541093a | |||
| 13265be66e | |||
| ebd32adb68 | |||
| 56a65cd10a | |||
| e379684002 | |||
| 4077ac18f6 | |||
| 278e690795 | |||
| e81fc65b90 | |||
| abd956d7d0 | |||
| fca17908b2 | |||
| 39a3a5aeb0 | |||
| 2e30f577b5 | |||
| dcb5355233 | |||
| d1ced7ac63 | |||
| bf0cf23144 | |||
| 890b32ceae | |||
| c347d51c84 | |||
| 7278e9f22f | |||
| 4e98c8d699 | |||
| 3ec1bbe264 | |||
| f4a2a1585d | |||
| 35f4aa8730 | |||
| 5b1d160dac | |||
| db87ba51e3 | |||
| fd6e852061 | |||
| 8ff4b9e1f4 | |||
| 78a2d85150 | |||
| fd2d1d17ed | |||
| 37f3091103 | |||
| 6dc5f05702 | |||
| 77df2558b3 | |||
| 315541f2d2 |
@@ -87,7 +87,7 @@ dependencies {
|
||||
//api 'cc.winboll.studio:libappbase:15.12.2'
|
||||
|
||||
// WinBoLL备用库 jitpack.io 地址
|
||||
api 'com.github.ZhanGSKen:AES:aes-v15.12.7'
|
||||
api 'com.github.ZhanGSKen:AES:aes-v15.12.8'
|
||||
api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
|
||||
|
||||
//api fileTree(dir: 'libs', include: ['*.aar'])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Dec 25 16:31:07 HKT 2025
|
||||
stageCount=32
|
||||
#Tue Dec 30 18:16:29 HKT 2025
|
||||
stageCount=43
|
||||
libraryProject=
|
||||
baseVersion=15.14
|
||||
publishVersion=15.14.31
|
||||
publishVersion=15.14.42
|
||||
buildCount=0
|
||||
baseBetaVersion=15.14.32
|
||||
baseBetaVersion=15.14.43
|
||||
|
||||
@@ -4,57 +4,84 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cc.winboll.studio.powerbell">
|
||||
|
||||
<!-- 前台服务权限 -->
|
||||
<!-- 此应用可显示在其他应用上方 -->
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<!-- 运行前台服务 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<!-- 运行“specialUse”类型的前台服务 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
||||
|
||||
<!-- 系统事件权限 -->
|
||||
<!-- 开机启动 -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
||||
<!-- 通知权限 -->
|
||||
<!-- 显示通知 -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<!-- 应用统计与查询权限 -->
|
||||
<!-- PACKAGE_USAGE_STATS -->
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
|
||||
|
||||
<!-- BATTERY_STATS -->
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
||||
|
||||
<!-- 计算应用存储空间 -->
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||
|
||||
<!-- 请求忽略电池优化 -->
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
|
||||
<!-- 读取您共享存储空间中的内容 -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- 修改或删除您共享存储空间中的内容 -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- MANAGE_EXTERNAL_STORAGE -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- 拍摄照片和视频 -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission"/>
|
||||
|
||||
<!-- 电池与存储统计权限 -->
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
||||
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
|
||||
<!-- 外部存储权限(添加版本兼容) -->
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- 相机权限 -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<!-- 硬件特性声明 -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false"/>
|
||||
|
||||
<!-- 应用查询 -->
|
||||
<queries>
|
||||
|
||||
<package android:name="com.miui.securitycenter"/>
|
||||
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:process=":main"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -66,15 +93,13 @@
|
||||
android:supportsRtl="true"
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
|
||||
<!-- 主活动(主进程) -->
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"/>
|
||||
|
||||
<!-- 活动别名(启动器,主进程) -->
|
||||
<activity-alias
|
||||
android:name=".MainActivityEN1"
|
||||
android:targetActivity=".MainActivity"
|
||||
@@ -82,13 +107,19 @@
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:enabled="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcutsmainen1"/>
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
@@ -98,13 +129,19 @@
|
||||
android:label="@string/app_name_cn1"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:enabled="false">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcutsmaincn1"/>
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
@@ -114,138 +151,175 @@
|
||||
android:label="@string/app_name_cn2"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:enabled="false">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcutsmaincn2"/>
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<!-- 功能活动(主进程) -->
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.CrashActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.ClearRecordActivity"
|
||||
android:parentActivityName="cc.winboll.studio.powerbell.MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.BackgroundSettingsActivity"
|
||||
android:parentActivityName="cc.winboll.studio.powerbell.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
<data android:mimeType="image/jpeg"/>
|
||||
|
||||
<data android:mimeType="image/jpg"/>
|
||||
|
||||
<data android:mimeType="image/png"/>
|
||||
|
||||
<data android:mimeType="image/webp"/>
|
||||
|
||||
<data android:mimeType="image/*"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.BatteryReporterActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.PixelPickerActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.BatteryReportActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".unittest.MainUnitTestActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.ShortcutActionActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name="cc.winboll.studio.powerbell.unittest.MainUnitTest2Activity"
|
||||
android:exported="false"/>
|
||||
|
||||
<!-- 第三方活动(主进程) -->
|
||||
<activity
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:exported="true"/>
|
||||
|
||||
<!-- 广播接收器(主进程) -->
|
||||
<receiver
|
||||
android:process=":main"
|
||||
android:name=".receivers.MainReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process=".main"
|
||||
android:directBootAware="true">
|
||||
|
||||
<intent-filter android:priority="1000">
|
||||
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
|
||||
<action android:name="android.intent.action.POWER_CONNECTED"/>
|
||||
|
||||
<action android:name="android.intent.action.USER_PRESENT"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<!-- 服务(ControlCenterService主进程,AssistantService独立进程) -->
|
||||
<service
|
||||
android:name=".services.ControlCenterService"
|
||||
android:priority="1000"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=".main"
|
||||
android:process=":main"
|
||||
android:stopWithTask="false"
|
||||
android:foregroundServiceType="dataSync">
|
||||
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
||||
android:value="后台核心功能运行、持续保活"/>
|
||||
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".services.AssistantService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=".assistant"
|
||||
android:process=":assistant"
|
||||
android:stopWithTask="false"
|
||||
android:foregroundServiceType="dataSync">
|
||||
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
||||
android:value="辅助核心功能运行"/>
|
||||
|
||||
</service>
|
||||
|
||||
<!-- 内容提供者(主进程) -->
|
||||
<service
|
||||
android:name=".services.TTSPlayService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":main"
|
||||
android:stopWithTask="false"/>
|
||||
|
||||
<service android:name=".services.ThoughtfulService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":main"
|
||||
android:stopWithTask="false"/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
android:process=".main">
|
||||
android:process=":main">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<!-- 元数据 -->
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="4.0"/>
|
||||
@@ -253,4 +327,3 @@
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
|
||||
@@ -17,13 +17,18 @@ import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView;
|
||||
/**
|
||||
* 应用全局入口类(适配Android API 30,基于Java 7编写)
|
||||
* 核心策略:极致强制缓存 - 无论内存紧张程度,永不自动清理任何缓存(Bitmap/视图控件/路径记录)
|
||||
*
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Version 1.0.0
|
||||
* @Date 2025-12-25
|
||||
* @Date 2025-12-29
|
||||
*/
|
||||
public class App extends GlobalApplication {
|
||||
// ===================== 常量定义区(按功能分类排序) =====================
|
||||
public static final String TAG = "App";
|
||||
|
||||
// ==================== 常量定义 ====================
|
||||
|
||||
private static final String TAG = "App";
|
||||
private static final String CACHE_PROTECT_TAG = "FORCE_CACHE_PROTECT";
|
||||
private static final int INVALID_BATTERY_VALUE = -1;
|
||||
|
||||
// 组件跳转常量
|
||||
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
||||
@@ -35,278 +40,268 @@ public class App extends GlobalApplication {
|
||||
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1";
|
||||
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2";
|
||||
|
||||
// 缓存防护常量
|
||||
private static final String CACHE_PROTECT_TAG = "FORCE_CACHE_PROTECT";
|
||||
private static final int INVALID_BATTERY_VALUE = -1;
|
||||
// ==================== 静态属性 ====================
|
||||
|
||||
// ===================== 静态属性区(按工具类优先级排序) =====================
|
||||
// 数据配置工具
|
||||
private static App sApp;
|
||||
|
||||
// 配置与缓存
|
||||
public static AppConfigUtils sAppConfigUtils;
|
||||
private static AppCacheUtils sAppCacheUtils;
|
||||
|
||||
// 全局Bitmap缓存工具(极致强制保持:一旦初始化,永不销毁)
|
||||
// 资源与视图缓存(强制驻留)
|
||||
public static BackgroundSourceUtils sBackgroundSourceUtils;
|
||||
public static BitmapCacheUtils sBitmapCacheUtils;
|
||||
|
||||
// 全局视图控件缓存工具(极致强制保持:一旦初始化,永不销毁)
|
||||
// 核心修改:改为私有静态,通过实例方法获取,避免全局通用暴露
|
||||
private static MemoryCachedBackgroundView sMemoryCachedBackgroundView;
|
||||
|
||||
// 状态
|
||||
public static volatile int sQuantityOfElectricity = INVALID_BATTERY_VALUE;
|
||||
|
||||
// ===================== 成员属性区(按生命周期关联度排序) =====================
|
||||
// 全局广播接收器
|
||||
// 系统工具
|
||||
private static NotificationManagerUtils sNotificationManagerUtils;
|
||||
|
||||
// ==================== 成员属性 ====================
|
||||
|
||||
private GlobalApplicationReceiver mGlobalReceiver;
|
||||
|
||||
// 通知管理工具
|
||||
private static NotificationManagerUtils sNotificationManagerUtils;
|
||||
private static App sApp;
|
||||
// ==================== 公共静态方法 (工具/单例) ====================
|
||||
|
||||
// ===================== 公共静态方法区(工具类实例获取) =====================
|
||||
/**
|
||||
* 获取应用配置工具实例
|
||||
* 获取应用单例
|
||||
*/
|
||||
public static App getInstance() {
|
||||
LogUtils.d(TAG, "getInstance() called | Result: " + sApp);
|
||||
return sApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置工具实例
|
||||
*/
|
||||
public static AppConfigUtils getAppConfigUtils(Context context) {
|
||||
LogUtils.d(TAG, String.format("getAppConfigUtils() 调用 | 传入Context类型=%s",
|
||||
context != null ? context.getClass().getSimpleName() : "null"));
|
||||
String contextName = context != null ? context.getClass().getSimpleName() : "null";
|
||||
LogUtils.d(TAG, "getAppConfigUtils() called with: context = [" + contextName + "]");
|
||||
|
||||
if (sAppConfigUtils == null) {
|
||||
sAppConfigUtils = AppConfigUtils.getInstance(context);
|
||||
LogUtils.d(TAG, "getAppConfigUtils():AppConfigUtils实例已初始化");
|
||||
LogUtils.d(TAG, "getAppConfigUtils: Initialized new instance");
|
||||
}
|
||||
return sAppConfigUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用缓存工具实例
|
||||
* 获取缓存工具实例
|
||||
*/
|
||||
public static AppCacheUtils getAppCacheUtils(Context context) {
|
||||
LogUtils.d(TAG, String.format("getAppCacheUtils() 调用 | 传入Context类型=%s",
|
||||
context != null ? context.getClass().getSimpleName() : "null"));
|
||||
String contextName = context != null ? context.getClass().getSimpleName() : "null";
|
||||
LogUtils.d(TAG, "getAppCacheUtils() called with: context = [" + contextName + "]");
|
||||
|
||||
if (sAppCacheUtils == null) {
|
||||
sAppCacheUtils = AppCacheUtils.getInstance(context);
|
||||
LogUtils.d(TAG, "getAppCacheUtils():AppCacheUtils实例已初始化");
|
||||
LogUtils.d(TAG, "getAppCacheUtils: Initialized new instance");
|
||||
}
|
||||
return sAppCacheUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用单例实例
|
||||
*/
|
||||
public static App getInstance() {
|
||||
return sApp;
|
||||
}
|
||||
// ==================== 公共成员方法 (业务) ====================
|
||||
|
||||
// ===================== 公共成员方法区(业务功能) =====================
|
||||
/**
|
||||
* 清除电池历史数据
|
||||
*/
|
||||
public void clearBatteryHistory() {
|
||||
LogUtils.d(TAG, "clearBatteryHistory() 调用");
|
||||
LogUtils.d(TAG, "clearBatteryHistory() called");
|
||||
if (sAppCacheUtils != null) {
|
||||
sAppCacheUtils.clearBatteryHistory();
|
||||
LogUtils.d(TAG, "clearBatteryHistory():电池历史数据已清除");
|
||||
LogUtils.d(TAG, "clearBatteryHistory: Success");
|
||||
} else {
|
||||
LogUtils.w(TAG, "clearBatteryHistory():AppCacheUtils未初始化,清除失败");
|
||||
LogUtils.w(TAG, "clearBatteryHistory: Failed, sAppCacheUtils is null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动清理所有缓存(带严格权限控制,仅主动调用生效)
|
||||
* 极致强制缓存策略下,仅提供手动清理入口,永不自动调用
|
||||
* 手动清理所有缓存(仅主动调用生效)
|
||||
*/
|
||||
public static void manualClearAllCache() {
|
||||
LogUtils.w(TAG, String.format("%s 手动清理缓存调用(极致强制缓存策略下,需谨慎使用)", CACHE_PROTECT_TAG));
|
||||
// 清理Bitmap缓存
|
||||
LogUtils.w(CACHE_PROTECT_TAG, "manualClearAllCache() called - Manual trigger only");
|
||||
|
||||
if (sBitmapCacheUtils != null) {
|
||||
sBitmapCacheUtils.clearAllCache();
|
||||
LogUtils.d(TAG, String.format("%s Bitmap缓存已手动清理", CACHE_PROTECT_TAG));
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "manualClearAllCache: Bitmap cache cleared");
|
||||
}
|
||||
// 清理视图控件缓存(仅清除静态引用,不销毁实例)
|
||||
|
||||
// 仅置空引用,不销毁实例(符合极致缓存策略)
|
||||
if (sMemoryCachedBackgroundView != null) {
|
||||
LogUtils.d(TAG, String.format("%s 视图控件缓存实例保持,仅清除静态引用", CACHE_PROTECT_TAG));
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "manualClearAllCache: View cache reference cleared");
|
||||
sMemoryCachedBackgroundView = null;
|
||||
}
|
||||
LogUtils.w(TAG, String.format("%s 手动清理缓存完成(部分缓存实例仍可能保留在内存中)", CACHE_PROTECT_TAG));
|
||||
|
||||
LogUtils.w(CACHE_PROTECT_TAG, "manualClearAllCache: Manual cleanup finished");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视图控件缓存实例(非通用:仅通过App实例调用,避免全局直接访问)
|
||||
* 获取视图缓存实例
|
||||
*/
|
||||
public MemoryCachedBackgroundView getMemoryCachedBackgroundView() {
|
||||
LogUtils.d(TAG, "getMemoryCachedBackgroundView() 调用 | 视图控件缓存实例获取");
|
||||
if (sMemoryCachedBackgroundView == null) {
|
||||
LogUtils.w(TAG, "getMemoryCachedBackgroundView():视图控件缓存实例未初始化,返回null");
|
||||
}
|
||||
LogUtils.d(TAG, "getMemoryCachedBackgroundView() called | Current: " + sMemoryCachedBackgroundView);
|
||||
return sMemoryCachedBackgroundView;
|
||||
}
|
||||
|
||||
// ===================== 生命周期方法区(按执行顺序排序) =====================
|
||||
/**
|
||||
* 发送通知消息
|
||||
*/
|
||||
public static void notifyMessage(String title, String content) {
|
||||
LogUtils.d(TAG, "notifyMessage() called with: title = [" + title + "], content = [" + content + "]");
|
||||
|
||||
boolean canSend = isDebugging() && sApp != null && sNotificationManagerUtils != null;
|
||||
if (canSend) {
|
||||
NotificationMessage message = new NotificationMessage(title, content, "");
|
||||
sNotificationManagerUtils.showMessageNotification(sApp, message);
|
||||
LogUtils.d(TAG, "notifyMessage: Sent successfully");
|
||||
} else {
|
||||
LogUtils.d(TAG, "notifyMessage: Send failed, conditions not met");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 生命周期方法 ====================
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate() called | Initializing application...");
|
||||
|
||||
sApp = this;
|
||||
LogUtils.d(TAG, "onCreate() 应用启动,开始初始化");
|
||||
|
||||
// 初始化调试模式
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
LogUtils.d(TAG, String.format("onCreate() 调试模式=%b", BuildConfig.DEBUG));
|
||||
LogUtils.d(TAG, "onCreate: Debug mode = " + BuildConfig.DEBUG);
|
||||
|
||||
// 初始化基础工具
|
||||
// 初始化核心组件
|
||||
initBaseTools();
|
||||
//App.notifyMessage(TAG, "onCreate() 应用启动中。。。");
|
||||
// 初始化工具类实例(核心:极致强制缓存,永不销毁)
|
||||
initUtils();
|
||||
// 初始化广播接收器
|
||||
initReceiver();
|
||||
|
||||
LogUtils.d(TAG, "onCreate() 应用初始化完成,极致强制缓存策略已启用");
|
||||
LogUtils.d(TAG, "onCreate: Application initialization completed. Force-cache strategy active.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
LogUtils.d(TAG, "onTerminate() 应用终止,开始释放非缓存资源");
|
||||
LogUtils.d(TAG, "onTerminate() called | Releasing non-cache resources");
|
||||
|
||||
// 释放Toast工具
|
||||
// 仅释放非缓存资源
|
||||
ToastUtils.release();
|
||||
LogUtils.d(TAG, "onTerminate():Toast工具已释放");
|
||||
// 释放通知工具
|
||||
releaseNotificationManager();
|
||||
// 释放广播接收器
|
||||
releaseReceiver();
|
||||
|
||||
// 核心修改:应用终止时也不清理缓存,保持静态实例
|
||||
LogUtils.w(TAG, String.format("%s 应用终止,极致强制缓存策略生效,不清理任何缓存", CACHE_PROTECT_TAG));
|
||||
|
||||
LogUtils.d(TAG, "onTerminate() 非缓存资源释放完成,缓存实例保持");
|
||||
// 核心策略:不清理缓存
|
||||
LogUtils.w(CACHE_PROTECT_TAG, "onTerminate: Force-cache active, caches remain in memory");
|
||||
LogUtils.d(TAG, "onTerminate: Non-cache resources released");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
// 极致强制缓存:禁止任何缓存清理操作,仅记录日志
|
||||
LogUtils.w(TAG, String.format("%s onTrimMemory() 调用 | 内存等级level=%d | 极致强制保持所有缓存",
|
||||
CACHE_PROTECT_TAG, level));
|
||||
// 记录详细缓存状态,不执行任何清理
|
||||
LogUtils.w(CACHE_PROTECT_TAG, "onTrimMemory() called with level: " + level + " | Ignoring, caches protected");
|
||||
logDetailedCacheStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
// 极致强制缓存:低内存时也不清理任何缓存
|
||||
LogUtils.w(TAG, String.format("%s onLowMemory() 调用 | 极致强制保持所有缓存", CACHE_PROTECT_TAG));
|
||||
// 记录详细缓存状态,不执行任何清理
|
||||
LogUtils.w(CACHE_PROTECT_TAG, "onLowMemory() called | Force-cache active, no cleanup performed");
|
||||
logDetailedCacheStatus();
|
||||
}
|
||||
|
||||
// ===================== 私有初始化方法区(按初始化顺序排序) =====================
|
||||
// ==================== 私有初始化方法 ====================
|
||||
|
||||
/**
|
||||
* 初始化基础工具(Activity管理、Toast)
|
||||
* 初始化基础工具类
|
||||
*/
|
||||
private void initBaseTools() {
|
||||
LogUtils.d(TAG, "initBaseTools() 开始初始化基础工具");
|
||||
LogUtils.d(TAG, "initBaseTools: Starting...");
|
||||
WinBoLLActivityManager.init(this);
|
||||
ToastUtils.init(this);
|
||||
LogUtils.d(TAG, "initBaseTools() 基础工具初始化完成");
|
||||
sNotificationManagerUtils = new NotificationManagerUtils(this);
|
||||
LogUtils.d(TAG, "initBaseTools() 工具类初始化完成,极致强制缓存策略已生效");
|
||||
LogUtils.d(TAG, "initBaseTools: Completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化工具类实例(核心:极致强制缓存,一旦初始化永不销毁)
|
||||
* 初始化核心工具与缓存(极致强制驻留)
|
||||
*/
|
||||
private void initUtils() {
|
||||
LogUtils.d(TAG, "initUtils() 开始初始化工具类,启用极致强制缓存策略");
|
||||
//App.notifyMessage(TAG, "initUtils() 开始初始化工具类,启用极致强制缓存策略");
|
||||
LogUtils.d(TAG, "initUtils: Starting with force-cache strategy");
|
||||
|
||||
// 1. 配置与基础缓存
|
||||
sAppConfigUtils = getAppConfigUtils(this);
|
||||
sAppCacheUtils = getAppCacheUtils(this);
|
||||
|
||||
// 2. 资源与Bitmap缓存
|
||||
sBackgroundSourceUtils = BackgroundSourceUtils.getInstance(this);
|
||||
sBackgroundSourceUtils.loadSettings();
|
||||
// 极致强制初始化Bitmap缓存工具(必初始化,永不销毁)
|
||||
sBitmapCacheUtils = BitmapCacheUtils.getInstance();
|
||||
LogUtils.d(TAG, "initUtils() Bitmap缓存工具已初始化(极致强制保持,永不销毁)");
|
||||
LogUtils.d(TAG, "initUtils: Resource & Bitmap tools initialized (Permanent)");
|
||||
|
||||
// 极致强制初始化视图控件缓存工具(必初始化,永不销毁)
|
||||
// 3. 视图缓存
|
||||
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getLastInstance(this);
|
||||
if (sMemoryCachedBackgroundView == null) {
|
||||
//App.notifyMessage(TAG, "sMemoryCachedBackgroundView == null");
|
||||
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getInstance(this, sBackgroundSourceUtils.getCurrentBackgroundBean(), true);
|
||||
LogUtils.d(TAG, "initUtils: View cache - New instance created");
|
||||
}
|
||||
LogUtils.d(TAG, "initUtils() 视图控件缓存工具已初始化(极致强制保持,永不销毁)");
|
||||
LogUtils.d(TAG, "initUtils: View cache initialized (Permanent)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化广播接收器
|
||||
* 注册全局广播接收器
|
||||
*/
|
||||
private void initReceiver() {
|
||||
LogUtils.d(TAG, "initReceiver() 开始初始化广播接收器");
|
||||
LogUtils.d(TAG, "initReceiver: Starting...");
|
||||
mGlobalReceiver = new GlobalApplicationReceiver(this);
|
||||
mGlobalReceiver.registerAction();
|
||||
LogUtils.d(TAG, "initReceiver() 广播接收器注册完成");
|
||||
LogUtils.d(TAG, "initReceiver: Completed");
|
||||
}
|
||||
|
||||
// ===================== 私有释放方法区(按资源重要性排序) =====================
|
||||
// ==================== 私有释放方法 ====================
|
||||
|
||||
/**
|
||||
* 释放广播接收器资源
|
||||
* 释放广播接收器
|
||||
*/
|
||||
private void releaseReceiver() {
|
||||
LogUtils.d(TAG, "releaseReceiver() 开始释放广播接收器");
|
||||
LogUtils.d(TAG, "releaseReceiver: Starting...");
|
||||
if (mGlobalReceiver != null) {
|
||||
mGlobalReceiver.unregisterAction();
|
||||
mGlobalReceiver = null;
|
||||
LogUtils.d(TAG, "releaseReceiver() 广播接收器资源已释放");
|
||||
} else {
|
||||
LogUtils.d(TAG, "releaseReceiver() 广播接收器未初始化,无需释放");
|
||||
LogUtils.d(TAG, "releaseReceiver: Completed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放通知管理工具资源
|
||||
* 释放通知管理器
|
||||
*/
|
||||
private void releaseNotificationManager() {
|
||||
LogUtils.d(TAG, "releaseNotificationManager() 开始释放通知工具");
|
||||
LogUtils.d(TAG, "releaseNotificationManager: Starting...");
|
||||
if (sNotificationManagerUtils != null) {
|
||||
sNotificationManagerUtils.release();
|
||||
sNotificationManagerUtils = null;
|
||||
LogUtils.d(TAG, "releaseNotificationManager() 通知工具资源已释放");
|
||||
} else {
|
||||
LogUtils.d(TAG, "releaseNotificationManager() 通知工具未初始化,无需释放");
|
||||
LogUtils.d(TAG, "releaseNotificationManager: Completed");
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 私有工具方法区(辅助功能) =====================
|
||||
// ==================== 私有辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 记录详细缓存状态(用于调试,监控极致强制缓存效果)
|
||||
* 记录当前缓存详细状态(用于调试监控)
|
||||
*/
|
||||
private void logDetailedCacheStatus() {
|
||||
LogUtils.d(TAG, "logDetailedCacheStatus() 开始记录详细缓存状态");
|
||||
// Bitmap缓存状态
|
||||
LogUtils.d(TAG, "logDetailedCacheStatus: Reporting cache state");
|
||||
|
||||
if (sBitmapCacheUtils != null) {
|
||||
LogUtils.d(TAG, String.format("%s Bitmap缓存工具实例有效(极致强制保持)", CACHE_PROTECT_TAG));
|
||||
// 假设BitmapCacheUtils有获取缓存数量的方法
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "Cache Status: BitmapCache [Valid]");
|
||||
try {
|
||||
int cacheCount = sBitmapCacheUtils.getCacheCount();
|
||||
LogUtils.d(TAG, String.format("%s Bitmap缓存数量=%d", CACHE_PROTECT_TAG, cacheCount));
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "Cache Detail: Bitmap Count = " + sBitmapCacheUtils.getCacheCount());
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, String.format("%s Bitmap缓存数量获取失败(不影响缓存)| 异常信息=%s",
|
||||
CACHE_PROTECT_TAG, e.getMessage()));
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "Cache Detail: Failed to get bitmap count - " + e.getMessage());
|
||||
}
|
||||
}
|
||||
// 视图控件缓存状态
|
||||
if (sMemoryCachedBackgroundView != null) {
|
||||
LogUtils.d(TAG, String.format("%s 视图控件缓存工具实例有效(极致强制保持)", CACHE_PROTECT_TAG));
|
||||
// 记录视图实例总数
|
||||
int viewInstanceCount = MemoryCachedBackgroundView.getInstanceCount();
|
||||
LogUtils.d(TAG, String.format("%s 视图控件实例总数=%d", CACHE_PROTECT_TAG, viewInstanceCount));
|
||||
}
|
||||
LogUtils.d(TAG, "logDetailedCacheStatus() 详细缓存状态记录完成,所有缓存均极致强制保持");
|
||||
}
|
||||
|
||||
public static void notifyMessage(String title, String content) {
|
||||
if (isDebugging() && sApp != null && sNotificationManagerUtils != null) {
|
||||
NotificationMessage message = new NotificationMessage(title, content, "");
|
||||
sNotificationManagerUtils.showMessageNotification(sApp, message);
|
||||
if (sMemoryCachedBackgroundView != null) {
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "Cache Status: ViewCache [Valid]");
|
||||
LogUtils.d(CACHE_PROTECT_TAG, "Cache Detail: View Instance Count = " + MemoryCachedBackgroundView.getInstanceCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,16 +26,18 @@ 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.models.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.models.BatteryStyle;
|
||||
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
import cc.winboll.studio.powerbell.unittest.MainUnitTest2Activity;
|
||||
import cc.winboll.studio.powerbell.unittest.MainUnitTestActivity;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ImageUtils;
|
||||
import cc.winboll.studio.powerbell.utils.PermissionUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ServiceUtils;
|
||||
import cc.winboll.studio.powerbell.views.BatteryStyleView;
|
||||
import cc.winboll.studio.powerbell.views.MainContentView;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
/**
|
||||
* 应用核心主活动
|
||||
@@ -45,37 +47,42 @@ import cc.winboll.studio.libappbase.ToastUtils;
|
||||
*/
|
||||
public class MainActivity extends WinBoLLActivity implements MainContentView.OnViewActionListener {
|
||||
|
||||
// ======================== 静态常量(置顶统一,抽离魔法值)========================
|
||||
// ======================== 静态常量区(抽离魔法值,按功能分类)========================
|
||||
public static final String TAG = "MainActivity";
|
||||
private static final int REQUEST_BACKGROUND_SETTINGS_ACTIVITY = 1001;
|
||||
public static final String EXTRA_ISRELOAD_BACKGROUNDVIEW = "EXTRA_ISRELOAD_BACKGROUNDVIEW";
|
||||
public static final String EXTRA_ISRELOAD_ACCENTCOLOR = "EXTRA_ISRELOAD_ACCENTCOLOR";
|
||||
private static final long DELAY_LOAD_NON_CRITICAL = 500L;
|
||||
|
||||
// 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_UPDATE_SERVICE_SWITCH = 3;
|
||||
private static final int MSG_UPDATE_BATTERYDRAWABLE = 4;
|
||||
|
||||
// ======================== 静态成员(全局共享,严格管控生命周期)========================
|
||||
// ======================== 静态成员区(全局共享,管控生命周期)========================
|
||||
private static MainActivity sMainActivity;
|
||||
private static Handler sGlobalHandler;
|
||||
|
||||
// ======================== 工具类实例(单例,避免重复初始化)========================
|
||||
// ======================== 工具类实例区(单例化,避免重复初始化)========================
|
||||
private PermissionUtils mPermissionUtils;
|
||||
private AppConfigUtils mAppConfigUtils;
|
||||
private BackgroundSourceUtils mBgSourceUtils;
|
||||
|
||||
// ======================== 应用核心实例 =========================
|
||||
// ======================== 应用核心实例区 =========================
|
||||
private App mApplication;
|
||||
private MainContentView mMainContentView;
|
||||
private ControlCenterServiceBean mServiceControlBean;
|
||||
|
||||
// ======================== 基础视图组件 =========================
|
||||
// ======================== 基础视图组件区 =========================
|
||||
private Toolbar mToolbar;
|
||||
private ViewStub mAdsViewStub;
|
||||
private ADsBannerView mADsBannerView;
|
||||
private Drawable mFrameDrawable;
|
||||
private Menu mMenu;
|
||||
|
||||
// ======================== 生命周期方法(按系统调用顺序排列)========================
|
||||
// ======================== 生命周期方法区(按系统调用顺序排列)========================
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
@@ -89,7 +96,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, String.format("onCreate() | savedInstanceState=%s", savedInstanceState));
|
||||
LogUtils.d(TAG, "onCreate() 调用 | savedInstanceState: " + savedInstanceState);
|
||||
|
||||
initGlobalHandler();
|
||||
setContentView(R.layout.activity_main);
|
||||
@@ -98,20 +105,32 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
initCriticalView();
|
||||
initCoreUtilsAsync();
|
||||
loadNonCriticalViewDelayed();
|
||||
|
||||
// 处理首次启动参数
|
||||
handleReloadBackgroundParam(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
LogUtils.d(TAG, "onNewIntent() 调用 | intent: " + intent);
|
||||
// 关键:更新Activity持有的Intent,确保后续获取最新值
|
||||
setIntent(intent);
|
||||
// 统一处理刷新背景参数
|
||||
handleReloadBackgroundParam(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, String.format("onPostCreate() | savedInstanceState=%s", savedInstanceState));
|
||||
LogUtils.d(TAG, "onPostCreate() 调用 | savedInstanceState: " + savedInstanceState);
|
||||
mPermissionUtils.startPermissionRequest(this);
|
||||
LogUtils.d(TAG, "onPostCreate: 发起权限申请");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LogUtils.d(TAG, "onResume()");
|
||||
LogUtils.d(TAG, "onResume() 调用");
|
||||
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs(this);
|
||||
@@ -122,13 +141,13 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
LogUtils.d(TAG, "onPause()");
|
||||
LogUtils.d(TAG, "onPause() 调用");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy()");
|
||||
LogUtils.d(TAG, "onDestroy() 调用");
|
||||
|
||||
// 释放广告资源
|
||||
if (mADsBannerView != null) {
|
||||
@@ -142,7 +161,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
mMainContentView = null;
|
||||
LogUtils.d(TAG, "onDestroy: 核心视图资源已释放");
|
||||
}
|
||||
// 销毁Handler,防止内存泄漏
|
||||
// 销毁Handler防止内存泄漏
|
||||
if (sGlobalHandler != null) {
|
||||
sGlobalHandler.removeCallbacksAndMessages(null);
|
||||
sGlobalHandler = null;
|
||||
@@ -154,7 +173,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
mFrameDrawable = null;
|
||||
LogUtils.d(TAG, "onDestroy: 框架Drawable已释放");
|
||||
}
|
||||
// 置空所有引用
|
||||
// 置空所有引用,消除内存泄漏风险
|
||||
sMainActivity = null;
|
||||
mPermissionUtils = null;
|
||||
mAppConfigUtils = null;
|
||||
@@ -169,21 +188,23 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, String.format("onActivityResult() | requestCode=%d | resultCode=%d | data=%s",
|
||||
requestCode, resultCode, data));
|
||||
LogUtils.d(TAG, "onActivityResult() 调用 | requestCode: " + requestCode + " | resultCode: " + resultCode + " | data: " + data);
|
||||
mPermissionUtils.handlePermissionRequest(this, requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQUEST_BACKGROUND_SETTINGS_ACTIVITY && sGlobalHandler != null) {
|
||||
sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
|
||||
LogUtils.d(TAG, "onActivityResult: 发送背景加载消息");
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 菜单与导航方法 ========================
|
||||
// ======================== 菜单与导航方法区 ========================
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
LogUtils.d(TAG, String.format("onCreateOptionsMenu() | menu=%s", menu));
|
||||
LogUtils.d(TAG, "onCreateOptionsMenu() 调用 | menu: " + menu);
|
||||
mMenu = menu;
|
||||
AESThemeUtil.inflateMenu(this, menu);
|
||||
|
||||
// 调试模式加载测试菜单
|
||||
if (App.isDebugging()) {
|
||||
DevelopUtils.inflateMenu(this, menu);
|
||||
getMenuInflater().inflate(R.menu.toolbar_unittest, mMenu);
|
||||
@@ -195,14 +216,21 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
LogUtils.d(TAG, String.format("onOptionsItemSelected() | itemId=%d", item.getItemId()));
|
||||
LogUtils.d(TAG, "onOptionsItemSelected() 调用 | itemId: " + item.getItemId());
|
||||
// 主题切换处理
|
||||
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
|
||||
recreate();
|
||||
Intent mainIntent = new Intent(MainActivity.this, MainActivity.class);
|
||||
mainIntent.putExtra(MainActivity.EXTRA_ISRELOAD_BACKGROUNDVIEW, true);
|
||||
mainIntent.putExtra(MainActivity.EXTRA_ISRELOAD_ACCENTCOLOR, true);
|
||||
startActivity(mainIntent);
|
||||
return true;
|
||||
}
|
||||
// 开发者功能处理
|
||||
if (DevelopUtils.onDevelopItemSelected(this, item)) {
|
||||
return true;
|
||||
}
|
||||
// 菜单点击事件分发
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
@@ -234,7 +262,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
public void setupToolbar() {
|
||||
super.setupToolbar();
|
||||
LogUtils.d(TAG, "setupToolbar()");
|
||||
LogUtils.d(TAG, "setupToolbar() 调用");
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
LogUtils.d(TAG, "setupToolbar: 已隐藏返回按钮");
|
||||
@@ -243,35 +271,37 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
LogUtils.d(TAG, "onBackPressed()");
|
||||
LogUtils.d(TAG, "onBackPressed() 调用");
|
||||
moveTaskToBack(true);
|
||||
LogUtils.d(TAG, "onBackPressed: 应用已退至后台");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
LogUtils.d(TAG, String.format("dispatchKeyEvent() | event=%s", event));
|
||||
LogUtils.d(TAG, "dispatchKeyEvent() 调用 | event: " + event);
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
// ======================== 核心初始化方法 ========================
|
||||
// ======================== 核心初始化方法区 ========================
|
||||
private void initPermissionUtils() {
|
||||
LogUtils.d(TAG, "initPermissionUtils()");
|
||||
LogUtils.d(TAG, "initPermissionUtils() 调用");
|
||||
mPermissionUtils = PermissionUtils.getInstance();
|
||||
LogUtils.d(TAG, "initPermissionUtils: 权限工具类已初始化");
|
||||
}
|
||||
|
||||
private void initGlobalHandler() {
|
||||
LogUtils.d(TAG, "initGlobalHandler()");
|
||||
LogUtils.d(TAG, "initGlobalHandler() 调用");
|
||||
if (sGlobalHandler == null) {
|
||||
sGlobalHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
// Activity已销毁则跳过消息处理
|
||||
if (sMainActivity == null || sMainActivity.isFinishing() || sMainActivity.isDestroyed()) {
|
||||
LogUtils.w(TAG, String.format("handleMessage: Activity已销毁,跳过消息 | what=%d", msg.what));
|
||||
LogUtils.w(TAG, "handleMessage: Activity已销毁,跳过消息 | what: " + msg.what);
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, String.format("handleMessage() | what=%d", msg.what));
|
||||
LogUtils.d(TAG, "handleMessage() 调用 | what: " + msg.what);
|
||||
|
||||
switch (msg.what) {
|
||||
case MSG_RELOAD_APPCONFIG:
|
||||
sMainActivity.updateViewData();
|
||||
@@ -279,7 +309,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
case MSG_CURRENTVALUEBATTERY:
|
||||
if (sMainActivity.mMainContentView != null) {
|
||||
sMainActivity.mMainContentView.updateCurrentBattery(msg.arg1);
|
||||
LogUtils.d(TAG, String.format("handleMessage: 更新当前电量 | value=%d", msg.arg1));
|
||||
LogUtils.d(TAG, "handleMessage: 更新当前电量 | value: " + msg.arg1);
|
||||
}
|
||||
break;
|
||||
case MSG_LOAD_BACKGROUND:
|
||||
@@ -287,6 +317,9 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
break;
|
||||
case MSG_UPDATE_SERVICE_SWITCH:
|
||||
sMainActivity.updateServiceSwitchUI();
|
||||
break;
|
||||
case MSG_UPDATE_BATTERYDRAWABLE:
|
||||
sMainActivity.updateBatteryDrawable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -298,14 +331,14 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
private void initMainContentView() {
|
||||
LogUtils.d(TAG, "initMainContentView()");
|
||||
LogUtils.d(TAG, "initMainContentView() 调用");
|
||||
View rootView = findViewById(android.R.id.content);
|
||||
mMainContentView = new MainContentView(this, rootView, this);
|
||||
LogUtils.d(TAG, "initMainContentView: 核心内容视图已初始化");
|
||||
}
|
||||
|
||||
private void initCriticalView() {
|
||||
LogUtils.d(TAG, "initCriticalView()");
|
||||
LogUtils.d(TAG, "initCriticalView() 调用");
|
||||
sMainActivity = this;
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
@@ -318,11 +351,11 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
private void initCoreUtilsAsync() {
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync()");
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync() 调用");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, String.format("initCoreUtilsAsync: 异步线程启动 | threadId=%d", Thread.currentThread().getId()));
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 异步线程启动 | threadId: " + Thread.currentThread().getId());
|
||||
mApplication = (App) getApplication();
|
||||
mAppConfigUtils = AppConfigUtils.getInstance(getApplicationContext());
|
||||
mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity());
|
||||
@@ -338,8 +371,8 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
// 根据配置启停服务
|
||||
final boolean isServiceEnable = mServiceControlBean.isEnableService();
|
||||
final boolean isServiceAlive = ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName());
|
||||
LogUtils.d(TAG, String.format("initCoreUtilsAsync: 服务配置状态 | isServiceEnable=%b | isServiceAlive=%b",
|
||||
isServiceEnable, isServiceAlive));
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 服务配置状态 | isServiceEnable: " + isServiceEnable + " | isServiceAlive: " + isServiceAlive);
|
||||
|
||||
if (isServiceEnable && !isServiceAlive) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -366,7 +399,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
LogUtils.w(TAG, "initCoreUtilsAsync: Activity已销毁,跳过UI更新");
|
||||
return;
|
||||
}
|
||||
// 加载框架背景(适配API23+)
|
||||
// 适配API30,兼容低版本Drawable加载
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mFrameDrawable = getResources().getDrawable(R.drawable.bg_frame, getTheme());
|
||||
} else {
|
||||
@@ -383,7 +416,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
private void loadNonCriticalViewDelayed() {
|
||||
LogUtils.d(TAG, String.format("loadNonCriticalViewDelayed() | 延迟时长=%dms", DELAY_LOAD_NON_CRITICAL));
|
||||
LogUtils.d(TAG, "loadNonCriticalViewDelayed() 调用 | 延迟时长: " + DELAY_LOAD_NON_CRITICAL + "ms");
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -396,9 +429,34 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}, DELAY_LOAD_NON_CRITICAL);
|
||||
}
|
||||
|
||||
// ======================== 视图操作方法 ========================
|
||||
// ======================== 视图操作方法区 ========================
|
||||
private void handleReloadBackgroundParam(Intent intent) {
|
||||
LogUtils.d(TAG, "handleReloadBackgroundParam() 调用 | intent: " + intent);
|
||||
if (intent == null) {
|
||||
LogUtils.d(TAG, "handleReloadBackgroundParam: Intent 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isReloadAccentColor = intent.getBooleanExtra(EXTRA_ISRELOAD_ACCENTCOLOR, false);
|
||||
if (isReloadAccentColor) {
|
||||
App.sBackgroundSourceUtils.getCurrentBackgroundBean().setPixelColor(ImageUtils.getColorAccent(this));
|
||||
App.sBackgroundSourceUtils.saveSettings();
|
||||
}
|
||||
|
||||
boolean isReloadBackgroundView = intent.getBooleanExtra(EXTRA_ISRELOAD_BACKGROUNDVIEW, false);
|
||||
if (isReloadBackgroundView) {
|
||||
LogUtils.d(TAG, "handleReloadBackgroundParam: 接收到刷新背景视图指令");
|
||||
reloadBackgroundView();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadBackgroundView() {
|
||||
LogUtils.d(TAG, "reloadBackgroundView() 调用");
|
||||
mMainContentView.reloadBackgroundView();
|
||||
}
|
||||
|
||||
private void loadAdsView() {
|
||||
LogUtils.d(TAG, "loadAdsView()");
|
||||
LogUtils.d(TAG, "loadAdsView() 调用");
|
||||
if (mAdsViewStub == null) {
|
||||
LogUtils.e(TAG, "loadAdsView: 广告ViewStub为空,加载失败");
|
||||
return;
|
||||
@@ -413,7 +471,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
private void updateViewData() {
|
||||
LogUtils.d(TAG, "updateViewData()");
|
||||
LogUtils.d(TAG, "updateViewData() 调用");
|
||||
if (mMainContentView == null || mFrameDrawable == null) {
|
||||
LogUtils.e(TAG, "updateViewData: 核心视图或框架背景为空,更新失败");
|
||||
return;
|
||||
@@ -422,15 +480,25 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
LogUtils.d(TAG, "updateViewData: 视图数据已更新");
|
||||
}
|
||||
|
||||
void updateBatteryDrawable() {
|
||||
BatteryStyle batteryStyle = BatteryStyleView.getSavedBatteryStyle(this);
|
||||
mMainContentView.updateBatteryDrawable(batteryStyle);
|
||||
}
|
||||
|
||||
public static void sendUpdateBatteryDrawableMessage() {
|
||||
if (sGlobalHandler != null) {
|
||||
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_BATTERYDRAWABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadBackground() {
|
||||
LogUtils.d(TAG, "reloadBackground()");
|
||||
LogUtils.d(TAG, "reloadBackground() 调用");
|
||||
if (mMainContentView == null || mBgSourceUtils == null) {
|
||||
LogUtils.e(TAG, "reloadBackground: 核心视图或背景工具类为空,加载失败");
|
||||
return;
|
||||
}
|
||||
BackgroundBean currentBgBean = mBgSourceUtils.getCurrentBackgroundBean();
|
||||
if (currentBgBean != null) {
|
||||
//ToastUtils.show("currentBgBean");
|
||||
mMainContentView.backgroundView.loadByBackgroundBean(currentBgBean, true);
|
||||
LogUtils.d(TAG, "reloadBackground: 已加载自定义背景");
|
||||
} else {
|
||||
@@ -440,7 +508,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
private void updateServiceSwitchUI() {
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI()");
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI() 调用");
|
||||
if (mMainContentView == null || mServiceControlBean == null) {
|
||||
LogUtils.e(TAG, "updateServiceSwitchUI: 核心视图或服务配置为空,更新失败");
|
||||
return;
|
||||
@@ -449,12 +517,12 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
mMainContentView.setServiceSwitchEnabled(false);
|
||||
mMainContentView.setServiceSwitchChecked(configEnabled);
|
||||
mMainContentView.setServiceSwitchEnabled(true);
|
||||
LogUtils.d(TAG, String.format("updateServiceSwitchUI: 服务开关已更新 | 状态=%b", configEnabled));
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI: 服务开关已更新 | 状态: " + configEnabled);
|
||||
}
|
||||
|
||||
// ======================== 服务与线程管理方法 ========================
|
||||
// ======================== 服务与线程管理方法区 ========================
|
||||
private void toggleServiceEnableState(boolean isEnable) {
|
||||
LogUtils.d(TAG, String.format("toggleServiceEnableState() | 目标状态=%b", isEnable));
|
||||
LogUtils.d(TAG, "toggleServiceEnableState() 调用 | 目标状态: " + isEnable);
|
||||
if (mServiceControlBean == null) {
|
||||
LogUtils.e(TAG, "toggleServiceEnableState: 服务配置为空,切换失败");
|
||||
return;
|
||||
@@ -477,9 +545,9 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
|
||||
}
|
||||
|
||||
// ======================== 页面跳转方法 ========================
|
||||
// ======================== 页面跳转方法区 ========================
|
||||
private void startAboutActivity() {
|
||||
LogUtils.d(TAG, "startAboutActivity()");
|
||||
LogUtils.d(TAG, "startAboutActivity() 调用");
|
||||
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
|
||||
APPInfo appInfo = genDefaultAppInfo();
|
||||
aboutIntent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
|
||||
@@ -487,16 +555,16 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
LogUtils.d(TAG, "startAboutActivity: 关于页面已启动");
|
||||
}
|
||||
|
||||
// ======================== 消息发送方法 ========================
|
||||
// ======================== 消息发送方法区 ========================
|
||||
private void notifyServiceAppConfigChange() {
|
||||
LogUtils.d(TAG, "notifyServiceAppConfigChange()");
|
||||
LogUtils.d(TAG, "notifyServiceAppConfigChange() 调用");
|
||||
ControlCenterService.sendAppConfigStatusUpdateMessage(this);
|
||||
reloadAppConfig();
|
||||
LogUtils.d(TAG, "notifyServiceAppConfigChange: 服务配置已通知更新");
|
||||
}
|
||||
|
||||
public static void reloadAppConfig() {
|
||||
LogUtils.d(TAG, "reloadAppConfig()");
|
||||
LogUtils.d(TAG, "reloadAppConfig() 调用");
|
||||
if (sGlobalHandler != null) {
|
||||
sGlobalHandler.sendEmptyMessage(MSG_RELOAD_APPCONFIG);
|
||||
LogUtils.d(TAG, "reloadAppConfig: 配置重载消息已发送");
|
||||
@@ -506,7 +574,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
public static void sendCurrentBatteryValueMessage(int value) {
|
||||
LogUtils.d(TAG, String.format("sendCurrentBatteryValueMessage() | 电量=%d", value));
|
||||
LogUtils.d(TAG, "sendCurrentBatteryValueMessage() 调用 | 电量: " + value);
|
||||
if (sGlobalHandler != null) {
|
||||
Message msg = sGlobalHandler.obtainMessage(MSG_CURRENTVALUEBATTERY);
|
||||
msg.arg1 = value;
|
||||
@@ -517,9 +585,9 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 辅助工具方法 ========================
|
||||
// ======================== 辅助工具方法区 ========================
|
||||
private APPInfo genDefaultAppInfo() {
|
||||
LogUtils.d(TAG, "genDefaultAppInfo()");
|
||||
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||
String branchName = "powerbell";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName(getString(R.string.app_name));
|
||||
@@ -536,34 +604,34 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
// ======================== MainContentView 事件回调 ========================
|
||||
// ======================== MainContentView 事件回调区 ========================
|
||||
@Override
|
||||
public void onChargeReminderSwitchChanged(boolean isChecked) {
|
||||
LogUtils.d(TAG, String.format("onChargeReminderSwitchChanged() | isChecked=%b", isChecked));
|
||||
LogUtils.d(TAG, "onChargeReminderSwitchChanged() 调用 | isChecked: " + isChecked);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsageReminderSwitchChanged(boolean isChecked) {
|
||||
LogUtils.d(TAG, String.format("onUsageReminderSwitchChanged() | isChecked=%b", isChecked));
|
||||
LogUtils.d(TAG, "onUsageReminderSwitchChanged() 调用 | isChecked: " + isChecked);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceSwitchChanged(boolean isChecked) {
|
||||
LogUtils.d(TAG, String.format("onServiceSwitchChanged() | isChecked=%b", isChecked));
|
||||
LogUtils.d(TAG, "onServiceSwitchChanged() 调用 | isChecked: " + isChecked);
|
||||
toggleServiceEnableState(isChecked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChargeReminderProgressChanged(int progress) {
|
||||
LogUtils.d(TAG, String.format("onChargeReminderProgressChanged() | progress=%d", progress));
|
||||
LogUtils.d(TAG, "onChargeReminderProgressChanged() 调用 | progress: " + progress);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsageReminderProgressChanged(int progress) {
|
||||
LogUtils.d(TAG, String.format("onUsageReminderProgressChanged() | progress=%d", progress));
|
||||
LogUtils.d(TAG, "onUsageReminderProgressChanged() 调用 | progress: " + progress);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.core.content.FileProvider;
|
||||
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.dialogs.BackgroundPicturePreviewDialog;
|
||||
import cc.winboll.studio.powerbell.dialogs.ColorPaletteDialog;
|
||||
@@ -46,16 +47,19 @@ import java.io.File;
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
*/
|
||||
public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
// ====================== 常量定义(按功能分类)======================
|
||||
// ====================== 常量定义(按功能分类排序)======================
|
||||
public static final String TAG = "BackgroundSettingsActivity";
|
||||
|
||||
// 系统版本常量
|
||||
private static final int SDK_VERSION_TIRAMISU = 33;
|
||||
// 请求码(按功能分组)
|
||||
|
||||
// 请求码(按功能分组,从小到大排序)
|
||||
public static final int REQUEST_SELECT_PICTURE = 0;
|
||||
public static final int REQUEST_TAKE_PHOTO = 1;
|
||||
public static final int REQUEST_CROP_IMAGE = 2;
|
||||
private static final int REQUEST_PIXELPICKER = 1001;
|
||||
private static final int REQUEST_CAMERA_PERMISSION = 1004;
|
||||
|
||||
// Bitmap解析常量
|
||||
private static final int BITMAP_MAX_SIZE = 2048;
|
||||
private static final int BITMAP_MAX_SAMPLE_SIZE = 16;
|
||||
@@ -64,9 +68,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
// 工具类实例
|
||||
private BackgroundSourceUtils mBgSourceUtils;
|
||||
private BitmapCacheUtils mBitmapCache;
|
||||
|
||||
// 视图组件
|
||||
private Toolbar mToolbar;
|
||||
private BackgroundView mBackgroundView;
|
||||
|
||||
// 状态标记(volatile保证多线程可见性)
|
||||
private volatile boolean isCommitSettings = false;
|
||||
private volatile boolean isPreviewBackgroundChanged = false;
|
||||
@@ -90,16 +96,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
|
||||
// 初始化核心组件
|
||||
initCoreComponents();
|
||||
// 初始化界面与事件
|
||||
// 初始化Toolbar与点击事件
|
||||
initToolbar();
|
||||
initClickListeners();
|
||||
LogUtils.d(TAG, "界面与事件绑定完成");
|
||||
LogUtils.d(TAG, "onCreate() 视图与事件绑定完成");
|
||||
|
||||
// 处理分享意图或初始化预览
|
||||
handleIntentOrPreview();
|
||||
|
||||
// 初始化预览环境并刷新
|
||||
initPreviewEnvironment();
|
||||
|
||||
LogUtils.d(TAG, "onCreate() 初始化完成");
|
||||
}
|
||||
|
||||
@@ -122,13 +128,14 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
// 此时已获取真实宽高
|
||||
int width = mBackgroundView.getWidth();
|
||||
int height = mBackgroundView.getHeight();
|
||||
LogUtils.d(TAG, String.format("onPostCreate() 获取视图尺寸 | width=%d | height=%d", width, height));
|
||||
if (width > 0 && height > 0) {
|
||||
AppConfigUtils appConfigUtils = AppConfigUtils.getInstance(BackgroundSettingsActivity.this);
|
||||
appConfigUtils.loadAppConfig();
|
||||
appConfigUtils.mAppConfigBean.setDefaultFrameWidth(width);
|
||||
appConfigUtils.mAppConfigBean.setDefaultFrameHeight(height);
|
||||
appConfigUtils.saveAppConfig();
|
||||
LogUtils.d(TAG, String.format("保存默认相框尺寸 | width=%d | height=%d", width, height));
|
||||
LogUtils.d(TAG, "onPostCreate() 保存默认相框尺寸成功");
|
||||
doubleRefreshPreview();
|
||||
}
|
||||
}
|
||||
@@ -138,24 +145,27 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, String.format("onActivityResult() | requestCode=%d | resultCode=%d", requestCode, resultCode));
|
||||
LogUtils.d(TAG, String.format("onActivityResult() | requestCode=%d | resultCode=%d | data=%s",
|
||||
requestCode, resultCode, data != null ? data.toString() : "null"));
|
||||
|
||||
try {
|
||||
if (resultCode != RESULT_OK) {
|
||||
LogUtils.d(TAG, "结果非RESULT_OK,执行取消逻辑");
|
||||
LogUtils.d(TAG, String.format("onActivityResult() 操作取消 | requestCode=%d", requestCode));
|
||||
handleOperationCancelOrFail();
|
||||
return;
|
||||
}
|
||||
handleActivityResult(requestCode, data);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, String.format("onActivityResult() 异常 | requestCode=%d | 异常信息=%s", requestCode, e.getMessage()));
|
||||
LogUtils.e(TAG, String.format("onActivityResult() 异常 | requestCode=%d | 异常信息=%s",
|
||||
requestCode, e.getMessage()));
|
||||
ToastUtils.show("操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
LogUtils.d(TAG, String.format("finish() | isCommitSettings=%b | isPreviewBackgroundChanged=%b", isCommitSettings, isPreviewBackgroundChanged));
|
||||
LogUtils.d(TAG, String.format("finish() | isCommitSettings=%b | isPreviewBackgroundChanged=%b",
|
||||
isCommitSettings, isPreviewBackgroundChanged));
|
||||
if (isCommitSettings) {
|
||||
super.finish();
|
||||
} else {
|
||||
@@ -167,7 +177,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
LogUtils.d(TAG, String.format("onRequestPermissionsResult() | requestCode=%d | 权限数量=%d", requestCode, permissions.length));
|
||||
LogUtils.d(TAG, String.format("onRequestPermissionsResult() | requestCode=%d | 权限数量=%d | 结果数量=%d",
|
||||
requestCode, permissions.length, grantResults.length));
|
||||
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||
handleCameraPermissionResult(grantResults);
|
||||
}
|
||||
@@ -881,7 +892,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
* @param fileSize 文件大小
|
||||
*/
|
||||
private void handleCropFailure(boolean isFileExist, boolean isFileReadable, long fileSize) {
|
||||
LogUtils.e(TAG, String.format("handleCropFailure() | 裁剪失败,文件状态:存在=%b,可读=%b,大小=%d", isFileExist, isFileReadable, fileSize));
|
||||
LogUtils.e(TAG, String.format("handleCropFailure() | 裁剪失败,文件状态:存在=%b,可读=%b,大小=%d",
|
||||
isFileExist, isFileReadable, fileSize));
|
||||
handleOperationCancelOrFail();
|
||||
}
|
||||
|
||||
@@ -937,12 +949,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity {
|
||||
mBgSourceUtils.commitPreviewSourceToCurrent();
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
Intent mainIntent = new Intent(BackgroundSettingsActivity.this, MainActivity.class);
|
||||
mainIntent.putExtra(MainActivity.EXTRA_ISRELOAD_BACKGROUNDVIEW, true);
|
||||
startActivity(mainIntent);
|
||||
LogUtils.d(TAG, "handleFinishConfirmation() | 确认设置,启动MainActivity并刷新背景");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNo() {
|
||||
isCommitSettings = true;
|
||||
finish();
|
||||
LogUtils.d(TAG, "handleFinishConfirmation() | 取消设置,关闭页面");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -34,32 +34,34 @@ import java.util.Map;
|
||||
/**
|
||||
* 电池报告页面,统计应用24小时运行时长与电池消耗情况
|
||||
* 支持应用搜索、累计耗电计算、电池广播监听,适配 API30
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/10/22 13:21
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
*/
|
||||
public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
// ======================== 静态常量 =========================
|
||||
// ======================== 静态常量(按功能分类) =========================
|
||||
public static final String TAG = "BatteryReportActivity";
|
||||
private static final long ONE_DAY_MS = 24 * 3600 * 1000; // 24小时毫秒数
|
||||
private static final long ONE_MINUTE_MS = 60 * 1000; // 1分钟毫秒数
|
||||
private static final long ONE_DAY_MS = 24 * 3600 * 1000; // 24小时毫秒数
|
||||
private static final long ONE_MINUTE_MS = 60 * 1000; // 1分钟毫秒数
|
||||
|
||||
// ======================== 成员变量 =========================
|
||||
// ======================== 成员变量(按依赖优先级+功能分类) =========================
|
||||
// UI组件
|
||||
private Toolbar mToolbar;
|
||||
private RecyclerView rvBatteryReport;
|
||||
private EditText etSearch;
|
||||
|
||||
// 数据与适配器
|
||||
private BatteryReportAdapter adapter;
|
||||
private List<AppBatteryModel> dataList = new ArrayList<AppBatteryModel>();
|
||||
private List<AppBatteryModel> filteredList = new ArrayList<AppBatteryModel>();
|
||||
private List<AppBatteryModel> dataList = new ArrayList<>();
|
||||
private List<AppBatteryModel> filteredList = new ArrayList<>();
|
||||
|
||||
// 电池相关
|
||||
private BroadcastReceiver batteryReceiver;
|
||||
private int batteryCapacity = 5400; // 电池容量(mAh)
|
||||
private float lastBatteryPercent = 100.0f;
|
||||
private long lastCheckTime = System.currentTimeMillis();
|
||||
private int batteryCapacity = 5400; // 电池容量(mAh)
|
||||
private float lastBatteryPercent = 100.0f; // 上次电池百分比
|
||||
private long lastCheckTime = System.currentTimeMillis(); // 上次检查时间戳
|
||||
|
||||
// 缓存相关
|
||||
private Map<String, Long> appRunTimeCache = new HashMap<String, Long>();
|
||||
private Map<String, String> packageToAppNameCache = new HashMap<String, String>();
|
||||
private Map<String, Long> appRunTimeCache = new HashMap<>();
|
||||
private Map<String, String> packageToAppNameCache = new HashMap<>();
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
// ======================== 接口实现方法 =========================
|
||||
@@ -73,7 +75,7 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// ======================== 生命周期方法 =========================
|
||||
// ======================== 生命周期方法(按执行顺序排列) =========================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -84,6 +86,7 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
initView();
|
||||
// 初始化PackageManager
|
||||
mPackageManager = getPackageManager();
|
||||
LogUtils.d(TAG, "【onCreate】基础组件初始化完成");
|
||||
|
||||
// 权限检查(Java7 传统条件判断)
|
||||
if (!hasUsageStatsPermission(this)) {
|
||||
@@ -100,15 +103,15 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
updateAppRunTimeToModel();
|
||||
calculateInitial24hTotalConsumption();
|
||||
filteredList.addAll(dataList);
|
||||
LogUtils.d(TAG, "【onCreate】数据初始化完成,原始数据量:" + dataList.size());
|
||||
|
||||
// 初始化适配器
|
||||
adapter = new BatteryReportAdapter(this, filteredList, mPackageManager, packageToAppNameCache);
|
||||
rvBatteryReport.setAdapter(adapter);
|
||||
LogUtils.d(TAG, "【onCreate】适配器初始化完成,数据量:" + filteredList.size());
|
||||
LogUtils.d(TAG, "【onCreate】适配器初始化完成,过滤后数据量:" + filteredList.size());
|
||||
|
||||
// 绑定搜索监听
|
||||
// 绑定搜索监听 + 注册电池广播
|
||||
bindSearchListener();
|
||||
// 注册电池广播
|
||||
registerBatteryReceiver();
|
||||
|
||||
LogUtils.d(TAG, "【onCreate】BatteryReportActivity 初始化完成");
|
||||
@@ -156,8 +159,9 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
LogUtils.d(TAG, "【bindSearchListener】搜索关键词变化:" + s.toString());
|
||||
filterAppsByPackageAndName(s.toString());
|
||||
String keyword = s.toString().trim();
|
||||
LogUtils.d(TAG, "【bindSearchListener】搜索关键词变化:" + keyword);
|
||||
filterAppsByPackageAndName(keyword);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,11 +184,14 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
float dropPercent = lastBatteryPercent - currentPercent;
|
||||
long duration = System.currentTimeMillis() - lastCheckTime;
|
||||
LogUtils.d(TAG, "【电池广播】电池消耗:" + dropPercent + "%,时长:" + formatRunTime(duration));
|
||||
|
||||
// 更新运行时长并计算耗电
|
||||
appRunTimeCache = getAppRunTime();
|
||||
updateAppRunTimeToModel();
|
||||
calculateSingleConsumptionAndAccumulate(dropPercent, appRunTimeCache);
|
||||
}
|
||||
|
||||
// 刷新记录
|
||||
lastBatteryPercent = currentPercent;
|
||||
lastCheckTime = System.currentTimeMillis();
|
||||
}
|
||||
@@ -229,15 +236,12 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
private void loadAllAppPackage() {
|
||||
List<ApplicationInfo> appList = mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA);
|
||||
dataList.clear();
|
||||
|
||||
LogUtils.d(TAG, "【loadAllAppPackage】开始加载应用包名列表,共找到" + appList.size() + "个应用");
|
||||
|
||||
for (ApplicationInfo appInfo : appList) {
|
||||
String packageName = appInfo.packageName;
|
||||
// 初始化:单次耗电=0,累计耗电=0,运行时长=0
|
||||
dataList.add(new AppBatteryModel(packageName, 0.0f, 0.0f, 0));
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "【loadAllAppPackage】应用包名列表加载完成,共添加" + dataList.size() + "个包名");
|
||||
}
|
||||
|
||||
@@ -253,7 +257,6 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
String appName = getAppNameByPackage(packageName);
|
||||
packageToAppNameCache.put(packageName, appName);
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "【preCacheAllAppNames】预缓存完成,共缓存" + packageToAppNameCache.size() + "个应用名称");
|
||||
}
|
||||
|
||||
@@ -263,6 +266,7 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
* @return 应用名称,获取失败返回包名
|
||||
*/
|
||||
private String getAppNameByPackage(String packageName) {
|
||||
LogUtils.v(TAG, "【getAppNameByPackage】查询包名:" + packageName);
|
||||
try {
|
||||
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0);
|
||||
return mPackageManager.getApplicationLabel(appInfo).toString();
|
||||
@@ -296,7 +300,7 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
* @return 应用包名-运行时长(ms)映射
|
||||
*/
|
||||
private Map<String, Long> getAppRunTime() {
|
||||
Map<String, Long> runTimeMap = new HashMap<String, Long>();
|
||||
Map<String, Long> runTimeMap = new HashMap<>();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
try {
|
||||
android.app.usage.UsageStatsManager manager =
|
||||
@@ -341,7 +345,6 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
String packageName = model.getPackageName();
|
||||
Long app24hRunTime = appRunTimeCache.getOrDefault(packageName, 0L);
|
||||
|
||||
// 计算占比与累计耗电
|
||||
float ratio = (total24hRunTime > 0) ? (float) app24hRunTime / total24hRunTime : 0;
|
||||
float initialTotalConsumption = batteryCapacity * ratio;
|
||||
model.setTotalConsumption(initialTotalConsumption);
|
||||
@@ -351,11 +354,12 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算单次耗电(赋值给consumption)+ 累加至累计耗电(totalConsumption = totalConsumption + consumption)
|
||||
* 计算单次耗电(赋值给consumption)+ 累加至累计耗电
|
||||
* @param dropPercent 电池下降百分比
|
||||
* @param runTimeMap 应用运行时长映射
|
||||
* @param runTimeMap 应用运行时长映射
|
||||
*/
|
||||
private void calculateSingleConsumptionAndAccumulate(float dropPercent, Map<String, Long> runTimeMap) {
|
||||
LogUtils.d(TAG, "【calculateSingleConsumptionAndAccumulate】开始计算,电池下降百分比:" + dropPercent);
|
||||
long totalSingleRunTime = 0;
|
||||
// 1. 计算本次电池下降期间的总运行时长
|
||||
for (Map.Entry<String, Long> entry : runTimeMap.entrySet()) {
|
||||
@@ -363,28 +367,25 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
}
|
||||
LogUtils.d(TAG, "【calculateSingleConsumptionAndAccumulate】本次电池下降总运行时长:" + formatRunTime(totalSingleRunTime));
|
||||
|
||||
// 2. 遍历计算每个应用的“单次耗电”并“累加至累计”
|
||||
// 2. 遍历计算每个应用的单次耗电并累加
|
||||
for (AppBatteryModel model : dataList) {
|
||||
String packageName = model.getPackageName();
|
||||
Long appSingleRunTime = runTimeMap.getOrDefault(packageName, 0L);
|
||||
|
||||
// 步骤1:计算本次单次耗电
|
||||
float ratio = (totalSingleRunTime > 0) ? (float) appSingleRunTime / totalSingleRunTime : 0;
|
||||
float singleConsumption = batteryCapacity * dropPercent / 100 * ratio;
|
||||
model.setConsumption(singleConsumption);
|
||||
|
||||
// 步骤2:累加单次耗电到累计耗电
|
||||
// 累加至累计耗电
|
||||
float newTotalConsumption = model.getTotalConsumption() + singleConsumption;
|
||||
model.setTotalConsumption(newTotalConsumption);
|
||||
|
||||
// 同步运行时长
|
||||
model.setRunTime(appSingleRunTime);
|
||||
|
||||
LogUtils.v(TAG, String.format("【calculateSingleConsumptionAndAccumulate】应用包%s:单次耗电%.1f mAh,累计耗电%.1f mAh",
|
||||
packageName, singleConsumption, newTotalConsumption));
|
||||
}
|
||||
|
||||
// 3. 按累计耗电排序(从高到低)
|
||||
// 3. 按累计耗电降序排序
|
||||
Collections.sort(dataList, new Comparator<AppBatteryModel>() {
|
||||
@Override
|
||||
public int compare(AppBatteryModel m1, AppBatteryModel m2) {
|
||||
@@ -392,8 +393,8 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 重新应用过滤并刷新列表
|
||||
filterAppsByPackageAndName(etSearch.getText().toString());
|
||||
// 4. 重新过滤并刷新列表
|
||||
filterAppsByPackageAndName(etSearch.getText().toString().trim());
|
||||
LogUtils.d(TAG, "【calculateSingleConsumptionAndAccumulate】单次耗电计算与累加完成,列表已刷新");
|
||||
}
|
||||
|
||||
@@ -414,9 +415,7 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
String appName = packageToAppNameCache.get(packageName);
|
||||
String appNameLower = appName.toLowerCase();
|
||||
|
||||
boolean isMatched = packageNameLower.contains(lowerKeyword)
|
||||
|| appNameLower.contains(lowerKeyword);
|
||||
|
||||
boolean isMatched = packageNameLower.contains(lowerKeyword) || appNameLower.contains(lowerKeyword);
|
||||
if (isMatched) {
|
||||
filteredList.add(model);
|
||||
}
|
||||
@@ -456,7 +455,6 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
* - consumption:单次耗电(两次电池广播间的消耗)
|
||||
* - totalConsumption:累计耗电(24小时初始化值+后续单次累加)
|
||||
* - runTime:运行时长(ms)
|
||||
* - packageName:应用包名
|
||||
*/
|
||||
public static class AppBatteryModel {
|
||||
private String packageName; // 应用包名(核心标识)
|
||||
@@ -519,6 +517,7 @@ public class BatteryReportActivity extends WinBoLLActivity implements IWinBoLLAc
|
||||
this.mDataList = dataList;
|
||||
this.mPm = pm;
|
||||
this.mPackageToNameCache = packageToNameCache;
|
||||
LogUtils.d(TAG, "【BatteryReportAdapter】适配器构造完成,数据量:" + dataList.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,19 +22,20 @@ import java.util.ArrayList;
|
||||
/**
|
||||
* 电池记录清理页面,支持滑动清理记录、切换记录显示格式
|
||||
* 适配 API30,基于 Java7 开发
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
*/
|
||||
public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
// ======================== 静态常量 =========================
|
||||
// ======================== 静态常量(按功能分类) =========================
|
||||
public static final String TAG = "ClearRecordActivity";
|
||||
private static final String TOAST_MSG_CLEAR_SUCCESS = "The APP battery record is cleaned.";
|
||||
|
||||
// ======================== 成员变量 =========================
|
||||
// ======================== 成员变量(按依赖优先级+功能分类) =========================
|
||||
// UI组件
|
||||
private Toolbar mToolbar;
|
||||
private TextView mtvRecordText;
|
||||
private TextView tvAOHPCTCSeekBarMSG;
|
||||
private AOHPCTCSeekBar aOHPCTCSeekBar;
|
||||
|
||||
// 应用与配置
|
||||
private App mApplication;
|
||||
private boolean mIsShowRecordWithEnter = false; // 记录是否带换行显示
|
||||
@@ -50,7 +51,7 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
return TAG;
|
||||
}
|
||||
|
||||
// ======================== 生命周期方法 =========================
|
||||
// ======================== 生命周期方法(按执行顺序排列) =========================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -59,11 +60,11 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
|
||||
// 初始化应用实例
|
||||
mApplication = (App) getApplication();
|
||||
// 初始化UI组件
|
||||
LogUtils.d(TAG, "【onCreate】应用实例初始化完成");
|
||||
|
||||
// 初始化核心逻辑
|
||||
initView();
|
||||
// 初始化滑动清理控件
|
||||
initSeekBar();
|
||||
// 初始化记录显示文本
|
||||
initRecordText();
|
||||
|
||||
LogUtils.d(TAG, "【onCreate】ClearRecordActivity 初始化完成");
|
||||
@@ -75,7 +76,7 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
*/
|
||||
private void initView() {
|
||||
// 初始化Toolbar
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
|
||||
@@ -83,29 +84,30 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【导航栏】点击返回");
|
||||
LogUtils.d(TAG, "【导航栏】点击返回按钮,关闭当前页面");
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化显示文本组件
|
||||
tvAOHPCTCSeekBarMSG = findViewById(R.id.activityclearrecordTextView1);
|
||||
mtvRecordText = findViewById(R.id.activityclearrecordTextView2);
|
||||
tvAOHPCTCSeekBarMSG = (TextView) findViewById(R.id.activityclearrecordTextView1);
|
||||
mtvRecordText = (TextView) findViewById(R.id.activityclearrecordTextView2);
|
||||
tvAOHPCTCSeekBarMSG.setText(R.string.msg_AOHPCTCSeekBar_ClearRecord);
|
||||
|
||||
LogUtils.d(TAG, "【initView】UI组件初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化滑动清理控件
|
||||
* 初始化滑动清理控件,设置回调监听
|
||||
*/
|
||||
private void initSeekBar() {
|
||||
aOHPCTCSeekBar = findViewById(R.id.activityclearrecordAOHPCTCSeekBar1);
|
||||
aOHPCTCSeekBar = (AOHPCTCSeekBar) findViewById(R.id.activityclearrecordAOHPCTCSeekBar1);
|
||||
aOHPCTCSeekBar.setThumb(getDrawable(R.drawable.cursor_pointer));
|
||||
aOHPCTCSeekBar.setThumbOffset(0);
|
||||
aOHPCTCSeekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
|
||||
@Override
|
||||
public void onOHPCommit() {
|
||||
LogUtils.d(TAG, "【onOHPCommit】滑动清理触发");
|
||||
LogUtils.d(TAG, "【onOHPCommit】滑动清理触发,开始执行记录清理逻辑");
|
||||
// 清理电池历史记录
|
||||
mApplication.clearBatteryHistory();
|
||||
// 发送广播更新前台通知
|
||||
@@ -114,10 +116,11 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
initRecordText();
|
||||
// 提示清理成功
|
||||
ToastUtils.show(TOAST_MSG_CLEAR_SUCCESS);
|
||||
LogUtils.d(TAG, "【onOHPCommit】电池记录清理完成,已发送更新广播");
|
||||
LogUtils.d(TAG, "【onOHPCommit】电池记录清理完成,已发送前台通知更新广播");
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "【initSeekBar】滑动清理控件初始化完成");
|
||||
|
||||
LogUtils.d(TAG, "【initSeekBar】滑动清理控件初始化完成,回调监听已绑定");
|
||||
}
|
||||
|
||||
// ======================== 业务逻辑方法 =========================
|
||||
@@ -131,20 +134,20 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
// 判空处理:避免空列表导致异常
|
||||
if (listBatteryInfo == null || listBatteryInfo.isEmpty()) {
|
||||
szRecordText = getString(R.string.msg_no_battery_record);
|
||||
LogUtils.d(TAG, "【initRecordText】无电池记录数据");
|
||||
LogUtils.d(TAG, "【initRecordText】无电池记录数据,显示空记录提示文本");
|
||||
} else {
|
||||
// 根据配置切换显示格式
|
||||
if (mIsShowRecordWithEnter) {
|
||||
szRecordText = StringUtils.formatPCMListStringWithEnter(listBatteryInfo);
|
||||
LogUtils.d(TAG, "【initRecordText】使用带换行格式显示记录,数量:" + listBatteryInfo.size());
|
||||
LogUtils.d(TAG, String.format("【initRecordText】使用带换行格式显示记录,记录数量:%d", listBatteryInfo.size()));
|
||||
} else {
|
||||
szRecordText = StringUtils.formatPCMListString(listBatteryInfo);
|
||||
LogUtils.d(TAG, "【initRecordText】使用无换行格式显示记录,数量:" + listBatteryInfo.size());
|
||||
LogUtils.d(TAG, String.format("【initRecordText】使用无换行格式显示记录,记录数量:%d", listBatteryInfo.size()));
|
||||
}
|
||||
}
|
||||
|
||||
mtvRecordText.setText(szRecordText);
|
||||
LogUtils.d(TAG, "【initRecordText】记录显示文本初始化完成");
|
||||
LogUtils.d(TAG, "【initRecordText】记录显示文本刷新完成");
|
||||
}
|
||||
|
||||
// ======================== 事件回调方法 =========================
|
||||
@@ -155,7 +158,7 @@ public class ClearRecordActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
public void onShowRecordWithEnter(View view) {
|
||||
Switch swShowRecordWithEnter = (Switch) view;
|
||||
mIsShowRecordWithEnter = swShowRecordWithEnter.isChecked();
|
||||
LogUtils.d(TAG, "【onShowRecordWithEnter】记录显示格式切换,带换行:" + mIsShowRecordWithEnter);
|
||||
LogUtils.d(TAG, String.format("【onShowRecordWithEnter】记录显示格式切换,带换行显示:%b", mIsShowRecordWithEnter));
|
||||
// 刷新记录显示
|
||||
initRecordText();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.views.AToolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
@@ -49,7 +50,7 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
|
||||
// ======================== 成员变量 =========================
|
||||
// UI组件
|
||||
private AToolbar mAToolbar;
|
||||
private Toolbar mToolbar;
|
||||
private ImageView imageView;
|
||||
private TextView infoText;
|
||||
private ViewGroup imageContainer;
|
||||
@@ -112,7 +113,6 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
* 初始化所有UI组件
|
||||
*/
|
||||
private void initView() {
|
||||
mAToolbar = (AToolbar) findViewById(R.id.toolbar);
|
||||
imageView = findViewById(R.id.imageView);
|
||||
infoText = findViewById(R.id.infoText);
|
||||
imageContainer = findViewById(R.id.imageContainer);
|
||||
@@ -125,17 +125,24 @@ public class PixelPickerActivity extends WinBoLLActivity implements IWinBoLLActi
|
||||
* 初始化工具栏,设置导航与标题
|
||||
*/
|
||||
private void initToolbar() {
|
||||
setActionBar(mAToolbar);
|
||||
mAToolbar.setSubtitle(R.string.subtitle_activity_pixelpicker);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化");
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
if (mToolbar == null) {
|
||||
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
|
||||
return;
|
||||
}
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "【导航栏】点击返回");
|
||||
LogUtils.d(TAG, "导航栏 点击返回按钮");
|
||||
finish();
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
|
||||
LogUtils.d(TAG, "initToolbar() 配置完成");
|
||||
}
|
||||
|
||||
// ======================== 业务逻辑方法 =========================
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
package cc.winboll.studio.powerbell.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 应用设置窗口,提供应用配置项的统一入口
|
||||
@@ -44,6 +56,13 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
|
||||
|
||||
// 初始化工具栏
|
||||
initToolbar();
|
||||
|
||||
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
|
||||
if (thoughtfulServiceBean == null) {
|
||||
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||
}
|
||||
((CheckBox)findViewById(R.id.activitysettingsCheckBox1)).setChecked(thoughtfulServiceBean.isEnableUsePowerTts());
|
||||
((CheckBox)findViewById(R.id.activitysettingsCheckBox2)).setChecked(thoughtfulServiceBean.isEnableChargeTts());
|
||||
|
||||
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化完成");
|
||||
}
|
||||
@@ -70,5 +89,96 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
|
||||
});
|
||||
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
|
||||
}
|
||||
|
||||
public void onCheckTTSDrawOverlaysPermission(View view) {
|
||||
canDrawOverlays();
|
||||
}
|
||||
|
||||
public void onEnableChargeTts(View view) {
|
||||
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
|
||||
if (thoughtfulServiceBean == null) {
|
||||
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||
}
|
||||
thoughtfulServiceBean.setIsEnableChargeTts(((CheckBox)view).isChecked());
|
||||
ThoughtfulServiceBean.saveBean(this, thoughtfulServiceBean);
|
||||
}
|
||||
|
||||
public void onEnableUsePowerTts(View view) {
|
||||
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
|
||||
if (thoughtfulServiceBean == null) {
|
||||
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||
}
|
||||
thoughtfulServiceBean.setIsEnableUsePowerTts(((CheckBox)view).isChecked());
|
||||
ThoughtfulServiceBean.saveBean(this, thoughtfulServiceBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 悬浮窗权限检查与请求
|
||||
*/
|
||||
void canDrawOverlays() {
|
||||
LogUtils.d(TAG, "onCanDrawOverlays: 检查悬浮窗权限");
|
||||
// API6.0+校验权限
|
||||
if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
|
||||
LogUtils.d(TAG, "onCanDrawOverlays: 未开启悬浮窗权限,发起请求");
|
||||
showDrawOverlayRequestDialog();
|
||||
} else {
|
||||
ToastUtils.show("悬浮窗权限已开启");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示悬浮窗权限请求对话框
|
||||
*/
|
||||
private void showDrawOverlayRequestDialog() {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setTitle("权限请求")
|
||||
.setMessage("为保证通话监听功能正常,需开启悬浮窗权限")
|
||||
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
jumpToDrawOverlaySettings();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("稍后", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
// 解决对话框焦点问题
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setFlags(
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
|
||||
}
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转悬浮窗权限设置页面(反射适配低版本)
|
||||
*/
|
||||
private void jumpToDrawOverlaySettings() {
|
||||
LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转悬浮窗权限设置");
|
||||
try {
|
||||
// 反射获取设置页面Action(避免高版本API依赖)
|
||||
Class<?> settingsClazz = Settings.class;
|
||||
Field actionField = settingsClazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
|
||||
String action = (String) actionField.get(null);
|
||||
|
||||
// 跳转当前应用权限设置页
|
||||
Intent intent = new Intent(action);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "jumpToDrawOverlaySettings: 跳转权限设置失败", e);
|
||||
Toast.makeText(this, "请手动在设置中开启悬浮窗权限", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
|
||||
@@ -135,6 +136,7 @@ public class BackgroundPicturePreviewDialog extends Dialog {
|
||||
|
||||
// 解析Uri为文件路径
|
||||
String szSrcImage = UriUtils.getFilePathFromUri(mContext, mUriRecivedPicture);
|
||||
//App.notifyMessage(TAG, "szSrcImage : " + szSrcImage);
|
||||
if (TextUtils.isEmpty(szSrcImage)) {
|
||||
LogUtils.w(TAG, "【previewRecivedPicture】解析的文件路径为空");
|
||||
Toast.makeText(mContext, TOAST_MSG_EMPTY_FILE, Toast.LENGTH_SHORT).show();
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package cc.winboll.studio.powerbell.models;
|
||||
|
||||
/**
|
||||
* 电池绘制样式枚举 (单选选项)
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
*/
|
||||
public enum BatteryStyle {
|
||||
ENERGY_STYLE, // 能量样式
|
||||
ZEBRA_STYLE, // 条纹样式
|
||||
POINT_STYLE // 点阵样式
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
*/
|
||||
public class ControlCenterServiceBean extends BaseBean implements Parcelable, Serializable {
|
||||
// ====================== 静态常量(置顶统一管理,避免魔法值) ======================
|
||||
private static final long serialVersionUID = 1L; // Serializable 必备,保障反序列化兼容
|
||||
//private static final long serialVersionUID = 1L; // Serializable 必备,保障反序列化兼容
|
||||
private static final String TAG = "ControlCenterServiceBean";
|
||||
private static final String JSON_FIELD_IS_ENABLE_SERVICE = "isEnableService"; // JSON 字段常量,避免硬编码
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package cc.winboll.studio.powerbell.models;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* TTS 语音播放文本内容实体类
|
||||
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||
* 特性:实现序列化接口,支持跨页面/进程传递,属性默认值初始化
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/12/29 19:13
|
||||
*/
|
||||
public class TTSSpeakTextBean implements Serializable {
|
||||
|
||||
// ====================================== 常量区 - 置顶排序 ======================================
|
||||
/** 日志TAG 瞬态修饰,不参与序列化,减少序列化体积 */
|
||||
transient public static final String TAG = "TTSSpeakTextBean";
|
||||
|
||||
// ====================================== 成员属性区 - 业务属性排序 ======================================
|
||||
/** 延迟播放时长 单位:毫秒,默认值0:无延迟播放 */
|
||||
public int mnDelay = 0;
|
||||
/** TTS语音播放文本内容,默认值空字符串:防止空指针 */
|
||||
public String mszSpeakContent = "";
|
||||
|
||||
// ====================================== 构造方法区 - 无参+有参 完整实现 ======================================
|
||||
/**
|
||||
* 无参构造方法
|
||||
* Java7序列化规范必备 + 兼容反射实例化场景
|
||||
*/
|
||||
public TTSSpeakTextBean() {
|
||||
LogUtils.d(TAG, "【无参构造】TTSSpeakTextBean 实例化,使用默认值 | 延迟:" + mnDelay + " | 文本:" + mszSpeakContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有参构造方法【主构造】
|
||||
* @param nDelay 延迟播放时长(ms)
|
||||
* @param szSpeakContent 语音播放文本内容
|
||||
*/
|
||||
public TTSSpeakTextBean(int nDelay, String szSpeakContent) {
|
||||
LogUtils.d(TAG, "【有参构造】TTSSpeakTextBean 实例化,入参 | 延迟:" + nDelay + " | 文本:" + szSpeakContent);
|
||||
this.mnDelay = nDelay;
|
||||
this.mszSpeakContent = szSpeakContent;
|
||||
LogUtils.d(TAG, "【有参构造】赋值完成 | 最终延迟:" + this.mnDelay + " | 最终文本:" + this.mszSpeakContent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package cc.winboll.studio.powerbell.models;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/12/29 20:59
|
||||
* @Describe 贴心服务配置实体类 (适配API30 / Java7)
|
||||
*/
|
||||
public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Serializable {
|
||||
|
||||
// ====================== 常量区 - 置顶统一管理 ======================
|
||||
public static final String TAG = ThoughtfulServiceBean.class.getSimpleName();
|
||||
private static final long serialVersionUID = 1L; // Serializable 序列化兼容必备
|
||||
// JSON序列化字段常量 杜绝硬编码
|
||||
public static final String JSON_FIELD_IS_ENABLE_CHARGE_TTS = "isEnableChargeTts";
|
||||
public static final String JSON_FIELD_IS_ENABLE_USE_POWER_TTS = "isEnableUsePowerTts";
|
||||
|
||||
// ====================== 核心成员变量 - 私有封装 ======================
|
||||
private boolean isEnableChargeTts = false; // 是否启用 充电TTS贴心语音服务
|
||||
private boolean isEnableUsePowerTts = false; // 是否启用 用电TTS贴心语音服务
|
||||
|
||||
// ====================== Parcelable 静态创建器 (API30标准写法 必须public static final) ======================
|
||||
public static final Creator<ThoughtfulServiceBean> CREATOR = new Creator<ThoughtfulServiceBean>() {
|
||||
@Override
|
||||
public ThoughtfulServiceBean createFromParcel(Parcel source) {
|
||||
return new ThoughtfulServiceBean(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThoughtfulServiceBean[] newArray(int size) {
|
||||
LogUtils.d(TAG, "newArray: 初始化数组,size = " + size);
|
||||
return new ThoughtfulServiceBean[size];
|
||||
}
|
||||
};
|
||||
|
||||
// ====================== 构造方法区 (无参+有参+Parcel构造 全覆盖) ======================
|
||||
/**
|
||||
* 无参构造 - JSON解析/反射实例化 必备
|
||||
*/
|
||||
public ThoughtfulServiceBean() {
|
||||
LogUtils.d(TAG, "ThoughtfulServiceBean: 无参构造,初始化默认禁用所有TTS服务");
|
||||
}
|
||||
|
||||
/**
|
||||
* 全参构造 - 手动配置所有服务状态
|
||||
* @param isEnableChargeTts 充电TTS服务开关
|
||||
* @param isEnableUsePowerTts 用电TTS服务开关
|
||||
*/
|
||||
public ThoughtfulServiceBean(boolean isEnableChargeTts, boolean isEnableUsePowerTts) {
|
||||
this.isEnableChargeTts = isEnableChargeTts;
|
||||
this.isEnableUsePowerTts = isEnableUsePowerTts;
|
||||
LogUtils.d(TAG, "ThoughtfulServiceBean: 全参构造 | isEnableChargeTts=" + isEnableChargeTts + " | isEnableUsePowerTts=" + isEnableUsePowerTts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcel反序列化构造 - Parcelable必备 私有私有化
|
||||
*/
|
||||
private ThoughtfulServiceBean(Parcel in) {
|
||||
this.isEnableChargeTts = in.readByte() != 0;
|
||||
this.isEnableUsePowerTts = in.readByte() != 0;
|
||||
LogUtils.d(TAG, "ThoughtfulServiceBean: Parcel构造解析完成 | isEnableChargeTts=" + isEnableChargeTts + " | isEnableUsePowerTts=" + isEnableUsePowerTts);
|
||||
}
|
||||
|
||||
// ====================== Getter/Setter 方法区 (封装成员变量 统一访问) ======================
|
||||
public boolean isEnableChargeTts() {
|
||||
return isEnableChargeTts;
|
||||
}
|
||||
|
||||
public void setIsEnableChargeTts(boolean isEnableChargeTts) {
|
||||
LogUtils.d(TAG, "setIsEnableChargeTts: 旧值=" + this.isEnableChargeTts + " | 新值=" + isEnableChargeTts);
|
||||
this.isEnableChargeTts = isEnableChargeTts;
|
||||
}
|
||||
|
||||
public boolean isEnableUsePowerTts() {
|
||||
return isEnableUsePowerTts;
|
||||
}
|
||||
|
||||
public void setIsEnableUsePowerTts(boolean isEnableUsePowerTts) {
|
||||
LogUtils.d(TAG, "setIsEnableUsePowerTts: 旧值=" + this.isEnableUsePowerTts + " | 新值=" + isEnableUsePowerTts);
|
||||
this.isEnableUsePowerTts = isEnableUsePowerTts;
|
||||
}
|
||||
|
||||
// ====================== 重写父类 BaseBean 核心方法 (JSON序列化/反序列化 业务核心) ======================
|
||||
@Override
|
||||
public String getName() {
|
||||
String className = ThoughtfulServiceBean.class.getName();
|
||||
LogUtils.d(TAG, "getName: 返回当前实体类名 = " + className);
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON序列化 - 写入所有字段 适配持久化/网络传输
|
||||
*/
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name(JSON_FIELD_IS_ENABLE_CHARGE_TTS).value(this.isEnableChargeTts);
|
||||
jsonWriter.name(JSON_FIELD_IS_ENABLE_USE_POWER_TTS).value(this.isEnableUsePowerTts);
|
||||
LogUtils.d(TAG, "writeThisToJsonWriter: JSON序列化完成,所有TTS服务状态已写入");
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON反序列化 - 读取字段生成实体 适配数据恢复
|
||||
*/
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
ThoughtfulServiceBean bean = new ThoughtfulServiceBean();
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String fieldName = jsonReader.nextName();
|
||||
switch (fieldName) {
|
||||
case JSON_FIELD_IS_ENABLE_CHARGE_TTS:
|
||||
bean.setIsEnableChargeTts(jsonReader.nextBoolean());
|
||||
break;
|
||||
case JSON_FIELD_IS_ENABLE_USE_POWER_TTS:
|
||||
bean.setIsEnableUsePowerTts(jsonReader.nextBoolean());
|
||||
break;
|
||||
default:
|
||||
jsonReader.skipValue();
|
||||
LogUtils.w(TAG, "readBeanFromJsonReader: 跳过未知JSON字段 = " + fieldName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
jsonReader.endObject();
|
||||
LogUtils.d(TAG, "readBeanFromJsonReader: JSON反序列化完成,生成实体对象");
|
||||
return bean;
|
||||
}
|
||||
|
||||
// ====================== 实现 Parcelable 接口方法 (组件间Intent传递必备 API30/Java7完美适配) ======================
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0; // 无文件描述符等特殊内容,固定返回0即可
|
||||
}
|
||||
|
||||
/**
|
||||
* Parcel序列化 - boolean用byte存储(Java7/API30标准写法 避免兼容性问题)
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeByte((byte) (isEnableChargeTts ? 1 : 0));
|
||||
dest.writeByte((byte) (isEnableUsePowerTts ? 1 : 0));
|
||||
LogUtils.d(TAG, "writeToParcel: Parcel序列化完成,所有TTS服务状态已写入");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ 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;
|
||||
import cc.winboll.studio.powerbell.services.ThoughtfulService;
|
||||
|
||||
/**
|
||||
* 控制中心广播接收器
|
||||
@@ -48,7 +49,7 @@ public class ControlCenterServiceReceiver extends BroadcastReceiver {
|
||||
public ControlCenterServiceReceiver(ControlCenterService service) {
|
||||
LogUtils.d(TAG, String.format("ControlCenterServiceReceiver() 构造 | 服务实例:%s",
|
||||
service != null ? service.getClass().getSimpleName() : "null"));
|
||||
this.mwrControlCenterService = new WeakReference<>(service);
|
||||
this.mwrControlCenterService = new WeakReference<ControlCenterService>(service);
|
||||
}
|
||||
|
||||
// ====================== 广播核心接收逻辑(入口方法,分Action分发处理) ======================
|
||||
@@ -110,6 +111,15 @@ public class ControlCenterServiceReceiver extends BroadcastReceiver {
|
||||
LogUtils.d(TAG, "handleBatteryStateChanged() 跳过 | 电池状态无变化");
|
||||
return;
|
||||
}
|
||||
|
||||
// 在插拔充电线时,执行贴心服务
|
||||
if(currentCharging != sIsCharging) {
|
||||
if(currentCharging) {
|
||||
ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.CHARGE_STATE);
|
||||
} else {
|
||||
ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.DISCHARGE_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 更新静态缓存状态,保证多线程可见
|
||||
sIsCharging = currentCharging;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package cc.winboll.studio.powerbell.services;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.models.TTSSpeakTextBean;
|
||||
import cc.winboll.studio.powerbell.utils.TextToSpeechUtils;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* TTS 语音播放后台服务组件
|
||||
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||
* 功能:后台承载TTS语音播放,解耦页面生命周期,避免页面销毁中断播放
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/12/29 19:12
|
||||
*/
|
||||
public class TTSPlayService extends Service {
|
||||
|
||||
// ====================================== 常量区 - 静态全局常量 置顶排序 ======================================
|
||||
public static final String TAG = "TTSPlayService";
|
||||
public static final String EXTRA_SPEAKDATA = "EXTRA_SPEAKDATA";
|
||||
|
||||
// ====================================== 对外公开静态快捷调用方法【新增核心】======================================
|
||||
/**
|
||||
* 公开静态方法:一键启动TTS播放服务,播放指定文本内容
|
||||
* @param context 上下文对象
|
||||
* @param speakText 需要播放的语音文本内容
|
||||
*/
|
||||
public static void startPlayTTS(Context context, String speakText) {
|
||||
LogUtils.d(TAG, "【startPlayTTS】静态快捷调用方法 | 入参Context=" + context + " | 播放文本=" + speakText);
|
||||
if (context != null && speakText != null && !speakText.isEmpty()) {
|
||||
// 初始化播放数据集合
|
||||
ArrayList<TTSSpeakTextBean> ttsBeanList = new ArrayList<>();
|
||||
// 添加播放文本,延迟时间为0:无延迟立即播放
|
||||
ttsBeanList.add(new TTSSpeakTextBean(0, speakText));
|
||||
LogUtils.d(TAG, "【startPlayTTS】封装播放数据完成,创建启动服务意图");
|
||||
|
||||
// 创建意图并封装序列化参数
|
||||
Intent intent = new Intent(context, TTSPlayService.class);
|
||||
intent.putExtra(EXTRA_SPEAKDATA, ttsBeanList);
|
||||
|
||||
// 启动当前服务
|
||||
context.startService(intent);
|
||||
LogUtils.d(TAG, "【startPlayTTS】已调用startService,TTS播放服务启动成功");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【startPlayTTS】上下文为空 或 播放文本为空/空字符串,跳过启动服务");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 生命周期方法 - 绑定服务 (无绑定逻辑) ======================================
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
LogUtils.d(TAG, "【onBind】服务绑定方法调用,入参Intent:" + intent);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ====================================== 生命周期方法 - 启动服务【核心方法】 ======================================
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "【onStartCommand】服务启动方法调用 | 入参Intent:" + intent + " | flags:" + flags + " | startId:" + startId);
|
||||
// 解析播放数据并执行播放
|
||||
if (intent != null) {
|
||||
LogUtils.d(TAG, "【onStartCommand】Intent不为空,开始解析序列化播放数据");
|
||||
ArrayList<TTSSpeakTextBean> listTTSSpeakTextBean = (ArrayList<TTSSpeakTextBean>) intent.getSerializableExtra(EXTRA_SPEAKDATA);
|
||||
if (listTTSSpeakTextBean != null && listTTSSpeakTextBean.size() > 0) {
|
||||
LogUtils.d(TAG, "【onStartCommand】解析播放数据成功,队列长度:" + listTTSSpeakTextBean.size() + ",调用TTS播放工具类");
|
||||
TextToSpeechUtils.getInstance(this).speekTTSList(listTTSSpeakTextBean);
|
||||
} else {
|
||||
LogUtils.d(TAG, "【onStartCommand】播放数据为空/长度0,跳过语音播放逻辑");
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【onStartCommand】Intent为空,无播放数据可解析");
|
||||
}
|
||||
// 返回默认值,保持原服务启动策略不变
|
||||
int result = super.onStartCommand(intent, flags, startId);
|
||||
LogUtils.d(TAG, "【onStartCommand】方法执行完成,返回值:" + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package cc.winboll.studio.powerbell.services;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.models.AppConfigBean;
|
||||
import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
|
||||
/**
|
||||
* 智能电池服务(充电/放电状态处理)
|
||||
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||
* 功能:接收充电/放电状态指令,根据不同状态执行对应业务任务
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/12/29 19:29
|
||||
*/
|
||||
public class ThoughtfulService extends Service {
|
||||
|
||||
// ====================================== 常量区 - 置顶排序 ======================================
|
||||
public static final String TAG = "ThoughtfulService";
|
||||
/** Intent传递 服务类型 的Key值 */
|
||||
public static final String EXTRA_SERVICE_TYPE = "EXTRA_SERVICE_TYPE";
|
||||
|
||||
// ====================================== 枚举类 - 服务类型 充电/放电状态 ======================================
|
||||
/**
|
||||
* 服务执行类型枚举
|
||||
* CHARGE_STATE : 充电状态服务
|
||||
* DISCHARGE_STATE : 放电(耗电)状态服务
|
||||
*/
|
||||
public enum ServiceType {
|
||||
CHARGE_STATE, //充电状态服务
|
||||
DISCHARGE_STATE //放电状态服务
|
||||
}
|
||||
|
||||
// ====================================== 对外公开静态启动函数【新增核心】入参Context + 枚举 ======================================
|
||||
/**
|
||||
* 公开静态方法:传入上下文+服务类型枚举,一键构建意图并启动当前服务
|
||||
* @param context 上下文对象
|
||||
* @param serviceType 服务类型枚举【充电/放电】
|
||||
*/
|
||||
public static void startServiceWithType(Context context, ServiceType serviceType) {
|
||||
LogUtils.d(TAG, "【startServiceWithType】静态启动方法调用 | Context=" + context + " | ServiceType=" + (serviceType == null ? "null" : serviceType.name()));
|
||||
|
||||
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(context, ThoughtfulServiceBean.class);
|
||||
if (thoughtfulServiceBean == null) {
|
||||
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||
}
|
||||
|
||||
// 对应TTS服务提醒没有启用,就退出
|
||||
if((serviceType == ServiceType.CHARGE_STATE && !thoughtfulServiceBean.isEnableChargeTts())
|
||||
||(serviceType == ServiceType.DISCHARGE_STATE && !thoughtfulServiceBean.isEnableUsePowerTts())){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 判空健壮性校验
|
||||
if (context != null && serviceType != null) {
|
||||
// 构建意图 + 封装枚举参数
|
||||
Intent intent = new Intent(context, ThoughtfulService.class);
|
||||
intent.putExtra(EXTRA_SERVICE_TYPE, serviceType);
|
||||
// 启动服务
|
||||
context.startService(intent);
|
||||
LogUtils.d(TAG, "【startServiceWithType】服务启动成功,执行[" + serviceType.name() + "]任务");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【startServiceWithType】上下文为空 或 服务类型枚举为空,跳过启动服务");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 生命周期方法 - 绑定服务 (原逻辑保留) ======================================
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
LogUtils.d(TAG, "【onBind】服务绑定方法调用,入参Intent:" + intent);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ====================================== 生命周期方法 - 启动服务【核心逻辑】接收枚举+分支执行任务 ======================================
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "【onStartCommand】服务启动方法调用 | intent=" + intent + " | flags=" + flags + " | startId=" + startId);
|
||||
// 判断意图非空,解析服务类型参数
|
||||
if (intent != null) {
|
||||
LogUtils.d(TAG, "【onStartCommand】Intent不为空,开始解析服务类型枚举参数");
|
||||
// 获取传递的服务类型枚举
|
||||
ServiceType serviceType = (ServiceType) intent.getSerializableExtra(EXTRA_SERVICE_TYPE);
|
||||
// 根据服务类型,执行对应任务
|
||||
if (serviceType != null) {
|
||||
LogUtils.d(TAG, "【onStartCommand】解析到服务类型:" + serviceType.name());
|
||||
switch (serviceType) {
|
||||
case CHARGE_STATE:
|
||||
// 执行【充电状态】对应的业务任务
|
||||
executeChargeStateTask();
|
||||
break;
|
||||
case DISCHARGE_STATE:
|
||||
// 执行【放电状态】对应的业务任务
|
||||
executeDischargeStateTask();
|
||||
break;
|
||||
default:
|
||||
LogUtils.d(TAG, "【onStartCommand】未知的服务类型,不执行任何任务");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【onStartCommand】未解析到有效服务类型参数,参数为空");
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【onStartCommand】启动服务的Intent为空,直接返回");
|
||||
}
|
||||
|
||||
// 返回默认策略,与原生逻辑一致
|
||||
int result = super.onStartCommand(intent, flags, startId);
|
||||
LogUtils.d(TAG, "【onStartCommand】服务执行完成,返回值:" + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ====================================== 私有业务方法 充电/放电 分任务执行 ======================================
|
||||
/**
|
||||
* 执行【充电状态】的业务任务
|
||||
* 可在此方法内编写 充电时的逻辑(语音提醒/电量监控/弹窗等)
|
||||
*/
|
||||
private void executeChargeStateTask() {
|
||||
LogUtils.d(TAG, "【executeChargeStateTask】执行【充电状态】业务任务 >>> ");
|
||||
//ToastUtils.show("【executeChargeStateTask】执行【充电状态】业务任务 >>> ");
|
||||
// TODO 此处添加充电状态需要执行的业务逻辑代码
|
||||
// 加载最新配置
|
||||
AppConfigBean latestConfig = AppConfigUtils.getInstance(this).loadAppConfig();
|
||||
if (latestConfig == null) {
|
||||
LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (latestConfig.isEnableChargeReminder()) {
|
||||
int nChargeReminderValue = latestConfig.getChargeReminderValue();
|
||||
String szRemind = String.format("限量充电提醒已启用,限量值为百分之%d。", nChargeReminderValue);
|
||||
szRemind = szRemind + szRemind + szRemind;
|
||||
TTSPlayService.startPlayTTS(this, szRemind);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行【放电(耗电)状态】的业务任务
|
||||
* 可在此方法内编写 放电时的逻辑(语音提醒/电量监控/弹窗等)
|
||||
*/
|
||||
private void executeDischargeStateTask() {
|
||||
LogUtils.d(TAG, "【executeDischargeStateTask】执行【放电状态】业务任务 >>> ");
|
||||
//ToastUtils.show("【executeDischargeStateTask】执行【放电状态】业务任务 >>> ");
|
||||
// TODO 此处添加放电状态需要执行的业务逻辑代码
|
||||
// 加载最新配置
|
||||
AppConfigBean latestConfig = AppConfigUtils.getInstance(this).loadAppConfig();
|
||||
if (latestConfig == null) {
|
||||
LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
|
||||
return;
|
||||
}
|
||||
|
||||
if (latestConfig.isEnableUsageReminder()) {
|
||||
int nUsageReminderValue = latestConfig.getUsageReminderValue();
|
||||
String szRemind = String.format("电量不足提醒已启用,低电值为百分之%d。", nUsageReminderValue);
|
||||
//szRemind = szRemind + szRemind + szRemind;
|
||||
TTSPlayService.startPlayTTS(this, szRemind);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class ImageCropUtils {
|
||||
|
||||
// 3. 初始化 uCrop + 强制 PNG 配置(保留透明核心)
|
||||
UCrop uCrop = UCrop.of(inputUri, outputUri);
|
||||
uCrop.withAspectRatio(aspectX, aspectY);
|
||||
//uCrop.withAspectRatio(aspectX, aspectY);
|
||||
UCrop.Options options = initCropOptions(activity, isFreeCrop, aspectX, aspectY);
|
||||
|
||||
// 4. 启动裁剪
|
||||
@@ -129,7 +129,7 @@ public class ImageCropUtils {
|
||||
|
||||
// 3. 初始化 uCrop + 强制 PNG 配置
|
||||
UCrop uCrop = UCrop.of(inputUri, outputUri);
|
||||
uCrop.withAspectRatio(aspectX, aspectY);
|
||||
//uCrop.withAspectRatio(aspectX, aspectY);
|
||||
UCrop.Options options = initCropOptions(activity, isFreeCrop, aspectX, aspectY);
|
||||
|
||||
// 4. 启动裁剪
|
||||
@@ -296,7 +296,8 @@ public class ImageCropUtils {
|
||||
|
||||
// 裁剪模式配置(自由裁剪/固定比例)
|
||||
options.setFreeStyleCropEnabled(isFreeCrop);
|
||||
|
||||
options.withAspectRatio(aspectX, aspectY);
|
||||
|
||||
// 核心:强制 PNG 保留透明(固定配置,无需判断原图格式)
|
||||
options.setCompressionFormat(FORCE_COMPRESS_FORMAT); // 强制 PNG 压缩
|
||||
options.setCompressionQuality(100); // PNG 100% 质量,不损失透明
|
||||
@@ -305,7 +306,8 @@ public class ImageCropUtils {
|
||||
options.setCropGridColor(activity.getResources().getColor(R.color.colorAccent)); // 网格线主题色
|
||||
|
||||
// 通用 UI 配置(保持原有风格)
|
||||
options.setHideBottomControls(true); // 隐藏底部控制栏
|
||||
|
||||
//options.setHideBottomControls(true); // 隐藏底部控制栏
|
||||
options.setToolbarTitle("图片裁剪");
|
||||
options.setToolbarColor(activity.getResources().getColor(R.color.colorPrimary));
|
||||
options.setToolbarWidgetColor(activity.getResources().getColor(android.R.color.white));
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.TTSSpeakTextBean;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* TTS语音播放工具类 (单例实现)
|
||||
* 适配:Java7 语法规范 | Android API36 系统版本【修复崩溃】
|
||||
* 功能:队列播放语音文本 + 播放悬浮窗展示 + 点击悬浮窗停止播放/关闭悬浮窗
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/12/29 19:03
|
||||
*/
|
||||
public class TextToSpeechUtils {
|
||||
|
||||
// ====================================== 常量区 - 静态全局常量 (置顶) ======================================
|
||||
public static final String TAG = "TextToSpeechUtils";
|
||||
public static final String UNIQUE_ID = "UNIQUE_ID";
|
||||
|
||||
// ====================================== 单例实例 - 静态私有 (饿汉式优化) ======================================
|
||||
private static volatile TextToSpeechUtils sTextToSpeechUtils;
|
||||
|
||||
// ====================================== 成员属性区 - 私有成员变量 (按功能归类 有序排列) ======================================
|
||||
private Context mContext;
|
||||
private WindowManager mWindowManager;
|
||||
private TextToSpeech mTextToSpeech;
|
||||
private View mView;
|
||||
private volatile boolean isExist = false;
|
||||
private UtteranceProgressListener mUtteranceProgressListener;
|
||||
|
||||
// ====================================== 构造方法 - 私有私有化 (单例模式) ======================================
|
||||
private TextToSpeechUtils(Context context) {
|
||||
LogUtils.d(TAG, "【构造方法】初始化TextToSpeechUtil实例");
|
||||
this.mContext = context.getApplicationContext();
|
||||
this.mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
this.initUtteranceProgressListener();
|
||||
LogUtils.d(TAG, "【构造方法】初始化完成,获取WindowManager实例:"+mWindowManager);
|
||||
}
|
||||
|
||||
// ====================================== 对外暴露方法 - 单例获取入口 (线程安全) ======================================
|
||||
public static synchronized TextToSpeechUtils getInstance(Context context) {
|
||||
LogUtils.d(TAG, "【getInstance】获取单例实例,入参Context:" + context);
|
||||
if (sTextToSpeechUtils == null) {
|
||||
LogUtils.d(TAG, "【getInstance】实例为空,创建新的TextToSpeechUtil对象");
|
||||
sTextToSpeechUtils = new TextToSpeechUtils(context);
|
||||
}
|
||||
return sTextToSpeechUtils;
|
||||
}
|
||||
|
||||
// ====================================== 核心对外业务方法 - 播放TTS语音队列 【主入口】 ======================================
|
||||
public void speekTTSList(final ArrayList<TTSSpeakTextBean> listTTSSpeakTextBean) {
|
||||
LogUtils.d(TAG, "【speekTTSList】播放语音队列调用,入参队列长度:" + (listTTSSpeakTextBean == null ? 0 : listTTSSpeakTextBean.size()));
|
||||
// 重置播放退出标志位
|
||||
isExist = false;
|
||||
LogUtils.d(TAG, "【speekTTSList】重置播放退出标志位 isExist = " + isExist);
|
||||
|
||||
// TTS实例为空 → 初始化TTS后重放
|
||||
if (mTextToSpeech == null) {
|
||||
LogUtils.d(TAG, "【speekTTSList】TextToSpeech实例为空,开始初始化TTS");
|
||||
mTextToSpeech = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int initStatus) {
|
||||
LogUtils.d(TAG, "【onInit】TTS初始化回调,初始化状态码:" + initStatus);
|
||||
if (initStatus == TextToSpeech.SUCCESS) {
|
||||
LogUtils.d(TAG, "【onInit】TTS初始化成功,重新调用语音播放方法");
|
||||
speekTTSList(listTTSSpeakTextBean);
|
||||
} else {
|
||||
LogUtils.d(TAG, "【onInit】TTS init failed : " + initStatus + ". The app [https://play.google.com/store/apps/details?id=com.google.android.tts] maybe fix this TTS probrem. ");
|
||||
}
|
||||
}
|
||||
});
|
||||
mTextToSpeech.setOnUtteranceProgressListener(mUtteranceProgressListener);
|
||||
LogUtils.d(TAG, "【speekTTSList】已为TTS绑定播放进度监听器");
|
||||
} else {
|
||||
// TTS实例就绪 → 执行播放逻辑
|
||||
if (listTTSSpeakTextBean != null && listTTSSpeakTextBean.size() > 0) {
|
||||
LogUtils.d(TAG, "【speekTTSList】TTS实例就绪,语音队列数据有效,开始播放逻辑处理");
|
||||
// 清理过期的悬浮窗 - 防止内存泄漏/重复添加
|
||||
clearFloatWindow();
|
||||
|
||||
// ========== 修复1:添加悬浮窗权限检查,有权限才初始化悬浮窗,无权限则只播语音不崩溃 ==========
|
||||
if (checkOverlayPermission()) {
|
||||
initWindow();
|
||||
LogUtils.d(TAG, "【speekTTSList】悬浮窗初始化并显示完成");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【speekTTSList】悬浮窗权限未授予,跳过悬浮窗显示,仅播放语音");
|
||||
}
|
||||
|
||||
// 获取第一条语音的延迟时间并休眠
|
||||
int nDelay = listTTSSpeakTextBean.get(0).mnDelay;
|
||||
LogUtils.d(TAG, "【speekTTSList】获取播放延迟时间:" + nDelay + "ms,开始休眠等待");
|
||||
try {
|
||||
Thread.sleep(nDelay);
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.d(TAG, "【speekTTSList】休眠等待被中断", e);
|
||||
}
|
||||
LogUtils.d(TAG, "【speekTTSList】休眠等待完成,开始循环播放语音队列");
|
||||
|
||||
// 循环播放语音队列
|
||||
for (int speakPosition = 0; speakPosition < listTTSSpeakTextBean.size() && !isExist; speakPosition++) {
|
||||
String szSpeakContent = listTTSSpeakTextBean.get(speakPosition).mszSpeakContent;
|
||||
isExist = (listTTSSpeakTextBean.size() - 2 < speakPosition);
|
||||
LogUtils.d(TAG, "【speekTTSList】播放索引:" + speakPosition + " | 播放文本:" + szSpeakContent + " | 当前退出标记位:" + isExist);
|
||||
|
||||
// 第一条语音清空队列播放,后续语音追加播放
|
||||
if (speakPosition == 0) {
|
||||
mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_FLUSH, null, UNIQUE_ID);
|
||||
LogUtils.d(TAG, "【speekTTSList】执行清空队列播放 → QUEUE_FLUSH");
|
||||
} else {
|
||||
mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_ADD, null, UNIQUE_ID);
|
||||
LogUtils.d(TAG, "【speekTTSList】执行追加队列播放 → QUEUE_ADD");
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "【speekTTSList】语音队列循环播放逻辑执行完毕");
|
||||
} else {
|
||||
LogUtils.d(TAG, "【speekTTSList】语音队列为空/长度0,跳过播放逻辑");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 私有工具方法 - 初始化播放监听器 ======================================
|
||||
private void initUtteranceProgressListener() {
|
||||
LogUtils.d(TAG, "【initUtteranceProgressListener】初始化TTS播放进度监听器");
|
||||
mUtteranceProgressListener = new UtteranceProgressListener() {
|
||||
@Override
|
||||
public void onStart(String utteranceId) {
|
||||
LogUtils.d(TAG, "【onStart】TTS语音播放开始,唯一标识ID:" + utteranceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone(String utteranceId) {
|
||||
LogUtils.d(TAG, "【onDone】TTS语音播放结束,唯一标识ID:" + utteranceId + " | 退出标志位:" + isExist);
|
||||
// 播放完成 关闭悬浮窗
|
||||
if (isExist && mWindowManager != null && mView != null) {
|
||||
LogUtils.d(TAG, "【onDone】满足关闭条件,执行悬浮窗移除操作");
|
||||
clearFloatWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String utteranceId) {
|
||||
LogUtils.d(TAG, "【onError】TTS语音播放出错,唯一标识ID:" + utteranceId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ====================================== 私有核心方法 - 初始化并添加悬浮窗 【核心修复 根治崩溃】 ======================================
|
||||
private void initWindow() {
|
||||
LogUtils.d(TAG, "【initWindow】开始初始化播放悬浮窗");
|
||||
// 创建Window布局参数
|
||||
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
|
||||
// ========== 修复2 重中之重:Android 12(API31)+ 彻底废弃TYPE_PHONE,统一用TYPE_APPLICATION_OVERLAY 适配API36 ==========
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
LogUtils.d(TAG, "【initWindow】系统版本>=API26,悬浮窗类型:TYPE_APPLICATION_OVERLAY");
|
||||
} else {
|
||||
// 仅低版本用TYPE_PHONE,高版本不再走这里
|
||||
params.type = WindowManager.LayoutParams.TYPE_PHONE;
|
||||
LogUtils.d(TAG, "【initWindow】系统版本<API26,悬浮窗类型:TYPE_PHONE");
|
||||
}
|
||||
// 悬浮窗样式配置
|
||||
params.alpha = 0.9f;
|
||||
params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
|
||||
params.x = 20;
|
||||
params.y = 20;
|
||||
params.format = PixelFormat.RGBA_8888;
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
// 核心Flag:无焦点+不阻塞触摸事件穿透
|
||||
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
|
||||
|
||||
// 加载悬浮窗布局
|
||||
mView = View.inflate(mContext, R.layout.view_tts_back, null);
|
||||
LinearLayout llMain = mView.findViewById(R.id.viewttsbackLinearLayout1);
|
||||
llMain.setOnClickListener(new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
LogUtils.d(TAG, "【onClick】悬浮窗被点击,执行停止播放+关闭悬浮窗");
|
||||
isExist = true;
|
||||
if (mTextToSpeech != null) {
|
||||
mTextToSpeech.stop();
|
||||
LogUtils.d(TAG, "【onClick】已调用TTS.stop()停止播放");
|
||||
}
|
||||
clearFloatWindow();
|
||||
}
|
||||
});
|
||||
// ========== 修复3:添加异常捕获+重复添加判断,防止addView时报错导致整个TTS播放崩溃 ==========
|
||||
try {
|
||||
if (mWindowManager != null && mView != null && mView.getWindowToken() == null) {
|
||||
mWindowManager.addView(mView, params);
|
||||
LogUtils.d(TAG, "【initWindow】悬浮窗添加到Window成功,布局加载完成");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "【initWindow】悬浮窗添加异常(权限/系统限制),不影响语音播放:", e);
|
||||
mView = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 私有工具方法 - 清理悬浮窗 (通用封装) 【优化修复】 ======================================
|
||||
private void clearFloatWindow() {
|
||||
LogUtils.d(TAG, "【clearFloatWindow】执行悬浮窗清理操作");
|
||||
if (mWindowManager != null && mView != null) {
|
||||
try {
|
||||
mWindowManager.removeView(mView);
|
||||
LogUtils.d(TAG, "【clearFloatWindow】悬浮窗移除成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "【clearFloatWindow】悬浮窗移除异常", e);
|
||||
} finally {
|
||||
mView = null;
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "【clearFloatWindow】无需清理,WindowManager或View为空");
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== ✅ 新增:悬浮窗权限检查【必须】Android6.0+ 强制校验 防止崩溃 ✅ ======================================
|
||||
private boolean checkOverlayPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
boolean hasPermission = android.provider.Settings.canDrawOverlays(mContext);
|
||||
LogUtils.d(TAG, "【checkOverlayPermission】Android6.0+ 悬浮窗权限校验结果:" + hasPermission);
|
||||
return hasPermission;
|
||||
} else {
|
||||
// 低版本默认有权限
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== ✅ 新增:释放资源方法【根治内存泄漏】建议在Service/Activity销毁时调用 ✅ ======================================
|
||||
public void release() {
|
||||
LogUtils.d(TAG, "【release】释放TTS资源和悬浮窗");
|
||||
clearFloatWindow();
|
||||
if (mTextToSpeech != null) {
|
||||
mTextToSpeech.stop();
|
||||
mTextToSpeech.shutdown();
|
||||
mTextToSpeech = null;
|
||||
}
|
||||
sTextToSpeechUtils = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,13 +156,6 @@ public class BackgroundView extends RelativeLayout {
|
||||
// 隐藏ImageView防止闪烁
|
||||
mIvBackground.setVisibility(View.GONE);
|
||||
|
||||
// 初始化配置工具类并保存默认相框尺寸
|
||||
AppConfigUtils appConfigUtils = AppConfigUtils.getInstance(mContext);
|
||||
appConfigUtils.loadAppConfig();
|
||||
LogUtils.d(TAG, String.format("【loadImage】默认相框尺寸 | W=%d | H=%d",
|
||||
appConfigUtils.mAppConfigBean.getDefaultFrameWidth(),
|
||||
appConfigUtils.mAppConfigBean.getDefaultFrameHeight()));
|
||||
|
||||
// 刷新逻辑:重新解码原始品质图片并更新缓存
|
||||
if (isRefresh) {
|
||||
LogUtils.d(TAG, "【loadImage】执行刷新逻辑:重新解码原始品质图片");
|
||||
@@ -174,8 +167,8 @@ public class BackgroundView extends RelativeLayout {
|
||||
// 合成纯色背景图片(使用配置文件中默认相框尺寸)
|
||||
Bitmap combinedBitmap = ImageUtils.drawBitmapOnSolidBackground(
|
||||
bgColor,
|
||||
appConfigUtils.mAppConfigBean.getDefaultFrameWidth(),
|
||||
appConfigUtils.mAppConfigBean.getDefaultFrameHeight(),
|
||||
App.sAppConfigUtils.mAppConfigBean.getDefaultFrameWidth(),
|
||||
App.sAppConfigUtils.mAppConfigBean.getDefaultFrameHeight(),
|
||||
newBitmap
|
||||
);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.models.BatteryStyle;
|
||||
|
||||
/**
|
||||
* 电池电量Drawable:适配API30,兼容小米机型,支持能量/条纹两种绘制风格切换
|
||||
@@ -31,7 +32,7 @@ public class BatteryDrawable extends Drawable {
|
||||
private final Paint mBatteryPaint;
|
||||
// 业务控制变量
|
||||
private int mBatteryValue = -1; // 当前电量(0-100,-1=未初始化)
|
||||
private boolean mIsEnergyStyle = true; // 绘制风格(true=能量,false=条纹)
|
||||
private BatteryStyle mBatteryStyle = BatteryStyle.ENERGY_STYLE; // 绘制风格(true=能量,false=条纹)
|
||||
|
||||
// ====================================== 构造方法(重载适配,优先暴露常用构造) ======================================
|
||||
/**
|
||||
@@ -49,13 +50,16 @@ public class BatteryDrawable extends Drawable {
|
||||
* @param batteryColor 电量显示颜色
|
||||
* @param isEnergyStyle 是否启用能量风格
|
||||
*/
|
||||
public BatteryDrawable(int batteryColor, boolean isEnergyStyle) {
|
||||
LogUtils.d(TAG, "【BatteryDrawable】构造器2调用 | 颜色=" + Integer.toHexString(batteryColor) + " | 风格=" + (isEnergyStyle ? "能量" : "条纹"));
|
||||
public BatteryDrawable(int batteryColor, BatteryStyle batteryStyle) {
|
||||
mBatteryPaint = new Paint();
|
||||
mIsEnergyStyle = isEnergyStyle;
|
||||
mBatteryStyle = batteryStyle;
|
||||
initPaintConfig(batteryColor);
|
||||
}
|
||||
|
||||
public void setIsEnergyStyle(BatteryStyle batteryStyle) {
|
||||
this.mBatteryStyle = batteryStyle;
|
||||
}
|
||||
|
||||
// ====================================== 私有初始化方法(封装复用,隐藏内部逻辑) ======================================
|
||||
/**
|
||||
* 初始化画笔配置(适配API30渲染特性,优化小米机型兼容性)
|
||||
@@ -74,8 +78,7 @@ public class BatteryDrawable extends Drawable {
|
||||
// ====================================== 核心绘制方法(Drawable抽象方法,优先级最高) ======================================
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
LogUtils.d(TAG, "【draw】绘制开始 | 当前电量=" + mBatteryValue + " | 风格=" + (mIsEnergyStyle ? "能量" : "条纹"));
|
||||
// 未初始化/异常电量,直接跳过,避免无效绘制
|
||||
// 未初始化/异常电量,直接跳过,避免无效绘制
|
||||
if (mBatteryValue < 0) {
|
||||
LogUtils.w(TAG, "【draw】电量未初始化,跳过绘制");
|
||||
return;
|
||||
@@ -99,10 +102,12 @@ public class BatteryDrawable extends Drawable {
|
||||
LogUtils.d(TAG, "【draw】绘制参数校准 | 左边界=" + left + " | 右边界=" + right + " | 高度=" + drawHeight);
|
||||
|
||||
// 按风格执行绘制
|
||||
if (mIsEnergyStyle) {
|
||||
if (mBatteryStyle == BatteryStyle.ENERGY_STYLE) {
|
||||
drawEnergyStyle(canvas, validBattery, left, right, drawHeight);
|
||||
} else {
|
||||
drawStripeStyle(canvas, validBattery, left, right, drawHeight);
|
||||
} else if (mBatteryStyle == BatteryStyle.ZEBRA_STYLE) {
|
||||
drawZebraStyle(canvas, validBattery, left, right, drawHeight);
|
||||
} else if (mBatteryStyle == BatteryStyle.POINT_STYLE) {
|
||||
drawPointStyle(canvas, validBattery, left, right, drawHeight);
|
||||
}
|
||||
LogUtils.d(TAG, "【draw】绘制完成");
|
||||
}
|
||||
@@ -118,9 +123,25 @@ public class BatteryDrawable extends Drawable {
|
||||
*/
|
||||
private void drawEnergyStyle(Canvas canvas, int battery, int left, int right, int height) {
|
||||
LogUtils.d(TAG, "【drawEnergyStyle】能量风格绘制开始 | 电量=" + battery);
|
||||
int top = height - (height * battery / BATTERY_MAX); // 计算电量对应顶部坐标
|
||||
canvas.drawRect(new Rect(left, top, right, height), mBatteryPaint);
|
||||
LogUtils.d(TAG, "【drawEnergyStyle】能量风格绘制完成 | 顶部坐标=" + top);
|
||||
// int top = height - (height * battery / BATTERY_MAX); // 计算电量对应顶部坐标
|
||||
// canvas.drawRect(new Rect(left, top, right, height), mBatteryPaint);
|
||||
// LogUtils.d(TAG, "【drawEnergyStyle】能量风格绘制完成 | 顶部坐标=" + top);
|
||||
int nWidth = getBounds().width();
|
||||
int nHeight = getBounds().height();
|
||||
int mnDx = nHeight / 203;
|
||||
|
||||
// 绘制耗电电量提醒值电量
|
||||
// 能量绘图风格
|
||||
int nTop;
|
||||
int nLeft = 0;
|
||||
int nBottom;
|
||||
int nRight = nWidth;
|
||||
|
||||
//for (int i = 0; i < mnValue; i ++) {
|
||||
nBottom = nHeight;
|
||||
nTop = nHeight - (nHeight * mBatteryValue / 100);
|
||||
canvas.drawRect(new Rect(nLeft, nTop, nRight, nBottom), mBatteryPaint);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,15 +152,75 @@ public class BatteryDrawable extends Drawable {
|
||||
* @param right 右边界
|
||||
* @param height 绘制高度
|
||||
*/
|
||||
private void drawStripeStyle(Canvas canvas, int battery, int left, int right, int height) {
|
||||
private void drawZebraStyle(Canvas canvas, int battery, int left, int right, int height) {
|
||||
LogUtils.d(TAG, "【drawStripeStyle】条纹风格绘制开始 | 电量=" + battery);
|
||||
int stripeHeight = height / STRIPE_COUNT; // 单条条纹高度(均匀拆分)
|
||||
// 从底部向上绘制对应电量条纹
|
||||
for (int i = 0; i < battery; i++) {
|
||||
int bottom = height - (stripeHeight * i);
|
||||
int top = bottom - stripeHeight;
|
||||
canvas.drawRect(new Rect(left, top, right, bottom), mBatteryPaint);
|
||||
}
|
||||
// int stripeHeight = height / STRIPE_COUNT; // 单条条纹高度(均匀拆分)
|
||||
// // 从底部向上绘制对应电量条纹
|
||||
// for (int i = 0; i < battery; i++) {
|
||||
// int bottom = height - (stripeHeight * i);
|
||||
// int top = bottom - stripeHeight;
|
||||
// canvas.drawRect(new Rect(left, top, right, bottom), mBatteryPaint);
|
||||
// }
|
||||
|
||||
int nWidth = getBounds().width();
|
||||
int nHeight = getBounds().height();
|
||||
int mnDx = nHeight / 203;
|
||||
|
||||
|
||||
// 意兴阑珊绘图风格
|
||||
int nTop;
|
||||
int nLeft = 0;
|
||||
int nBottom;
|
||||
int nRight = nWidth;
|
||||
|
||||
for (int i = 0; i < mBatteryValue; i ++) {
|
||||
nBottom = (nHeight * (100 - i) / 100) - mnDx;
|
||||
nTop = nBottom + mnDx;
|
||||
canvas.drawRect(new Rect(nLeft, nTop, nRight, nBottom), mBatteryPaint);
|
||||
}
|
||||
LogUtils.d(TAG, "【drawStripeStyle】条纹风格绘制完成 | 条纹数量=" + battery);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 点阵风格绘制
|
||||
* @param canvas 绘制画布
|
||||
* @param battery 有效电量(0-100)
|
||||
* @param left 左边界
|
||||
* @param right 右边界
|
||||
* @param height 绘制高度
|
||||
*/
|
||||
private void drawPointStyle(Canvas canvas, int battery, int left, int right, int height) {
|
||||
LogUtils.d(TAG, "【drawStripeStyle】条纹风格绘制开始 | 电量=" + battery);
|
||||
|
||||
int nWidth = getBounds().width();
|
||||
int nHeight = getBounds().height();
|
||||
int mnDx = nHeight / 203;
|
||||
|
||||
|
||||
// 意兴阑珊绘图风格
|
||||
int nTop;
|
||||
int nLeft = 0;
|
||||
int nBottom;
|
||||
int nRight = nWidth;
|
||||
|
||||
int nLineWidth = nRight - nLeft;
|
||||
int radius_horizontal = (nLineWidth / 10) / 2;
|
||||
int radius_vertical = mnDx/2;
|
||||
int radius = Math.min(radius_horizontal, radius_vertical);
|
||||
|
||||
for (int i = 0; i < mBatteryValue; i ++) {
|
||||
nBottom = (nHeight * (100 - i) / 100) - mnDx;
|
||||
nTop = nBottom + mnDx;
|
||||
//canvas.drawRect(new Rect(nLeft, nTop, nRight, nBottom), mBatteryPaint);
|
||||
|
||||
for (int j = 0; j < 10; j++) {
|
||||
// cx, cy 圆心坐标;radius 半径;paint 画笔
|
||||
int cx = radius_horizontal + radius_horizontal * j * 2;
|
||||
int cy = nTop + radius_vertical;
|
||||
canvas.drawCircle(cx, cy, radius, mBatteryPaint);
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "【drawStripeStyle】条纹风格绘制完成 | 条纹数量=" + battery);
|
||||
}
|
||||
|
||||
@@ -159,9 +240,8 @@ public class BatteryDrawable extends Drawable {
|
||||
* 切换绘制风格
|
||||
* @param isEnergyStyle true=能量风格,false=条纹风格
|
||||
*/
|
||||
public void switchDrawStyle(boolean isEnergyStyle) {
|
||||
LogUtils.d(TAG, "【switchDrawStyle】风格切换 | 旧风格=" + (mIsEnergyStyle ? "能量" : "条纹") + " | 新风格=" + (isEnergyStyle ? "能量" : "条纹"));
|
||||
mIsEnergyStyle = isEnergyStyle;
|
||||
public void setDrawStyle(BatteryStyle batteryStyle) {
|
||||
mBatteryStyle = batteryStyle;
|
||||
invalidateSelf();
|
||||
LogUtils.d(TAG, "【switchDrawStyle】已触发重绘");
|
||||
}
|
||||
@@ -188,12 +268,10 @@ public class BatteryDrawable extends Drawable {
|
||||
return mBatteryValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前绘制风格
|
||||
* @return true=能量风格,false=条纹风格
|
||||
*/
|
||||
public boolean isEnergyStyle() {
|
||||
return mIsEnergyStyle;
|
||||
|
||||
|
||||
public BatteryStyle getEnergyStyle() {
|
||||
return mBatteryStyle;
|
||||
}
|
||||
|
||||
// ====================================== Drawable抽象方法(必须实现,精简逻辑) ======================================
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package cc.winboll.studio.powerbell.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.MainActivity;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.BatteryStyle;
|
||||
|
||||
/**
|
||||
* 电池样式单选视图,水平展示所有BatteryStyle枚举选项
|
||||
* 每个选项 = RadioButton单选按钮 + BatteryDrawable预览控件
|
||||
* 适配API30、Java7规范,联动BatteryDrawable绘制样式
|
||||
* 包含:SP持久化存储 + 公共静态方法读取SP枚举值 + 彻底修复点击不回调+单选失效
|
||||
* 默认选中:BatteryStyle.ENERGY_STYLE
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
*/
|
||||
public class BatteryStyleView extends LinearLayout implements RadioGroup.OnCheckedChangeListener {
|
||||
// ====================== 常量区 ======================
|
||||
public static final String TAG = "BatteryStyleView";
|
||||
private static final int DEFAULT_BATTERY_COLOR = Color.parseColor("#FF4CAF50");
|
||||
private static final int DEFAULT_CHECKED_STYLE_INDEX = 1; // ✅ 修改默认选中下标 1 = ENERGY_STYLE
|
||||
public static final String SP_NAME = "sp_battery_style_config";
|
||||
public static final String SP_KEY_BATTERY_STYLE = "key_selected_battery_style";
|
||||
|
||||
// ====================== 控件变量 ======================
|
||||
private RadioGroup rgBatteryStyle;
|
||||
private RadioButton rbZebraStyle;
|
||||
private RadioButton rbEnergyStyle;
|
||||
private RadioButton rbPointStyle; // ✅ 新增:圆点样式单选按钮
|
||||
private RelativeLayout rlZebraPreview;
|
||||
private RelativeLayout rlEnergyPreview;
|
||||
private RelativeLayout rlPointPreview; // ✅ 新增:圆点样式预览布局
|
||||
private BatteryDrawable mZebraDrawable;
|
||||
private BatteryDrawable mEnergyDrawable;
|
||||
private BatteryDrawable mPointDrawable; // ✅ 新增:圆点样式Drawable实例
|
||||
|
||||
// ====================== 业务变量 ======================
|
||||
private BatteryStyle mCurrentStyle = BatteryStyle.ENERGY_STYLE; // ✅ 修改默认样式为 能量样式
|
||||
private OnBatteryStyleSelectedListener mStyleSelectedListener;
|
||||
private int mBatteryColor = DEFAULT_BATTERY_COLOR;
|
||||
private int mBatteryValue = 100;
|
||||
private SharedPreferences mSp;
|
||||
|
||||
// ====================== 构造方法 ======================
|
||||
public BatteryStyleView(Context context) {
|
||||
super(context);
|
||||
initSP(context);
|
||||
initView(context, null);
|
||||
}
|
||||
|
||||
public BatteryStyleView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initSP(context);
|
||||
initAttrs(context, attrs);
|
||||
initView(context, attrs);
|
||||
}
|
||||
|
||||
public BatteryStyleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initSP(context);
|
||||
initAttrs(context, attrs);
|
||||
initView(context, attrs);
|
||||
}
|
||||
|
||||
// ====================== 初始化SP持久化 ======================
|
||||
private void initSP(Context context) {
|
||||
mSp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
LogUtils.d(TAG, "【initSP】SharedPreferences初始化完成,文件名称 = " + SP_NAME);
|
||||
}
|
||||
|
||||
// ====================== 初始化方法 ======================
|
||||
private void initAttrs(Context context, AttributeSet attrs) {
|
||||
if (attrs == null) return;
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BatteryStyleView);
|
||||
mBatteryColor = typedArray.getColor(R.styleable.BatteryStyleView_batteryPreviewColor, DEFAULT_BATTERY_COLOR);
|
||||
mBatteryValue = typedArray.getInt(R.styleable.BatteryStyleView_previewBatteryValue, 100);
|
||||
int styleIndex = typedArray.getInt(R.styleable.BatteryStyleView_defaultSelectedStyle, DEFAULT_CHECKED_STYLE_INDEX);
|
||||
mCurrentStyle = getStyleFromSP() == null ? (styleIndex == 0 ? BatteryStyle.ENERGY_STYLE : styleIndex ==1 ? BatteryStyle.ZEBRA_STYLE : BatteryStyle.POINT_STYLE) : getStyleFromSP();
|
||||
typedArray.recycle();
|
||||
LogUtils.d(TAG, "【initAttrs】解析属性完成 电量颜色=" + Integer.toHexString(mBatteryColor) + " 预览电量=" + mBatteryValue + " 默认样式=" + mCurrentStyle.name());
|
||||
}
|
||||
|
||||
private void initView(Context context, AttributeSet attrs) {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_battery_style, this, true);
|
||||
rgBatteryStyle = findViewById(R.id.rg_battery_style);
|
||||
rbZebraStyle = findViewById(R.id.rb_zebra_style);
|
||||
rbEnergyStyle = findViewById(R.id.rb_energy_style);
|
||||
rbPointStyle = findViewById(R.id.rb_point_style); // ✅ 新增:绑定圆点样式单选按钮
|
||||
rlZebraPreview = findViewById(R.id.rl_zebra_preview);
|
||||
rlEnergyPreview = findViewById(R.id.rl_energy_preview);
|
||||
rlPointPreview = findViewById(R.id.rl_point_preview); // ✅ 新增:绑定圆点样式预览布局
|
||||
|
||||
initPreviewDrawable();
|
||||
rgBatteryStyle.setOnCheckedChangeListener(this);
|
||||
addRadioBtnClickLister();
|
||||
setDefaultChecked();
|
||||
LogUtils.d(TAG, "【initView】视图初始化完成");
|
||||
}
|
||||
|
||||
private void initPreviewDrawable() {
|
||||
mZebraDrawable = new BatteryDrawable(mBatteryColor, BatteryStyle.ZEBRA_STYLE);
|
||||
mZebraDrawable.setBatteryValue(mBatteryValue);
|
||||
rlZebraPreview.setBackground(mZebraDrawable);
|
||||
|
||||
mEnergyDrawable = new BatteryDrawable(mBatteryColor, BatteryStyle.ENERGY_STYLE);
|
||||
mEnergyDrawable.setBatteryValue(mBatteryValue);
|
||||
rlEnergyPreview.setBackground(mEnergyDrawable);
|
||||
|
||||
// ✅ 新增:初始化圆点样式Drawable + 绑定预览布局 + 设置电量值
|
||||
mPointDrawable = new BatteryDrawable(mBatteryColor, BatteryStyle.POINT_STYLE);
|
||||
mPointDrawable.setBatteryValue(mBatteryValue);
|
||||
rlPointPreview.setBackground(mPointDrawable);
|
||||
|
||||
LogUtils.d(TAG, "【initPreviewDrawable】Drawable预览初始化完成");
|
||||
}
|
||||
|
||||
private void setDefaultChecked() {
|
||||
// ✅ 新增:圆点样式的默认选中判断
|
||||
if (mCurrentStyle == BatteryStyle.ZEBRA_STYLE) {
|
||||
rbZebraStyle.setChecked(true);
|
||||
} else if (mCurrentStyle == BatteryStyle.POINT_STYLE) {
|
||||
rbPointStyle.setChecked(true);
|
||||
} else {
|
||||
rbEnergyStyle.setChecked(true);
|
||||
}
|
||||
LogUtils.d(TAG, "【setDefaultChecked】默认选中样式 = " + mCurrentStyle.name());
|
||||
}
|
||||
|
||||
private void addRadioBtnClickLister() {
|
||||
rbZebraStyle.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
rbZebraStyle.setChecked(true);
|
||||
rbEnergyStyle.setChecked(false);
|
||||
rbPointStyle.setChecked(false); // ✅ 新增:取消圆点样式选中
|
||||
handleStyleSelect(BatteryStyle.ZEBRA_STYLE);
|
||||
}
|
||||
});
|
||||
|
||||
rbEnergyStyle.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
rbEnergyStyle.setChecked(true);
|
||||
rbZebraStyle.setChecked(false);
|
||||
rbPointStyle.setChecked(false); // ✅ 新增:取消圆点样式选中
|
||||
handleStyleSelect(BatteryStyle.ENERGY_STYLE);
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ 新增:圆点样式单选按钮点击事件
|
||||
rbPointStyle.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
rbPointStyle.setChecked(true);
|
||||
rbZebraStyle.setChecked(false);
|
||||
rbEnergyStyle.setChecked(false);
|
||||
handleStyleSelect(BatteryStyle.POINT_STYLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ====================== RadioGroup 选中回调 (点击必触发) ======================
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup group, int checkedId) {
|
||||
ToastUtils.show("onCheckedChanged");
|
||||
if (checkedId == R.id.rb_zebra_style) {
|
||||
handleStyleSelect(BatteryStyle.ZEBRA_STYLE);
|
||||
} else if (checkedId == R.id.rb_energy_style) {
|
||||
handleStyleSelect(BatteryStyle.ENERGY_STYLE);
|
||||
} else if (checkedId == R.id.rb_point_style) { // ✅ 新增:圆点样式选中回调
|
||||
handleStyleSelect(BatteryStyle.POINT_STYLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStyleSelect(BatteryStyle style) {
|
||||
mCurrentStyle = style;
|
||||
saveStyle2SP(mCurrentStyle);
|
||||
MainActivity.sendUpdateBatteryDrawableMessage();
|
||||
LogUtils.d(TAG, "【handleStyleSelect】选中样式 → " + mCurrentStyle.name() + ",已存入SP");
|
||||
if (mStyleSelectedListener != null) {
|
||||
mStyleSelectedListener.onStyleSelected(mCurrentStyle);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== SP持久化 存储+读取 封装方法 ======================
|
||||
private void saveStyle2SP(BatteryStyle style) {
|
||||
mSp.edit().putString(SP_KEY_BATTERY_STYLE, style.name()).commit();
|
||||
}
|
||||
|
||||
private BatteryStyle getStyleFromSP() {
|
||||
String styleStr = mSp.getString(SP_KEY_BATTERY_STYLE, null);
|
||||
if (styleStr == null) return null;
|
||||
try {
|
||||
return BatteryStyle.valueOf(styleStr);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "【getStyleFromSP】SP读取样式异常 = " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 公共静态方法 读取SP存储的枚举值 ======================
|
||||
public static BatteryStyle getSavedBatteryStyle(Context context) {
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
String styleStr = sp.getString(SP_KEY_BATTERY_STYLE, null);
|
||||
if (styleStr == null) {
|
||||
LogUtils.w(TAG, "【getSavedBatteryStyle】SP无存储值,返回默认样式 ENERGY_STYLE");
|
||||
return BatteryStyle.ENERGY_STYLE; // ✅ 静态方法默认值同步修改为能量样式
|
||||
}
|
||||
try {
|
||||
BatteryStyle style = BatteryStyle.valueOf(styleStr);
|
||||
LogUtils.d(TAG, "【getSavedBatteryStyle】SP读取成功 → " + style.name());
|
||||
return style;
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "【getSavedBatteryStyle】SP读取异常 = " + e.getMessage() + ",返回默认样式 ENERGY_STYLE");
|
||||
return BatteryStyle.ENERGY_STYLE; // ✅ 异常兜底值同步修改为能量样式
|
||||
}
|
||||
}
|
||||
|
||||
public static BatteryStyle getSavedBatteryStyle(Context context, BatteryStyle defaultStyle) {
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
String styleStr = sp.getString(SP_KEY_BATTERY_STYLE, null);
|
||||
if (styleStr == null) {
|
||||
LogUtils.w(TAG, "【getSavedBatteryStyle】SP无存储值,返回自定义默认样式 → " + defaultStyle.name());
|
||||
return defaultStyle;
|
||||
}
|
||||
try {
|
||||
BatteryStyle style = BatteryStyle.valueOf(styleStr);
|
||||
LogUtils.d(TAG, "【getSavedBatteryStyle】SP读取成功 → " + style.name());
|
||||
return style;
|
||||
} catch (IllegalArgumentException e) {
|
||||
LogUtils.e(TAG, "【getSavedBatteryStyle】SP读取异常 = " + e.getMessage() + ",返回自定义默认样式 → " + defaultStyle.name());
|
||||
return defaultStyle;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 对外暴露方法 ======================
|
||||
public void setSelectedStyle(BatteryStyle style) {
|
||||
mCurrentStyle = style;
|
||||
// ✅ 新增:圆点样式的手动选中赋值
|
||||
rbZebraStyle.setChecked(style == BatteryStyle.ZEBRA_STYLE);
|
||||
rbEnergyStyle.setChecked(style == BatteryStyle.ENERGY_STYLE);
|
||||
rbPointStyle.setChecked(style == BatteryStyle.POINT_STYLE);
|
||||
saveStyle2SP(style);
|
||||
LogUtils.d(TAG, "【setSelectedStyle】手动设置选中样式 → " + style.name() + ",已存入SP");
|
||||
}
|
||||
|
||||
public BatteryStyle getCurrentStyle() {
|
||||
return mCurrentStyle;
|
||||
}
|
||||
|
||||
public void setPreviewBatteryValue(int batteryValue) {
|
||||
this.mBatteryValue = batteryValue;
|
||||
mZebraDrawable.setBatteryValue(batteryValue);
|
||||
mEnergyDrawable.setBatteryValue(batteryValue);
|
||||
mPointDrawable.setBatteryValue(batteryValue); // ✅ 新增:圆点样式同步电量值
|
||||
}
|
||||
|
||||
public void setPreviewBatteryColor(int color) {
|
||||
this.mBatteryColor = color;
|
||||
mZebraDrawable.updateBatteryColor(color);
|
||||
mEnergyDrawable.updateBatteryColor(color);
|
||||
mPointDrawable.updateBatteryColor(color); // ✅ 新增:圆点样式同步颜色值
|
||||
}
|
||||
|
||||
public void setOnBatteryStyleSelectedListener(OnBatteryStyleSelectedListener listener) {
|
||||
this.mStyleSelectedListener = listener;
|
||||
}
|
||||
|
||||
// ====================== 选中回调接口 ======================
|
||||
public interface OnBatteryStyleSelectedListener {
|
||||
void onStyleSelected(BatteryStyle batteryStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,20 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.models.BatteryStyle;
|
||||
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
|
||||
/**
|
||||
* 主页面核心视图封装类:统一管理视图绑定、数据更新、事件监听,解耦 Activity 逻辑
|
||||
* 适配:Java7 | API30 | 小米手机,优化性能与资源回收,杜绝内存泄漏,配置变更确认对话框
|
||||
* 新增:拖动进度条时实时预览 sbUsageReminder 与 sbChargeReminder 比值
|
||||
* 修复:updateBatteryDrawable() 电池样式切换后重绘失效问题
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/17 13:14
|
||||
*/
|
||||
@@ -43,7 +45,7 @@ public class MainContentView {
|
||||
private static final int BATTERY_MIN = 0;
|
||||
private static final int BATTERY_MAX = 100;
|
||||
|
||||
// ====================================== 内部静态类(临时数据载体,避免外部依赖) ======================================
|
||||
// ====================================== 内部缓存类(解耦,避免冗余) ======================================
|
||||
/**
|
||||
* 临时配置数据实体(缓存变更信息,取消时恢复)
|
||||
*/
|
||||
@@ -89,6 +91,8 @@ public class MainContentView {
|
||||
public RelativeLayout mainLayout;
|
||||
public MemoryCachedBackgroundView backgroundView;
|
||||
private LinearLayout mllBackgroundView;
|
||||
private volatile BatteryStyle mBatteryStyle = BatteryStyle.ENERGY_STYLE;
|
||||
|
||||
// 容器布局控件
|
||||
public LinearLayout llLeftSeekBar;
|
||||
public LinearLayout llRightSeekBar;
|
||||
@@ -133,6 +137,7 @@ public class MainContentView {
|
||||
this.mContext = context;
|
||||
this.mActionListener = actionListener;
|
||||
this.mAppConfigUtils = AppConfigUtils.getInstance(context.getApplicationContext());
|
||||
mBatteryStyle = BatteryStyleView.getSavedBatteryStyle(context);
|
||||
|
||||
// 执行核心初始化流程(按顺序执行,避免依赖空指针)
|
||||
bindViews(rootView);
|
||||
@@ -154,7 +159,7 @@ public class MainContentView {
|
||||
mainLayout = (RelativeLayout) rootView.findViewById(R.id.activitymainRelativeLayout1);
|
||||
mllBackgroundView = (LinearLayout) rootView.findViewById(R.id.ll_backgroundview);
|
||||
|
||||
backgroundView = App.getInstance().getMemoryCachedBackgroundView().getLastInstance(mContext);
|
||||
backgroundView = App.getInstance().getMemoryCachedBackgroundView();
|
||||
if (backgroundView == null) {
|
||||
App.sBackgroundSourceUtils.loadSettings();
|
||||
BackgroundBean backgroundBean = App.sBackgroundSourceUtils.getCurrentBackgroundBean();
|
||||
@@ -197,23 +202,72 @@ public class MainContentView {
|
||||
LogUtils.d(TAG, "【bindViews】视图绑定完成");
|
||||
}
|
||||
|
||||
public void reloadBackgroundView() {
|
||||
if (backgroundView != null) {
|
||||
App.sBackgroundSourceUtils.loadSettings();
|
||||
BackgroundBean backgroundBean = App.sBackgroundSourceUtils.getCurrentBackgroundBean();
|
||||
backgroundView.loadByBackgroundBean(backgroundBean, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化电池 Drawable(集成 BatteryDrawable,默认能量风格,适配小米机型渲染)
|
||||
*/
|
||||
private void initBatteryDrawables() {
|
||||
LogUtils.d(TAG, "【initBatteryDrawables】电池Drawable初始化开始");
|
||||
LogUtils.d(TAG, "【initBatteryDrawables】电池Drawable初始化开始 | style="+mBatteryStyle.name());
|
||||
// 当前电量 Drawable(颜色从资源读取,适配 API30 主题)
|
||||
int colorCurrent = getResourceColor(R.color.colorCurrent);
|
||||
mCurrentBatteryDrawable = new BatteryDrawable(colorCurrent);
|
||||
mCurrentBatteryDrawable.setDrawStyle(mBatteryStyle);
|
||||
// 充电提醒 Drawable
|
||||
int colorCharge = getResourceColor(R.color.colorCharge);
|
||||
mChargeReminderBatteryDrawable = new BatteryDrawable(colorCharge);
|
||||
// 耗电提醒 Drawable
|
||||
mChargeReminderBatteryDrawable.setDrawStyle(mBatteryStyle);
|
||||
// 耗电提醒 Drawable
|
||||
int colorUsage = getResourceColor(R.color.colorUsege);
|
||||
mUsageReminderBatteryDrawable = new BatteryDrawable(colorUsage);
|
||||
LogUtils.d(TAG, "【initBatteryDrawables】电池Drawable初始化完成");
|
||||
mUsageReminderBatteryDrawable.setDrawStyle(mBatteryStyle);
|
||||
LogUtils.d(TAG, "【initBatteryDrawables】电池Drawable初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ 核心修复:电池样式切换+强制重绘刷新 完整版
|
||||
* 修复点1:重新创建Drawable后,重新给ImageView赋值Drawable
|
||||
* 修复点2:重置所有Drawable的电量值,保证样式切换后数值不变
|
||||
* 修复点3:调用ImageView.invalidate()强制触发重绘(API30必加)
|
||||
* 修复点4:Drawable.invalidateSelf() 双保险刷新绘制内容
|
||||
* @param batteryStyle 切换后的电池样式
|
||||
*/
|
||||
public void updateBatteryDrawable(BatteryStyle batteryStyle) {
|
||||
if(batteryStyle == null || batteryStyle == mBatteryStyle){
|
||||
LogUtils.d(TAG, "【updateBatteryDrawable】样式无变化,跳过刷新");
|
||||
return;
|
||||
}
|
||||
// 1. 更新样式标记
|
||||
mBatteryStyle = batteryStyle;
|
||||
// 2. 重新初始化Drawable并设置新样式
|
||||
initBatteryDrawables();
|
||||
// 3. 重置所有Drawable的电量值 → 保证样式切换后数值不变
|
||||
mCurrentBatteryDrawable.setBatteryValue(mAppConfigUtils.getCurrentBatteryValue());
|
||||
mChargeReminderBatteryDrawable.setBatteryValue(mCurrentChargeProgress);
|
||||
mUsageReminderBatteryDrawable.setBatteryValue(mCurrentUsageProgress);
|
||||
// 4. 重新给ImageView赋值Drawable → 核心修复:之前缺失这一步
|
||||
if(ivCurrentBattery != null) ivCurrentBattery.setImageDrawable(mCurrentBatteryDrawable);
|
||||
if(ivChargeReminderBattery != null) ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
|
||||
if(ivUsageReminderBattery != null) ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
|
||||
// 5. Drawable自身刷新 → 双保险
|
||||
mCurrentBatteryDrawable.invalidateSelf();
|
||||
mChargeReminderBatteryDrawable.invalidateSelf();
|
||||
mUsageReminderBatteryDrawable.invalidateSelf();
|
||||
// 6. ✅ API30关键修复:ImageView强制重绘,解决绘制缓存不刷新问题
|
||||
if(ivCurrentBattery != null) ivCurrentBattery.invalidate();
|
||||
if(ivChargeReminderBattery != null) ivChargeReminderBattery.invalidate();
|
||||
if(ivUsageReminderBattery != null) ivUsageReminderBattery.invalidate();
|
||||
|
||||
LogUtils.d(TAG, "【updateBatteryDrawable】样式切换完成:"+mBatteryStyle.name() + " | 重绘触发成功");
|
||||
ToastUtils.show("电池样式已切换为:"+mBatteryStyle.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置变更确认对话框(核心优化:保存 Builder 实例,解决消息不生效问题)
|
||||
*/
|
||||
|
||||
@@ -189,11 +189,11 @@ public class MemoryCachedBackgroundView extends BackgroundView {
|
||||
* 从缓存实例中获取上下文(用于无外部上下文时的SP操作)
|
||||
* @return 上下文实例,无则返回null
|
||||
*/
|
||||
private static Context getContextFromCache() {
|
||||
Context context = sCachedView != null ? sCachedView.getContext() : null;
|
||||
LogUtils.d(TAG, String.format("getContextFromCache 调用 | 从缓存获取上下文=%s", context));
|
||||
return context;
|
||||
}
|
||||
// private static Context getContextFromCache() {
|
||||
// Context context = sCachedView != null ? sCachedView.getContext() : null;
|
||||
// LogUtils.d(TAG, String.format("getContextFromCache 调用 | 从缓存获取上下文=%s", context));
|
||||
// return context;
|
||||
// }
|
||||
|
||||
// ====================================== 重写父类方法:增强日志+SP持久化(强制保持版) ======================================
|
||||
@Override
|
||||
|
||||
41
powerbell/src/main/res/drawable/bg_frame_white.xml
Normal file
41
powerbell/src/main/res/drawable/bg_frame_white.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<!-- 阴影部分 -->
|
||||
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
|
||||
<item
|
||||
android:left="2dp"
|
||||
android:top="2dp"
|
||||
android:right="2dp"
|
||||
android:bottom="2dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="#0FFFFFFF"
|
||||
android:startColor="#0FFFFFFF" />
|
||||
<corners
|
||||
android:bottomLeftRadius="6dip"
|
||||
android:bottomRightRadius="6dip"
|
||||
android:topLeftRadius="6dip"
|
||||
android:topRightRadius="6dip" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- 背景部分 -->
|
||||
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
|
||||
<item
|
||||
android:left="3dp"
|
||||
android:top="3dp"
|
||||
android:right="3dp"
|
||||
android:bottom="5dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="#CFFFFFFF"
|
||||
android:startColor="#CFFFFFFF" />
|
||||
<corners
|
||||
android:bottomLeftRadius="6dip"
|
||||
android:bottomRightRadius="6dip"
|
||||
android:topLeftRadius="6dip"
|
||||
android:topRightRadius="6dip" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
11
powerbell/src/main/res/drawable/speaker.xml
Normal file
11
powerbell/src/main/res/drawable/speaker.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#ff000000"
|
||||
android:pathData="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"/>
|
||||
|
||||
</vector>
|
||||
@@ -113,8 +113,9 @@
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
@@ -127,7 +128,7 @@
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/fragmentandroidviewImageView2"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
@@ -135,7 +136,7 @@
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
@@ -150,7 +151,7 @@
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/fragmentandroidviewImageView1"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
@@ -158,8 +159,9 @@
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
@@ -172,7 +174,7 @@
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/fragmentandroidviewImageView3"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<cc.winboll.studio.libaes.views.AToolbar
|
||||
<cc.winboll.studio.libaes.views.ASupportToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_height"
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -14,17 +14,46 @@
|
||||
style="@style/DefaultAToolbar"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="用电提醒启用时的TTS贴心服务"
|
||||
android:onClick="onEnableUsePowerTts"
|
||||
android:id="@+id/activitysettingsCheckBox1"/>
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="充电提醒启用时的TTS贴心服务"
|
||||
android:onClick="onEnableChargeTts"
|
||||
android:id="@+id/activitysettingsCheckBox2"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CheckPermission"
|
||||
android:padding="10dp"
|
||||
android:onClick="onCheckPermission"/>
|
||||
android:gravity="right">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="检查TTS语音悬浮窗权限"
|
||||
android:padding="10dp"
|
||||
android:onClick="onCheckTTSDrawOverlaysPermission"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -33,5 +62,20 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<cc.winboll.studio.powerbell.views.BatteryStyleView
|
||||
android:id="@+id/battery_style_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:batteryPreviewColor="@color/colorPrimary"
|
||||
app:previewBatteryValue="100"
|
||||
app:defaultSelectedStyle="zebra_style"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
94
powerbell/src/main/res/layout/view_battery_style.xml
Normal file
94
powerbell/src/main/res/layout/view_battery_style.xml
Normal file
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RadioGroup
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/rg_battery_style"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="8dp"
|
||||
android:spacing="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_energy_style"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/energy_style"
|
||||
android:textSize="14sp"
|
||||
android:buttonTint="@color/colorPrimary"
|
||||
android:textColor="@color/colorPrimary"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_energy_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="#F5F5F5"
|
||||
android:padding="1dp"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_zebra_style"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/zebra_style"
|
||||
android:textSize="14sp"
|
||||
android:buttonTint="@color/colorPrimary"
|
||||
android:textColor="@color/colorPrimary"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_zebra_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="#F5F5F5"
|
||||
android:padding="1dp"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_point_style"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/point_style"
|
||||
android:textSize="14sp"
|
||||
android:buttonTint="@color/colorPrimary"
|
||||
android:textColor="@color/colorPrimary"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/rl_point_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="#F5F5F5"
|
||||
android:padding="1dp"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
43
powerbell/src/main/res/layout/view_tts_back.xml
Normal file
43
powerbell/src/main/res/layout/view_tts_back.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_frame_white"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:id="@+id/viewttsbackLinearLayout1">
|
||||
|
||||
<LinearLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="10dp"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="#FF000000"
|
||||
android:textStyle="bold"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:textAllCaps="false"
|
||||
android:background="@drawable/speaker"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Click To Stop TTS Play"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="#FF000000"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- BatteryStyleView 自定义属性 -->
|
||||
<declare-styleable name="BatteryStyleView">
|
||||
<attr name="batteryPreviewColor" format="color"/>
|
||||
<attr name="previewBatteryValue" format="integer"/>
|
||||
<attr name="defaultSelectedStyle" format="integer">
|
||||
<enum name="zebra_style" value="0"/>
|
||||
<enum name="energy_style" value="1"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
<string name="subtitle_activity_about">About The APP</string>
|
||||
<string name="msg_AOHPCTCSeekBar_ClearRecord">>>>Seek 100% Right Is Clean Record.>>></string>
|
||||
<string name="msg_no_battery_record">No Battery Record</string>
|
||||
<string name="zebra_style">Zebra Style</string>
|
||||
<string name="energy_style">Energy Style</string>
|
||||
<string name="point_style">Point Style</string>
|
||||
|
||||
<!-- 权限申请相关字符串(统一管理,避免硬编码) -->
|
||||
<string name="permission_title">权限申请</string>
|
||||
|
||||
Reference in New Issue
Block a user