Compare commits
30 Commits
powerbell-
...
powerbell-
| Author | SHA1 | Date | |
|---|---|---|---|
| 8609c2f784 | |||
| 863b743330 | |||
| 61b7afa4b5 | |||
| 8e4c7a6832 | |||
| d29068b029 | |||
| 51963f8e0f | |||
| 18a5762c15 | |||
| 60de16ab45 | |||
| 6d60d71991 | |||
| 0cfbc43acb | |||
| 20227e29ef | |||
| 2b7007f478 | |||
| 212b8185c8 | |||
| 2ef09e020e | |||
| 6de9b7379b | |||
| 7fba2c8812 | |||
| 20e3a5f974 | |||
| c8333e1e81 | |||
| 6beb56efae | |||
| eb51b2c8f4 | |||
| 36bdd059b0 | |||
| bf051dcc9f | |||
| b38a8df462 | |||
| a8dbe43d4b | |||
| 05a8dc5205 | |||
| 280fc1abd6 | |||
| fe2084f5ff | |||
| 2a75aa140e | |||
| 04b597cbe7 | |||
| 35d210c378 |
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sat Dec 20 18:45:13 HKT 2025
|
||||
stageCount=11
|
||||
#Mon Dec 22 11:02:54 HKT 2025
|
||||
stageCount=22
|
||||
libraryProject=
|
||||
baseVersion=15.14
|
||||
publishVersion=15.14.10
|
||||
publishVersion=15.14.21
|
||||
buildCount=0
|
||||
baseBetaVersion=15.14.11
|
||||
baseBetaVersion=15.14.22
|
||||
|
||||
@@ -4,54 +4,73 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cc.winboll.studio.powerbell">
|
||||
|
||||
<!-- ====================== 原有权限保留 + 补充核心权限 ====================== -->
|
||||
<!-- 运行前台服务(原有保留,补充 Android 12+ 特殊前台服务权限) -->
|
||||
<!-- 运行前台服务 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
|
||||
<!-- 开机启动(原有保留,确保自启广播生效) -->
|
||||
<!-- 运行“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.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.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
|
||||
<!-- 拍摄照片和视频 -->
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions"/>
|
||||
|
||||
<!-- 相机相关(原有保留,补充权限等级声明,避免安装警告) -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false"/> <!-- 非核心功能设为非必须,兼容无相机设备 -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false"/>
|
||||
<uses-permission android:name="android.permission.CAMERA" /> <!-- 补充相机权限,原有仅声明feature无权限 -->
|
||||
|
||||
<!-- 应用信息相关(原有保留) -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false"/>
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission"/>
|
||||
|
||||
<!-- 新增:文件管理权限(对应 PermissionUtils 全文件管理逻辑) -->
|
||||
<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" /> <!-- API30+ 全文件权限 -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- 新增:忽略电池优化权限(对应 PermissionUtils 电池优化逻辑,必须声明) -->
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
|
||||
<!-- 新增:API30+ 跳转系统权限页兼容(避免自启/文件权限跳转失败) -->
|
||||
<queries>
|
||||
<!-- 全文件管理权限跳转兼容 -->
|
||||
|
||||
<!-- 小米自启权限跳转兼容(精准匹配安全中心包名) -->
|
||||
<package android:name="com.miui.securitycenter" />
|
||||
<!-- 电池优化权限跳转兼容 -->
|
||||
<package android:name="com.miui.securitycenter"/>
|
||||
|
||||
</queries>
|
||||
|
||||
@@ -68,17 +87,17 @@
|
||||
android:supportsRtl="true"
|
||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||
|
||||
<!-- ====================== 页面配置(原有保留,优化 exported 安全) ====================== -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
<activity
|
||||
android:name=".activities.CrashActivity"
|
||||
android:exported="false"/> <!-- 新增:非外部调用,设为 false,提升安全 -->
|
||||
android:exported="false"/>
|
||||
|
||||
<activity-alias
|
||||
android:name=".MainActivityEN1"
|
||||
@@ -87,13 +106,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
|
||||
@@ -103,13 +128,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
|
||||
@@ -119,53 +150,71 @@
|
||||
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:name=".activities.ClearRecordActivity"
|
||||
android:parentActivityName="cc.winboll.studio.powerbell.MainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false"/> <!-- 新增:非外部调用,设为 false -->
|
||||
android:exported="false"/>
|
||||
|
||||
<activity
|
||||
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>
|
||||
|
||||
<!-- ====================== 广播接收器(优化自启广播,提升保活成功率) ====================== -->
|
||||
<receiver
|
||||
android:name=".receivers.MainReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:directBootAware="true">
|
||||
<intent-filter android:priority="1000"> <!-- 新增:广播优先级最高,确保优先接收开机广播 -->
|
||||
|
||||
<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>
|
||||
|
||||
<!-- ====================== 服务配置(核心优化:前台服务保活,适配 API29-30) ====================== -->
|
||||
<!-- 核心前台服务:ControlCenterService(保活核心,重点优化) -->
|
||||
<service
|
||||
android:name=".services.ControlCenterService"
|
||||
android:priority="1000"
|
||||
@@ -173,67 +222,74 @@
|
||||
android:exported="false"
|
||||
android:process=".controlcenterservice"
|
||||
android:foregroundServiceType="dataSync">
|
||||
<!-- 新增:Android 12+ 前台服务用途声明(系统强制,否则拦截服务启动) -->
|
||||
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
||||
android:value="后台核心功能运行、持续保活" /> <!-- 按实际用途填写,不可空 -->
|
||||
android:value="后台核心功能运行、持续保活"/>
|
||||
|
||||
</service>
|
||||
|
||||
<!-- 辅助服务:AssistantService(按需优化,增强稳定性) -->
|
||||
<service
|
||||
android:name=".services.AssistantService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=".assistantservice"> <!-- 若需前台启动,添加此配置;纯后台可移除 -->
|
||||
android:process=".assistantservice">
|
||||
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
||||
android:value="辅助核心功能运行" />
|
||||
android:value="辅助核心功能运行"/>
|
||||
|
||||
</service>
|
||||
|
||||
<!-- ====================== 其他配置(原有保留,补充优化) ====================== -->
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="4.0"/>
|
||||
|
||||
<!-- 所有非外部调用的 Activity,统一设 exported=false,提升安全 -->
|
||||
<activity
|
||||
<activity
|
||||
android:name=".activities.BatteryReporterActivity"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
|
||||
<activity
|
||||
android:name=".activities.PixelPickerActivity"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
|
||||
<activity
|
||||
android:name=".activities.BatteryReportActivity"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
|
||||
<activity
|
||||
android:name=".unittest.MainUnitTestActivity"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
|
||||
<activity
|
||||
android:name=".activities.ShortcutActionActivity"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
|
||||
<activity
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:exported="false"/>
|
||||
|
||||
<!-- 文件提供者(原有保留,正常使用) -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<!-- UCrop 第三方页面(原有保留,exported=true 正常) -->
|
||||
<activity
|
||||
android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:exported="true">
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name="cc.winboll.studio.powerbell.unittest.MainUnitTest2Activity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
</manifest>
|
||||
BIN
powerbell/src/main/assets/images/blank100x100.png
Normal file
BIN
powerbell/src/main/assets/images/blank100x100.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
Binary file not shown.
|
Before Width: | Height: | Size: 100 B |
@@ -2,139 +2,249 @@ package cc.winboll.studio.powerbell;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.models.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.receivers.GlobalApplicationReceiver;
|
||||
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BitmapCacheUtils;
|
||||
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
|
||||
import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 应用全局入口类(适配Android API 30,基于Java 7编写)
|
||||
* 核心策略:无论内存是否紧张,强制保持位图缓存与视图控件缓存
|
||||
*/
|
||||
public class App extends GlobalApplication {
|
||||
// ===================== 常量定义区 =====================
|
||||
public static final String TAG = "App";
|
||||
|
||||
// 组件跳转常量
|
||||
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
||||
public static final String COMPONENT_CN1 = "cc.winboll.studio.powerbell.MainActivityCN1";
|
||||
public static final String COMPONENT_CN2 = "cc.winboll.studio.powerbell.MainActivityCN2";
|
||||
|
||||
// 动作跳转常量
|
||||
public static final String ACTION_SWITCHTO_EN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_EN1";
|
||||
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";
|
||||
|
||||
// 数据配置存储工具
|
||||
static AppConfigUtils _mAppConfigUtils;
|
||||
static AppCacheUtils _mAppCacheUtils;
|
||||
// 新增:全局 Bitmap 缓存工具(常驻内存)
|
||||
public static BitmapCacheUtils _mBitmapCacheUtils;
|
||||
// ===================== 静态属性区 =====================
|
||||
// 数据配置工具
|
||||
private static AppConfigUtils sAppConfigUtils;
|
||||
private static AppCacheUtils sAppCacheUtils;
|
||||
// 全局Bitmap缓存工具(强制保持,不随内存紧张清理)
|
||||
public static BitmapCacheUtils sBitmapCacheUtils;
|
||||
// 全局视图控件缓存工具(强制保持,不随内存紧张清理)
|
||||
public static MemoryCachedBackgroundView sMemoryCachedBackgroundView;
|
||||
// 临时文件夹路径
|
||||
private static String sTempDirPath = "";
|
||||
|
||||
GlobalApplicationReceiver mReceiver;
|
||||
static String szTempDir = "";
|
||||
// ===================== 成员属性区 =====================
|
||||
// 全局广播接收器
|
||||
private GlobalApplicationReceiver mGlobalReceiver;
|
||||
// 通知管理工具
|
||||
private NotificationManagerUtils mNotificationManager;
|
||||
|
||||
// ===================== 公共方法区 =====================
|
||||
/**
|
||||
* 获取临时文件夹路径
|
||||
*/
|
||||
public static String getTempDirPath() {
|
||||
return szTempDir;
|
||||
return sTempDirPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用配置工具实例
|
||||
*/
|
||||
public static AppConfigUtils getAppConfigUtils(Context context) {
|
||||
LogUtils.d(TAG, "getAppConfigUtils() 调用,传入Context:" + context.getClass().getSimpleName());
|
||||
if (sAppConfigUtils == null) {
|
||||
sAppConfigUtils = AppConfigUtils.getInstance(context);
|
||||
LogUtils.d(TAG, "getAppConfigUtils():AppConfigUtils实例已初始化");
|
||||
}
|
||||
return sAppConfigUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用缓存工具实例
|
||||
*/
|
||||
public static AppCacheUtils getAppCacheUtils(Context context) {
|
||||
LogUtils.d(TAG, "getAppCacheUtils() 调用,传入Context:" + context.getClass().getSimpleName());
|
||||
if (sAppCacheUtils == null) {
|
||||
sAppCacheUtils = AppCacheUtils.getInstance(context);
|
||||
LogUtils.d(TAG, "getAppCacheUtils():AppCacheUtils实例已初始化");
|
||||
}
|
||||
return sAppCacheUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除电池历史数据
|
||||
*/
|
||||
public void clearBatteryHistory() {
|
||||
LogUtils.d(TAG, "clearBatteryHistory() 调用");
|
||||
sAppCacheUtils.clearBatteryHistory();
|
||||
}
|
||||
|
||||
// ===================== 生命周期方法区 =====================
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate() 应用启动,开始初始化");
|
||||
|
||||
// 初始化调试模式
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
LogUtils.d(TAG, "onCreate() 调试模式:" + BuildConfig.DEBUG);
|
||||
|
||||
// 初始化活动窗口管理
|
||||
WinBoLLActivityManager.init(this);
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
// 初始化基础工具
|
||||
initBaseTools();
|
||||
// 初始化临时文件夹
|
||||
initTempDir();
|
||||
// 初始化工具类实例(含强制缓存工具)
|
||||
initUtils();
|
||||
// 初始化广播接收器
|
||||
initReceiver();
|
||||
|
||||
// 临时文件夹初始化(保持原有逻辑)
|
||||
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
File powerBellDir = new File(picturesDir, "PowerBell");
|
||||
if (!powerBellDir.exists()) {
|
||||
powerBellDir.mkdirs();
|
||||
}
|
||||
szTempDir = powerBellDir.getAbsolutePath();
|
||||
|
||||
// 设置数据配置存储工具
|
||||
_mAppConfigUtils = getAppConfigUtils(this);
|
||||
_mAppCacheUtils = getAppCacheUtils(this);
|
||||
// 初始化全局 Bitmap 缓存工具(关键:App 启动时初始化,常驻内存)
|
||||
_mBitmapCacheUtils = BitmapCacheUtils.getInstance();
|
||||
|
||||
mReceiver = new GlobalApplicationReceiver(this);
|
||||
mReceiver.registerAction();
|
||||
|
||||
// ======================== 新增:异步预加载背景图 ========================
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// 1. 获取背景源工具类实例
|
||||
BackgroundSourceUtils bgSourceUtils = BackgroundSourceUtils.getInstance(App.this);
|
||||
if (bgSourceUtils == null) {
|
||||
LogUtils.e(TAG, "preloadBitmap: BackgroundSourceUtils 实例为空");
|
||||
return;
|
||||
}
|
||||
// 2. 获取当前背景Bean
|
||||
BackgroundBean bgBean = bgSourceUtils.getCurrentBackgroundBean();
|
||||
if (bgBean == null || !bgBean.isUseBackgroundFile()) {
|
||||
LogUtils.d(TAG, "preloadBitmap: 无有效背景文件,跳过预加载");
|
||||
return;
|
||||
}
|
||||
// 3. 获取背景图路径(优先取压缩图路径)
|
||||
String bgPath = bgBean.isUseBackgroundScaledCompressFile()
|
||||
? bgBean.getBackgroundScaledCompressFilePath()
|
||||
: bgBean.getBackgroundFilePath();
|
||||
// 4. 预加载到全局缓存
|
||||
if (_mBitmapCacheUtils != null) {
|
||||
_mBitmapCacheUtils.cacheBitmap(bgPath);
|
||||
LogUtils.d(TAG, "preloadBitmap: 应用启动时预加载成功 - " + bgPath);
|
||||
} else {
|
||||
LogUtils.e(TAG, "preloadBitmap: 全局 BitmapCacheUtils 未初始化");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "preloadBitmap: 预加载失败 - " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}, 1000); // 延迟1秒执行,避免阻塞应用初始化
|
||||
// ======================== 预加载逻辑结束 ========================
|
||||
}
|
||||
|
||||
// 保持原有方法不变
|
||||
public static AppConfigUtils getAppConfigUtils(Context context) {
|
||||
if (_mAppConfigUtils == null) {
|
||||
_mAppConfigUtils = AppConfigUtils.getInstance(context);
|
||||
}
|
||||
return _mAppConfigUtils;
|
||||
}
|
||||
|
||||
public static AppCacheUtils getAppCacheUtils(Context context) {
|
||||
if (_mAppCacheUtils == null) {
|
||||
_mAppCacheUtils = AppCacheUtils.getInstance(context);
|
||||
}
|
||||
return _mAppCacheUtils;
|
||||
}
|
||||
|
||||
public void clearBatteryHistory() {
|
||||
_mAppCacheUtils.clearBatteryHistory();
|
||||
LogUtils.d(TAG, "onCreate() 应用初始化完成,强制缓存策略已启用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
LogUtils.d(TAG, "onTerminate() 应用终止,开始释放资源");
|
||||
|
||||
// 释放Toast工具
|
||||
ToastUtils.release();
|
||||
// 释放通知工具
|
||||
releaseNotificationManager();
|
||||
// 释放广播接收器
|
||||
releaseReceiver();
|
||||
|
||||
LogUtils.d(TAG, "onTerminate() 应用资源释放完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
}
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
// 核心修改:移除所有缓存清理逻辑,强制保持位图和视图控件缓存
|
||||
LogUtils.w(TAG, "onTrimMemory() 调用,内存等级level:" + level + ",强制保持所有缓存(不清理)");
|
||||
// 仅记录缓存状态,不执行任何清理操作
|
||||
logCacheStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
// 核心修改:低内存时也不清理缓存,仅记录日志
|
||||
LogUtils.w(TAG, "onLowMemory() 调用,强制保持所有缓存(不清理)");
|
||||
// 仅记录缓存状态,不执行任何清理操作
|
||||
logCacheStatus();
|
||||
}
|
||||
|
||||
// ===================== 私有初始化方法区 =====================
|
||||
/**
|
||||
* 初始化基础工具(Activity管理、Toast)
|
||||
*/
|
||||
private void initBaseTools() {
|
||||
LogUtils.d(TAG, "initBaseTools() 开始初始化基础工具");
|
||||
WinBoLLActivityManager.init(this);
|
||||
ToastUtils.init(this);
|
||||
LogUtils.d(TAG, "initBaseTools() 基础工具初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化临时文件夹(适配API 30外部存储访问)
|
||||
*/
|
||||
private void initTempDir() {
|
||||
LogUtils.d(TAG, "initTempDir() 开始初始化临时文件夹");
|
||||
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
File powerBellDir = new File(picturesDir, "PowerBell");
|
||||
if (!powerBellDir.exists()) {
|
||||
boolean isMkSuccess = powerBellDir.mkdirs();
|
||||
LogUtils.d(TAG, "initTempDir() 文件夹创建结果:" + isMkSuccess);
|
||||
}
|
||||
sTempDirPath = powerBellDir.getAbsolutePath();
|
||||
LogUtils.d(TAG, "initTempDir() 临时文件夹路径:" + sTempDirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化工具类实例(核心:强制初始化缓存工具,不随内存紧张重建)
|
||||
*/
|
||||
private void initUtils() {
|
||||
LogUtils.d(TAG, "initUtils() 开始初始化工具类,启用强制缓存策略");
|
||||
sAppConfigUtils = getAppConfigUtils(this);
|
||||
sAppCacheUtils = getAppCacheUtils(this);
|
||||
|
||||
// 强制初始化Bitmap缓存工具(单例唯一,不重复创建)
|
||||
if (sBitmapCacheUtils == null) {
|
||||
sBitmapCacheUtils = BitmapCacheUtils.getInstance();
|
||||
LogUtils.d(TAG, "initUtils() Bitmap缓存工具已初始化(强制保持)");
|
||||
}
|
||||
|
||||
// 强制初始化视图控件缓存工具(单例唯一,不重复创建)
|
||||
if (sMemoryCachedBackgroundView == null) {
|
||||
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getLastInstance(this);
|
||||
LogUtils.d(TAG, "initUtils() 视图控件缓存工具已初始化(强制保持)");
|
||||
}
|
||||
|
||||
mNotificationManager = new NotificationManagerUtils(this);
|
||||
LogUtils.d(TAG, "initUtils() 工具类初始化完成,强制缓存策略已生效");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化广播接收器
|
||||
*/
|
||||
private void initReceiver() {
|
||||
LogUtils.d(TAG, "initReceiver() 开始初始化广播接收器");
|
||||
mGlobalReceiver = new GlobalApplicationReceiver(this);
|
||||
mGlobalReceiver.registerAction();
|
||||
LogUtils.d(TAG, "initReceiver() 广播接收器注册完成");
|
||||
}
|
||||
|
||||
// ===================== 私有工具方法区 =====================
|
||||
/**
|
||||
* 释放广播接收器资源
|
||||
*/
|
||||
private void releaseReceiver() {
|
||||
LogUtils.d(TAG, "releaseReceiver() 开始释放广播接收器");
|
||||
if (mGlobalReceiver != null) {
|
||||
mGlobalReceiver.unregisterAction();
|
||||
mGlobalReceiver = null;
|
||||
LogUtils.d(TAG, "releaseReceiver() 广播接收器资源已释放");
|
||||
} else {
|
||||
LogUtils.d(TAG, "releaseReceiver() 广播接收器未初始化,无需释放");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放通知管理工具资源
|
||||
*/
|
||||
private void releaseNotificationManager() {
|
||||
LogUtils.d(TAG, "releaseNotificationManager() 开始释放通知工具");
|
||||
if (mNotificationManager != null) {
|
||||
mNotificationManager.release();
|
||||
mNotificationManager = null;
|
||||
LogUtils.d(TAG, "releaseNotificationManager() 通知工具资源已释放");
|
||||
} else {
|
||||
LogUtils.d(TAG, "releaseNotificationManager() 通知工具未初始化,无需释放");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录缓存状态(用于调试,不影响缓存数据)
|
||||
*/
|
||||
private void logCacheStatus() {
|
||||
LogUtils.d(TAG, "logCacheStatus() 开始记录缓存状态");
|
||||
if (sBitmapCacheUtils != null) {
|
||||
LogUtils.d(TAG, "logCacheStatus() Bitmap缓存工具实例有效(强制保持)");
|
||||
}
|
||||
if (sMemoryCachedBackgroundView != null) {
|
||||
LogUtils.d(TAG, "logCacheStatus() 视图控件缓存工具实例有效(强制保持)");
|
||||
}
|
||||
LogUtils.d(TAG, "logCacheStatus() 缓存状态记录完成,所有缓存均强制保持");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.activitys.AboutActivity;
|
||||
import cc.winboll.studio.libaes.models.APPInfo;
|
||||
@@ -28,6 +29,7 @@ import cc.winboll.studio.powerbell.activities.WinBoLLActivity;
|
||||
import cc.winboll.studio.powerbell.models.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
import cc.winboll.studio.powerbell.unittest.MainUnitTest2Activity;
|
||||
import cc.winboll.studio.powerbell.unittest.MainUnitTestActivity;
|
||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
@@ -41,14 +43,11 @@ import cc.winboll.studio.powerbell.views.MainContentView;
|
||||
* 适配:Java7 | API30 | 内存泄漏防护 | UI与服务状态实时同步
|
||||
*/
|
||||
public class MainActivity extends WinBoLLActivity implements MainContentView.OnViewActionListener {
|
||||
// ======================== 静态常量(置顶统一,抽离魔法值,便于维护)========================
|
||||
public static final String TAG = "MainActivity";
|
||||
|
||||
// 请求码常量
|
||||
// ======================== 静态常量(置顶统一,抽离魔法值)========================
|
||||
public static final String TAG = "MainActivity";
|
||||
private static final int REQUEST_READ_MEDIA_IMAGES = 1001;
|
||||
// 延迟加载常量(非核心视图延迟加载时长)
|
||||
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;
|
||||
@@ -75,7 +74,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
private Drawable mFrameDrawable;
|
||||
private Menu mMenu;
|
||||
|
||||
// ======================== 生命周期方法(按系统调用顺序排列,逻辑闭环)========================
|
||||
// ======================== 生命周期方法(按系统调用顺序排列)========================
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
@@ -88,12 +87,10 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
LogUtils.d(TAG, "onCreate: 页面启动,开始初始化流程 | savedInstanceState=" + savedInstanceState);
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate() | savedInstanceState=" + savedInstanceState);
|
||||
|
||||
// 优先初始化全局Handler,避免消息丢失
|
||||
initGlobalHandler();
|
||||
// 布局与核心流程初始化
|
||||
setContentView(R.layout.activity_main);
|
||||
initPermissionUtils();
|
||||
initMainContentView();
|
||||
@@ -105,62 +102,56 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onPostCreate: 发起权限申请");
|
||||
LogUtils.d(TAG, "onPostCreate() | savedInstanceState=" + savedInstanceState);
|
||||
mPermissionUtils.startPermissionRequest(this);
|
||||
LogUtils.d(TAG, "onPostCreate: 发起权限申请");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LogUtils.d(TAG, "onResume: 恢复页面状态");
|
||||
// 恢复背景与服务开关UI
|
||||
LogUtils.d(TAG, "onResume()");
|
||||
if (sGlobalHandler != null) {
|
||||
sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
|
||||
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
|
||||
LogUtils.d(TAG, "onResume: 发送背景加载与服务开关更新消息");
|
||||
}
|
||||
// 恢复广告
|
||||
if (mADsBannerView != null) {
|
||||
mADsBannerView.resumeADs(this);
|
||||
LogUtils.d(TAG, "onResume: 广告视图恢复展示");
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
mADsBannerView.releaseAdResources();
|
||||
mADsBannerView = null;
|
||||
LogUtils.d(TAG, "onDestroy: 广告资源已释放");
|
||||
}
|
||||
// 释放核心视图
|
||||
if (mMainContentView != null) {
|
||||
mMainContentView.releaseResources();
|
||||
mMainContentView = null;
|
||||
LogUtils.d(TAG, "onDestroy: 核心视图资源已释放");
|
||||
}
|
||||
// 销毁Handler,防止内存泄漏
|
||||
if (sGlobalHandler != null) {
|
||||
sGlobalHandler.removeCallbacksAndMessages(null);
|
||||
sGlobalHandler = null;
|
||||
LogUtils.d(TAG, "onDestroy: 全局Handler已销毁");
|
||||
}
|
||||
// 释放Drawable,清空回调
|
||||
// 释放Drawable
|
||||
if (mFrameDrawable != null) {
|
||||
mFrameDrawable.setCallback(null);
|
||||
mFrameDrawable = null;
|
||||
LogUtils.d(TAG, "onDestroy: 框架背景Drawable已释放");
|
||||
}
|
||||
// 置空所有引用,帮助GC回收
|
||||
// 置空所有引用
|
||||
sMainActivity = null;
|
||||
mPermissionUtils = null;
|
||||
mAppConfigUtils = null;
|
||||
@@ -170,31 +161,27 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
mApplication = null;
|
||||
mToolbar = null;
|
||||
mAdsViewStub = null;
|
||||
LogUtils.d(TAG, "onDestroy: 所有资源释放完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, "onActivityResult: requestCode=" + requestCode + " | resultCode=" + resultCode + " | data=" + data);
|
||||
LogUtils.d(TAG, "onActivityResult() | requestCode=" + requestCode + " | resultCode=" + resultCode + " | data=" + data);
|
||||
mPermissionUtils.handlePermissionRequest(this, requestCode, resultCode, data);
|
||||
// 背景设置完成后重新加载
|
||||
if (requestCode == REQUEST_READ_MEDIA_IMAGES && sGlobalHandler != null) {
|
||||
sGlobalHandler.sendEmptyMessage(MSG_LOAD_BACKGROUND);
|
||||
LogUtils.d(TAG, "onActivityResult: 背景设置完成,发送重新加载消息");
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 菜单与导航方法 ========================
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
LogUtils.d(TAG, "onCreateOptionsMenu: 初始化菜单");
|
||||
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);
|
||||
LogUtils.d(TAG, "onCreateOptionsMenu: 调试模式,加载单元测试菜单");
|
||||
}
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
|
||||
return true;
|
||||
@@ -202,7 +189,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 菜单点击 | itemId=" + item.getItemId());
|
||||
LogUtils.d(TAG, "onOptionsItemSelected() | itemId=" + item.getItemId());
|
||||
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
|
||||
recreate();
|
||||
return true;
|
||||
@@ -213,27 +200,24 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 跳转设置页面");
|
||||
break;
|
||||
case R.id.action_battery_report:
|
||||
startActivity(new Intent(this, BatteryReportActivity.class));
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 跳转电池报告页面");
|
||||
break;
|
||||
case R.id.action_clearrecord:
|
||||
startActivity(new Intent(this, ClearRecordActivity.class));
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 跳转记录清理页面");
|
||||
break;
|
||||
case R.id.action_changepicture:
|
||||
startActivityForResult(new Intent(this, BackgroundSettingsActivity.class), REQUEST_READ_MEDIA_IMAGES);
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 跳转背景设置页面 | requestCode=" + REQUEST_READ_MEDIA_IMAGES);
|
||||
break;
|
||||
case R.id.action_unittestactivity:
|
||||
startActivity(new Intent(this, MainUnitTestActivity.class));
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 跳转单元测试页面");
|
||||
break;
|
||||
case R.id.action_unittest2activity:
|
||||
startActivity(new Intent(this, MainUnitTest2Activity.class));
|
||||
break;
|
||||
case R.id.action_about:
|
||||
startAboutActivity();
|
||||
LogUtils.d(TAG, "onOptionsItemSelected: 跳转关于页面");
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -244,41 +228,41 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
public void setupToolbar() {
|
||||
super.setupToolbar();
|
||||
LogUtils.d(TAG, "setupToolbar()");
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
LogUtils.d(TAG, "setupToolbar: 隐藏返回按钮");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
LogUtils.d(TAG, "onBackPressed()");
|
||||
moveTaskToBack(true);
|
||||
LogUtils.d(TAG, "onBackPressed: 应用退至后台");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent 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();
|
||||
}
|
||||
|
||||
private void initGlobalHandler() {
|
||||
LogUtils.d(TAG, "initGlobalHandler: 初始化全局Handler");
|
||||
LogUtils.d(TAG, "initGlobalHandler()");
|
||||
if (sGlobalHandler == null) {
|
||||
sGlobalHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
// 校验Activity状态,避免销毁后操作UI
|
||||
if (sMainActivity == null || sMainActivity.isFinishing() || sMainActivity.isDestroyed()) {
|
||||
LogUtils.w(TAG, "handleMessage: Activity已销毁,跳过消息 | what=" + msg.what);
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "handleMessage: 处理消息 | what=" + msg.what);
|
||||
LogUtils.d(TAG, "handleMessage() | what=" + msg.what);
|
||||
switch (msg.what) {
|
||||
case MSG_RELOAD_APPCONFIG:
|
||||
sMainActivity.updateViewData();
|
||||
@@ -299,30 +283,28 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
}
|
||||
};
|
||||
LogUtils.d(TAG, "initGlobalHandler: 全局Handler创建成功");
|
||||
}
|
||||
}
|
||||
|
||||
private void initMainContentView() {
|
||||
LogUtils.d(TAG, "initMainContentView: 初始化核心视图");
|
||||
LogUtils.d(TAG, "initMainContentView()");
|
||||
View rootView = findViewById(android.R.id.content);
|
||||
mMainContentView = new MainContentView(this, rootView, this);
|
||||
}
|
||||
|
||||
private void initCriticalView() {
|
||||
LogUtils.d(TAG, "initCriticalView: 初始化关键视图组件");
|
||||
LogUtils.d(TAG, "initCriticalView()");
|
||||
sMainActivity = this;
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(mToolbar);
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
|
||||
LogUtils.d(TAG, "initCriticalView: Toolbar样式设置完成");
|
||||
}
|
||||
mAdsViewStub = findViewById(R.id.stub_ads_banner);
|
||||
}
|
||||
|
||||
private void initCoreUtilsAsync() {
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 异步初始化核心工具类");
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync()");
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -336,7 +318,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
if (mServiceControlBean == null) {
|
||||
mServiceControlBean = new ControlCenterServiceBean(false);
|
||||
ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean);
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 本地无配置,创建默认禁用配置");
|
||||
}
|
||||
|
||||
// 根据配置启停服务
|
||||
@@ -348,7 +329,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
public void run() {
|
||||
ControlCenterService.startControlCenterService(getApplicationContext());
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 配置启用服务,启动服务");
|
||||
}
|
||||
});
|
||||
} else if (!isServiceEnable && isServiceAlive) {
|
||||
@@ -356,7 +336,6 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
@Override
|
||||
public void run() {
|
||||
ControlCenterService.stopControlCenterService(getApplicationContext());
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 配置禁用服务,停止服务");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -380,13 +359,12 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "initCoreUtilsAsync: 异步线程执行完成");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadNonCriticalViewDelayed() {
|
||||
LogUtils.d(TAG, "loadNonCriticalViewDelayed: 延迟加载非核心视图 | 延迟时长=" + DELAY_LOAD_NON_CRITICAL + "ms");
|
||||
LogUtils.d(TAG, "loadNonCriticalViewDelayed() | 延迟时长=" + DELAY_LOAD_NON_CRITICAL + "ms");
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -401,7 +379,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
// ======================== 视图操作方法 ========================
|
||||
private void loadAdsView() {
|
||||
LogUtils.d(TAG, "loadAdsView: 加载广告视图");
|
||||
LogUtils.d(TAG, "loadAdsView()");
|
||||
if (mAdsViewStub == null) {
|
||||
LogUtils.e(TAG, "loadAdsView: 广告ViewStub为空,加载失败");
|
||||
return;
|
||||
@@ -409,38 +387,34 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
if (mADsBannerView == null) {
|
||||
View adsView = mAdsViewStub.inflate();
|
||||
mADsBannerView = adsView.findViewById(R.id.adsbanner);
|
||||
LogUtils.d(TAG, "loadAdsView: 广告视图创建成功");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViewData() {
|
||||
LogUtils.d(TAG, "updateViewData: 更新视图数据");
|
||||
LogUtils.d(TAG, "updateViewData()");
|
||||
if (mMainContentView == null || mFrameDrawable == null) {
|
||||
LogUtils.e(TAG, "updateViewData: 核心视图或框架背景为空,更新失败");
|
||||
return;
|
||||
}
|
||||
mMainContentView.updateViewData(mFrameDrawable);
|
||||
LogUtils.d(TAG, "updateViewData: 视图数据更新完成");
|
||||
}
|
||||
|
||||
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) {
|
||||
mMainContentView.backgroundView.loadBackgroundBean(currentBgBean);
|
||||
LogUtils.d(TAG, "reloadBackground: 加载自定义背景成功");
|
||||
mMainContentView.backgroundView.loadByBackgroundBean(currentBgBean);
|
||||
} else {
|
||||
mMainContentView.backgroundView.setBackgroundResource(R.drawable.default_background);
|
||||
LogUtils.w(TAG, "reloadBackground: 无自定义背景,使用默认背景");
|
||||
}
|
||||
}
|
||||
|
||||
private void setMainLayoutBackgroundColor() {
|
||||
LogUtils.d(TAG, "setMainLayoutBackgroundColor: 设置主布局背景色");
|
||||
LogUtils.d(TAG, "setMainLayoutBackgroundColor()");
|
||||
if (isFinishing() || isDestroyed() || mMainContentView == null || mBgSourceUtils == null) {
|
||||
LogUtils.e(TAG, "setMainLayoutBackgroundColor: 上下文无效,设置失败");
|
||||
return;
|
||||
@@ -453,41 +427,34 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
private void updateServiceSwitchUI() {
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI: 更新服务开关UI状态");
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI()");
|
||||
if (mMainContentView == null || mServiceControlBean == null) {
|
||||
LogUtils.e(TAG, "updateServiceSwitchUI: 核心视图或服务配置为空,更新失败");
|
||||
return;
|
||||
}
|
||||
boolean configEnabled = mServiceControlBean.isEnableService();
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI: 服务配置启用状态=" + configEnabled);
|
||||
mMainContentView.setServiceSwitchEnabled(false);
|
||||
mMainContentView.setServiceSwitchChecked(configEnabled);
|
||||
mMainContentView.setServiceSwitchEnabled(true);
|
||||
LogUtils.d(TAG, "updateServiceSwitchUI: 服务开关UI状态更新完成");
|
||||
}
|
||||
|
||||
// ======================== 服务与线程管理方法 ========================
|
||||
private void toggleServiceEnableState(boolean isEnable) {
|
||||
LogUtils.d(TAG, "toggleServiceEnableState: 切换服务状态 | 目标状态=" + isEnable);
|
||||
LogUtils.d(TAG, "toggleServiceEnableState() | 目标状态=" + isEnable);
|
||||
if (mServiceControlBean == null) {
|
||||
LogUtils.e(TAG, "toggleServiceEnableState: 服务配置为空,切换失败");
|
||||
return;
|
||||
}
|
||||
mServiceControlBean.setIsEnableService(isEnable);
|
||||
ControlCenterServiceBean.saveBean(getApplicationContext(), mServiceControlBean);
|
||||
LogUtils.d(TAG, "toggleServiceEnableState: 服务配置已保存");
|
||||
|
||||
// UI开关联动服务启停
|
||||
if (isEnable) {
|
||||
// 开启:启动服务
|
||||
if (!ServiceUtils.isServiceAlive(getApplicationContext(), ControlCenterService.class.getName())) {
|
||||
ControlCenterService.startControlCenterService(getApplicationContext());
|
||||
LogUtils.d(TAG, "toggleServiceEnableState: 服务已启动");
|
||||
}
|
||||
} else {
|
||||
// 关闭:停止服务
|
||||
ControlCenterService.stopControlCenterService(getApplicationContext());
|
||||
LogUtils.d(TAG, "toggleServiceEnableState: 服务已停止");
|
||||
}
|
||||
|
||||
sGlobalHandler.sendEmptyMessage(MSG_UPDATE_SERVICE_SWITCH);
|
||||
@@ -495,7 +462,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
// ======================== 页面跳转方法 ========================
|
||||
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);
|
||||
@@ -504,14 +471,13 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
|
||||
// ======================== 消息发送方法 ========================
|
||||
private void notifyServiceAppConfigChange() {
|
||||
LogUtils.d(TAG, "notifyServiceAppConfigChange: 通知服务配置变更");
|
||||
LogUtils.d(TAG, "notifyServiceAppConfigChange()");
|
||||
ControlCenterService.sendAppConfigStatusUpdateMessage(this);
|
||||
reloadAppConfig();
|
||||
}
|
||||
|
||||
// ======================== 静态工具方法 ========================
|
||||
public static void reloadAppConfig() {
|
||||
LogUtils.d(TAG, "reloadAppConfig: 发送配置重载消息");
|
||||
LogUtils.d(TAG, "reloadAppConfig()");
|
||||
if (sGlobalHandler != null) {
|
||||
sGlobalHandler.sendEmptyMessage(MSG_RELOAD_APPCONFIG);
|
||||
} else {
|
||||
@@ -520,7 +486,7 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
}
|
||||
|
||||
public static void sendCurrentBatteryValueMessage(int value) {
|
||||
LogUtils.d(TAG, "sendCurrentBatteryValueMessage: 发送当前电量消息 | 电量=" + value);
|
||||
LogUtils.d(TAG, "sendCurrentBatteryValueMessage() | 电量=" + value);
|
||||
if (sGlobalHandler != null) {
|
||||
Message msg = sGlobalHandler.obtainMessage(MSG_CURRENTVALUEBATTERY);
|
||||
msg.arg1 = value;
|
||||
@@ -532,7 +498,7 @@ 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));
|
||||
@@ -551,31 +517,31 @@ public class MainActivity extends WinBoLLActivity implements MainContentView.OnV
|
||||
// ======================== MainContentView 事件回调 ========================
|
||||
@Override
|
||||
public void onChargeReminderSwitchChanged(boolean isChecked) {
|
||||
LogUtils.d(TAG, "onChargeReminderSwitchChanged: 充电提醒开关状态变更 | isChecked=" + isChecked);
|
||||
LogUtils.d(TAG, "onChargeReminderSwitchChanged() | isChecked=" + isChecked);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsageReminderSwitchChanged(boolean isChecked) {
|
||||
LogUtils.d(TAG, "onUsageReminderSwitchChanged: 耗电提醒开关状态变更 | isChecked=" + isChecked);
|
||||
LogUtils.d(TAG, "onUsageReminderSwitchChanged() | isChecked=" + isChecked);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceSwitchChanged(boolean isChecked) {
|
||||
LogUtils.d(TAG, "onServiceSwitchChanged: 服务总开关状态变更 | isChecked=" + isChecked);
|
||||
LogUtils.d(TAG, "onServiceSwitchChanged() | isChecked=" + isChecked);
|
||||
toggleServiceEnableState(isChecked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChargeReminderProgressChanged(int progress) {
|
||||
LogUtils.d(TAG, "onChargeReminderProgressChanged: 充电提醒阈值变更 | progress=" + progress);
|
||||
LogUtils.d(TAG, "onChargeReminderProgressChanged() | progress=" + progress);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsageReminderProgressChanged(int progress) {
|
||||
LogUtils.d(TAG, "onUsageReminderProgressChanged: 耗电提醒阈值变更 | progress=" + progress);
|
||||
LogUtils.d(TAG, "onUsageReminderProgressChanged() | progress=" + progress);
|
||||
notifyServiceAppConfigChange();
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import cc.winboll.studio.powerbell.utils.ImageDownloader;
|
||||
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
@@ -52,7 +53,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
|
||||
|
||||
// 按钮点击回调接口(Java7 接口实现)
|
||||
public interface OnDialogClickListener {
|
||||
void onConfirm(String szConfirmFilePath, String previewFileUrl); // 确认按钮点击
|
||||
void onConfirm(String szConfirmFilePath); // 确认按钮点击
|
||||
void onCancel(); // 取消按钮点击
|
||||
}
|
||||
|
||||
@@ -91,7 +92,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
|
||||
case MSG_IMAGE_LOAD_SUCCESS:
|
||||
// 图片加载成功,获取文件路径并设置背景
|
||||
mDownloadSavedPath = (String) msg.obj;
|
||||
previewBackground(mDownloadSavedPath);
|
||||
mBackgroundView.loadImage(mDownloadSavedPath);
|
||||
break;
|
||||
case MSG_IMAGE_LOAD_FAILED:
|
||||
// 图片加载失败,设置默认背景
|
||||
@@ -139,7 +140,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
|
||||
etURL = (EditText) dialogView.findViewById(R.id.et_url);
|
||||
mBackgroundView = (BackgroundView) dialogView.findViewById(R.id.bv_background_preview);
|
||||
// 加载初始图片
|
||||
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
|
||||
mBackgroundView.setBackgroundResource(R.drawable.blank100x100);
|
||||
// 设置按钮点击事件
|
||||
setButtonClickListeners();
|
||||
}
|
||||
@@ -168,13 +169,13 @@ public class NetworkBackgroundDialog extends AlertDialog {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d("NetworkBackgroundDialog", "确认按钮点击");
|
||||
// 确定预览背景资源
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
|
||||
utils.saveFileToPreviewBean(new File(mPreviewFilePath), mPreviewFileUrl);
|
||||
|
||||
dismiss(); // 关闭对话框
|
||||
if(TextUtils.isEmpty(mDownloadSavedPath)) {
|
||||
ToastUtils.show("未下载图片。");
|
||||
return;
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onConfirm(mPreviewFilePath, mPreviewFileUrl);
|
||||
listener.onConfirm(mDownloadSavedPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -207,7 +208,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
|
||||
mPreviewFilePath = previewFilePath;
|
||||
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(mContext);
|
||||
utils.saveFileToPreviewBean(new File(mPreviewFilePath), mPreviewFileUrl);
|
||||
mBackgroundView.loadBackgroundBean(utils.getPreviewBackgroundBean());
|
||||
mBackgroundView.loadByBackgroundBean(utils.getPreviewBackgroundBean());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
mBackgroundView.setBackgroundResource(R.drawable.ic_launcher);
|
||||
@@ -258,6 +259,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
|
||||
// 发送消息到主线程,携带图片路径
|
||||
Message successMsg = mUiHandler.obtainMessage(MSG_IMAGE_LOAD_SUCCESS, savePath);
|
||||
mUiHandler.sendMessage(successMsg);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -222,10 +222,14 @@ public class RemindThread extends Thread {
|
||||
} else if (!isCharging && isEnableUsageReminder && quantityOfElectricity <= usageReminderValue) {
|
||||
LogUtils.d(TAG, "触发耗电提醒 | 当前电量=" + quantityOfElectricity + " ≤ 阈值=" + usageReminderValue + " | threadId=" + getId());
|
||||
sendNotificationMessageInternal(REMIND_TYPE_USAGE, quantityOfElectricity, isCharging);
|
||||
}
|
||||
|
||||
// 安全休眠,保留中断标记
|
||||
} else {
|
||||
// 未有合适类型提醒,退出提醒线程
|
||||
LogUtils.d(TAG, "未有合适类型提醒,退出提醒线程");
|
||||
break;
|
||||
}
|
||||
// 安全休眠,保留中断标记
|
||||
safeSleepInternal(sleepTime);
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "循环运行异常,退出电量提醒线程 | 当前电量=" + quantityOfElectricity + " | threadId=" + getId(), e);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
package cc.winboll.studio.powerbell.unittest;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
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.BackgroundBean;
|
||||
import cc.winboll.studio.powerbell.utils.FileUtils;
|
||||
import cc.winboll.studio.powerbell.utils.ImageCropUtils;
|
||||
import cc.winboll.studio.powerbell.views.BackgroundView;
|
||||
import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/22 08:31
|
||||
* @Describe MainUnitTest2Activity
|
||||
*/
|
||||
public class MainUnitTest2Activity extends AppCompatActivity {
|
||||
// ====================== 常量定义 ======================
|
||||
public static final String TAG = "MainUnitTest2Activity";
|
||||
public static final int REQUEST_CROP_IMAGE = 0;
|
||||
private static final String ASSETS_TEST_IMAGE_PATH = "unittest/unittest-miku.png";
|
||||
|
||||
// ====================== 成员变量(移除所有Uri相关) ======================
|
||||
private MemoryCachedBackgroundView mMemoryCachedBackgroundView;
|
||||
private String mAppPrivateDirPath;
|
||||
private File mPrivateTestImageFile; // 仅用File,不用Uri
|
||||
private File mPrivateCropImageFile;
|
||||
BackgroundBean mPreviewBackgroundBean;
|
||||
LinearLayout mllBackgroundView;
|
||||
|
||||
// ====================== 生命周期方法 ======================
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "=== 页面 onCreate 启动 ===");
|
||||
|
||||
initBaseParams();
|
||||
initViewAndEvent();
|
||||
copyAssetsTestImageToPrivateDir();
|
||||
//loadBackgroundByFile(); // 直接用File加载
|
||||
mPreviewBackgroundBean = new BackgroundBean();
|
||||
mPreviewBackgroundBean.setBackgroundFileName(mPrivateTestImageFile.getName());
|
||||
mPreviewBackgroundBean.setBackgroundFilePath(mPrivateTestImageFile.getAbsolutePath());
|
||||
mPreviewBackgroundBean.setBackgroundScaledCompressFileName(mPrivateCropImageFile.getName());
|
||||
mPreviewBackgroundBean.setBackgroundScaledCompressFilePath(mPrivateCropImageFile.getAbsolutePath());
|
||||
mPreviewBackgroundBean.setIsUseBackgroundFile(true);
|
||||
doubleRefreshPreview();
|
||||
|
||||
ToastUtils.show("单元测试页面启动完成");
|
||||
LogUtils.d(TAG, "=== 页面 onCreate 初始化结束 ===");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LogUtils.d(TAG, "=== onActivityResult 回调 ===");
|
||||
if (requestCode == REQUEST_CROP_IMAGE) {
|
||||
handleCropResult(resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 初始化相关方法 ======================
|
||||
private void initBaseParams() {
|
||||
LogUtils.d(TAG, "初始化基础参数:工具类+私有目录+File");
|
||||
|
||||
// 私有目录(无需权限,无UID冲突)
|
||||
mAppPrivateDirPath = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath() + "/PowerBellTest/";
|
||||
File privateDir = new File(mAppPrivateDirPath);
|
||||
if (!privateDir.exists()) {
|
||||
privateDir.mkdirs();
|
||||
LogUtils.d(TAG, "创建私有目录:" + mAppPrivateDirPath);
|
||||
}
|
||||
|
||||
// 初始化File(无Uri)
|
||||
File refFile = new File(ASSETS_TEST_IMAGE_PATH);
|
||||
String uniqueTestName = FileUtils.createUniqueFileName(refFile) + ".png";
|
||||
String uniqueCropName = uniqueTestName.replace(".png", "_crop.png");
|
||||
mPrivateTestImageFile = new File(mAppPrivateDirPath, uniqueTestName);
|
||||
mPrivateCropImageFile = new File(mAppPrivateDirPath, uniqueCropName);
|
||||
|
||||
LogUtils.d(TAG, "测试图File路径:" + mPrivateTestImageFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private void initViewAndEvent() {
|
||||
LogUtils.d(TAG, "初始化布局与控件事件");
|
||||
setContentView(R.layout.activity_mainunittest2);
|
||||
mllBackgroundView = (LinearLayout) findViewById(R.id.ll_backgroundview);
|
||||
mMemoryCachedBackgroundView = MemoryCachedBackgroundView.getInstance(this, "", false);
|
||||
mllBackgroundView.addView(mMemoryCachedBackgroundView);
|
||||
|
||||
//mMemoryCachedBackgroundView = (BackgroundView) findViewById(R.id.backgroundview);
|
||||
|
||||
// 跳转主页面按钮
|
||||
Button btnMain = (Button) findViewById(R.id.btn_main_activity);
|
||||
btnMain.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "点击按钮:跳转主页面");
|
||||
startActivity(new Intent(MainUnitTest2Activity.this, MainActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
// 裁剪按钮(直接用File路径启动,无Uri)
|
||||
Button btnCrop = (Button) findViewById(R.id.btn_test_cropimage);
|
||||
btnCrop.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "点击按钮:启动裁剪(File路径版)");
|
||||
ToastUtils.show("准备启动图片裁剪");
|
||||
|
||||
if (mPrivateTestImageFile.exists() && mPrivateTestImageFile.length() > 100) {
|
||||
startCropTestByFile(); // 直接传File
|
||||
} else {
|
||||
ToastUtils.show("测试图片未准备好,重新拷贝");
|
||||
copyAssetsTestImageToPrivateDir();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 从assets拷贝图片(不变,确保File存在)
|
||||
private void copyAssetsTestImageToPrivateDir() {
|
||||
LogUtils.d(TAG, "开始拷贝assets图片到私有目录");
|
||||
if (mPrivateTestImageFile.exists() && mPrivateTestImageFile.length() > 100) {
|
||||
LogUtils.d(TAG, "图片已存在,无需拷贝");
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = getAssets().open(ASSETS_TEST_IMAGE_PATH);
|
||||
FileUtils.copyStreamToFile(inputStream, mPrivateTestImageFile);
|
||||
LogUtils.d(TAG, "图片拷贝成功,大小:" + mPrivateTestImageFile.length() + "字节");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "图片拷贝失败:" + e.getMessage(), e);
|
||||
ToastUtils.show("图片准备失败");
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "关闭流失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== 核心业务方法(全改为File路径) ======================
|
||||
/** 直接用File路径加载背景图(无Uri,无冲突) */
|
||||
// private void loadBackgroundByFile() {
|
||||
// LogUtils.d(TAG, "开始加载背景图(File路径版)");
|
||||
// if (mPrivateTestImageFile.exists() && mPrivateTestImageFile.length() > 100) {
|
||||
// mBackgroundView.loadImage(mPrivateTestImageFile.getAbsolutePath()); // 直接传路径
|
||||
// LogUtils.d(TAG, "背景图加载成功:" + mPrivateTestImageFile.getAbsolutePath());
|
||||
// ToastUtils.show("背景图加载成功");
|
||||
// } else {
|
||||
// LogUtils.e(TAG, "背景图加载失败:文件无效");
|
||||
// ToastUtils.show("背景图加载失败");
|
||||
// }
|
||||
// }
|
||||
|
||||
/** 直接用File启动裁剪(关键:调用ImageCropUtils的File重载方法) */
|
||||
private void startCropTestByFile() {
|
||||
LogUtils.d(TAG, "启动裁剪(File路径版),原图:" + mPrivateTestImageFile.getAbsolutePath());
|
||||
|
||||
// 确保输出目录存在
|
||||
File cropParent = mPrivateCropImageFile.getParentFile();
|
||||
if (!cropParent.exists()) {
|
||||
cropParent.mkdirs();
|
||||
}
|
||||
|
||||
// 调用ImageCropUtils的File参数方法(核心:绕开Uri)
|
||||
ImageCropUtils.startImageCrop(
|
||||
this,
|
||||
mPrivateTestImageFile, // 原图File
|
||||
mPrivateCropImageFile, // 输出File
|
||||
0,
|
||||
0,
|
||||
true,
|
||||
REQUEST_CROP_IMAGE
|
||||
);
|
||||
|
||||
LogUtils.d(TAG, "裁剪请求已发送,输出路径:" + mPrivateCropImageFile.getAbsolutePath());
|
||||
ToastUtils.show("已启动图片裁剪");
|
||||
}
|
||||
|
||||
/** 处理裁剪结果(直接校验输出File) */
|
||||
private void handleCropResult(int resultCode) {
|
||||
LogUtils.d(TAG, "裁剪回调处理:resultCode=" + resultCode);
|
||||
if (resultCode == RESULT_OK) {
|
||||
if (mPrivateCropImageFile.exists() && mPrivateCropImageFile.length() > 100) {
|
||||
mMemoryCachedBackgroundView.loadImage(mPrivateCropImageFile.getAbsolutePath());
|
||||
LogUtils.d(TAG, "裁剪成功,加载裁剪图:" + mPrivateCropImageFile.getAbsolutePath());
|
||||
ToastUtils.show("裁剪成功");
|
||||
mPreviewBackgroundBean.setIsUseBackgroundScaledCompressFile(true);
|
||||
doubleRefreshPreview();
|
||||
} else {
|
||||
LogUtils.e(TAG, "裁剪成功但输出文件无效");
|
||||
ToastUtils.show("裁剪失败:输出文件无效");
|
||||
}
|
||||
} else if (resultCode == RESULT_CANCELED) {
|
||||
LogUtils.d(TAG, "裁剪取消");
|
||||
ToastUtils.show("裁剪已取消");
|
||||
} else {
|
||||
LogUtils.e(TAG, "裁剪失败:resultCode异常");
|
||||
ToastUtils.show("裁剪失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 双重刷新预览,确保背景加载最新数据
|
||||
* 移除:缓存清空逻辑
|
||||
*/
|
||||
private void doubleRefreshPreview() {
|
||||
|
||||
// 第一重刷新
|
||||
try {
|
||||
mMemoryCachedBackgroundView.loadByBackgroundBean(mPreviewBackgroundBean, true);
|
||||
mMemoryCachedBackgroundView.setBackgroundColor(mPreviewBackgroundBean.getPixelColor());
|
||||
LogUtils.d(TAG, "【双重刷新】第一重完成");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【双重刷新】第一重异常:" + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// 第二重刷新(延迟执行)
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mMemoryCachedBackgroundView != null && !isFinishing()) {
|
||||
try {
|
||||
mMemoryCachedBackgroundView.loadByBackgroundBean(mPreviewBackgroundBean, true);
|
||||
mMemoryCachedBackgroundView.setBackgroundColor(mPreviewBackgroundBean.getPixelColor());
|
||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【双重刷新】第二重异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ public class MainUnitTestActivity extends AppCompatActivity {
|
||||
|
||||
// 第一重刷新
|
||||
try {
|
||||
mBackgroundView.loadBackgroundBean(mPreviewBackgroundBean, true);
|
||||
mBackgroundView.loadByBackgroundBean(mPreviewBackgroundBean, true);
|
||||
mBackgroundView.setBackgroundColor(mPreviewBackgroundBean.getPixelColor());
|
||||
LogUtils.d(TAG, "【双重刷新】第一重完成");
|
||||
} catch (Exception e) {
|
||||
@@ -235,7 +235,7 @@ public class MainUnitTestActivity extends AppCompatActivity {
|
||||
public void run() {
|
||||
if (mBackgroundView != null && !isFinishing()) {
|
||||
try {
|
||||
mBackgroundView.loadBackgroundBean(mPreviewBackgroundBean, true);
|
||||
mBackgroundView.loadByBackgroundBean(mPreviewBackgroundBean, true);
|
||||
mBackgroundView.setBackgroundColor(mPreviewBackgroundBean.getPixelColor());
|
||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cc.winboll.studio.powerbell.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
@@ -11,6 +12,7 @@ import androidx.core.content.FileProvider;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.powerbell.BuildConfig;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.BackgroundBean;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
@@ -181,37 +183,42 @@ public class BackgroundSourceUtils {
|
||||
/**
|
||||
* 检查背景是否为空并创建空白背景Bean
|
||||
*/
|
||||
boolean checkEmptyBackgroundAndCreateBlankBackgroundBean(BackgroundBean checkBackgroundBean) {
|
||||
public boolean checkEmptyBackgroundAndCreateBlankBackgroundBean(BackgroundBean checkBackgroundBean) {
|
||||
LogUtils.d(TAG, "【checkEmptyBackgroundAndCreateBlankBackgroundBean调用】开始检查背景Bean");
|
||||
File fCheckBackgroundFile = new File(checkBackgroundBean.getBackgroundFilePath());
|
||||
if (!fCheckBackgroundFile.exists()) {
|
||||
String newCropFileName = genNewCropFileName();
|
||||
String fileSuffix = "png";
|
||||
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||
|
||||
AssetsCopyUtils.copyAssetsFileToDir(mContext, "images/blank10x10.png", mCropSourceFile.getAbsolutePath());
|
||||
try {
|
||||
mCropResultFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
previewBackgroundBean.setIsUseBackgroundFile(true);
|
||||
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(false);
|
||||
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
|
||||
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
|
||||
saveSettings();
|
||||
LogUtils.d(TAG, "背景Bean为空,已创建空白背景并更新配置");
|
||||
return true;
|
||||
return createBlankBackgroundBean(checkBackgroundBean.getPixelColor());
|
||||
}
|
||||
LogUtils.d(TAG, "背景Bean文件存在,无需创建空白背景");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean createBlankBackgroundBean(int nBackgroundPixelColor) {
|
||||
String newCropFileName = genNewCropFileName();
|
||||
String fileSuffix = "png";
|
||||
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
|
||||
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
|
||||
|
||||
AssetsCopyUtils.copyAssetsFileToDir(mContext, "images/blank100x100.png", mCropSourceFile.getAbsolutePath());
|
||||
try {
|
||||
mCropResultFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
previewBackgroundBean.setPixelColor(nBackgroundPixelColor);
|
||||
previewBackgroundBean.setIsUseBackgroundFile(true);
|
||||
previewBackgroundBean.setIsUseBackgroundScaledCompressFile(false);
|
||||
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
|
||||
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
|
||||
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
|
||||
saveSettings();
|
||||
LogUtils.d(TAG, "背景Bean为空,已创建空白背景并更新配置");
|
||||
return true;
|
||||
}
|
||||
|
||||
String genNewCropFileName() {
|
||||
return UUID.randomUUID().toString() + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@@ -4,25 +4,33 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/11 01:57
|
||||
* @Describe 单例 Bitmap 缓存工具类(Java 7 兼容)
|
||||
* @Describe 单例 Bitmap 缓存工具类(Java 7 兼容)- 强制缓存版
|
||||
* 功能:内存缓存 Bitmap,支持路径关联缓存、全局获取、缓存清空、SP 持久化最后缓存路径、构造时预加载
|
||||
* 特点:1. 单例模式 2. 压缩加载避免OOM 3. 路径-Bitmap 映射 4. 线程安全 5. SP 持久化最后缓存路径 6. 构造时预加载
|
||||
* 特点:1. 单例模式 2. 硬引用唯一缓存(强制保持,内存紧张不回收) 3. 路径-Bitmap 映射 4. 线程安全
|
||||
* 5. SP 持久化最后缓存路径 6. 构造时预加载 7. 引用计数防误回收 8. 高压缩比减少OOM风险
|
||||
* 核心策略:无论内存如何紧张,强制保持已缓存的Bitmap,通过高压缩比降低单张Bitmap内存占用
|
||||
*/
|
||||
public class BitmapCacheUtils {
|
||||
public static final String TAG = "BitmapCacheUtils";
|
||||
// 最大图片尺寸(适配1080P屏幕,可根据需求调整)
|
||||
private static final int MAX_WIDTH = 1080;
|
||||
private static final int MAX_HEIGHT = 1920;
|
||||
// 最大图片尺寸(降低至720P,进一步减少内存占用,强制缓存核心策略)
|
||||
private static final int MAX_WIDTH = 720;
|
||||
private static final int MAX_HEIGHT = 1280;
|
||||
|
||||
// SP 相关常量
|
||||
private static final String SP_NAME = "BitmapCacheSP";
|
||||
@@ -30,18 +38,24 @@ public class BitmapCacheUtils {
|
||||
|
||||
// 单例实例(volatile 保证多线程可见性)
|
||||
private static volatile BitmapCacheUtils sInstance;
|
||||
// 路径-Bitmap 缓存容器(内存缓存)
|
||||
private final Map<String, Bitmap> mBitmapCacheMap;
|
||||
// 路径-Bitmap 硬引用缓存(唯一缓存,强制保持,内存紧张不回收)
|
||||
private final Map<String, Bitmap> mHardCacheMap;
|
||||
// 路径-引用计数 映射(解决多实例共享问题)
|
||||
private final Map<String, Integer> mRefCountMap;
|
||||
// SP 实例(用于持久化最后缓存路径)
|
||||
private final SharedPreferences mSp;
|
||||
|
||||
// 私有构造器(单例模式)
|
||||
private BitmapCacheUtils() {
|
||||
mBitmapCacheMap = new HashMap<>();
|
||||
// 使用ConcurrentHashMap保证线程安全,避免手动同步
|
||||
mHardCacheMap = new ConcurrentHashMap<>();
|
||||
mRefCountMap = new ConcurrentHashMap<>();
|
||||
// 初始化 SP(使用 App 全局上下文,避免内存泄漏)
|
||||
mSp = App.getInstance().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
// 构造时自动预加载 SP 中保存的最后一次缓存路径的图片
|
||||
preloadLastCachedBitmap();
|
||||
// 注册内存状态监听(仅记录日志,不清理缓存)
|
||||
registerMemoryStatusListener();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +72,28 @@ public class BitmapCacheUtils {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 补充接口:直接缓存已解码的Bitmap(适配BackgroundView改进需求)
|
||||
* @param imagePath 图片绝对路径
|
||||
* @param bitmap 已解码的有效Bitmap
|
||||
* @return 缓存后的Bitmap / null(参数无效)
|
||||
*/
|
||||
public Bitmap cacheBitmap(String imagePath, Bitmap bitmap) {
|
||||
if (TextUtils.isEmpty(imagePath) || !isBitmapValid(bitmap)) {
|
||||
LogUtils.e(TAG, "cacheBitmap: 路径或Bitmap无效");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 强制存入硬引用缓存,不转软引用
|
||||
mHardCacheMap.put(imagePath, bitmap);
|
||||
// 初始化引用计数为1
|
||||
mRefCountMap.put(imagePath, 1);
|
||||
// 持久化当前路径到 SP
|
||||
saveLastCachePathToSp(imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 直接缓存已解码Bitmap成功(强制保持) - " + imagePath);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心接口:根据图片路径缓存 Bitmap 到内存,并持久化路径到 SP
|
||||
* @param imagePath 图片绝对路径
|
||||
@@ -76,29 +112,26 @@ public class BitmapCacheUtils {
|
||||
}
|
||||
|
||||
// 已缓存则直接返回,避免重复加载
|
||||
if (mBitmapCacheMap.containsKey(imagePath)) {
|
||||
Bitmap cachedBitmap = mBitmapCacheMap.get(imagePath);
|
||||
// 额外校验缓存的Bitmap是否有效
|
||||
if (cachedBitmap != null && !cachedBitmap.isRecycled()) {
|
||||
LogUtils.d(TAG, "cacheBitmap: 图片已缓存,直接返回 - " + imagePath);
|
||||
// 持久化当前路径到 SP(更新最后缓存路径)
|
||||
saveLastCachePathToSp(imagePath);
|
||||
return cachedBitmap;
|
||||
} else {
|
||||
// 缓存的Bitmap已失效,移除后重新加载
|
||||
mBitmapCacheMap.remove(imagePath);
|
||||
LogUtils.w(TAG, "cacheBitmap: 缓存Bitmap已失效,移除后重新加载 - " + imagePath);
|
||||
}
|
||||
Bitmap hardCacheBitmap = mHardCacheMap.get(imagePath);
|
||||
if (isBitmapValid(hardCacheBitmap)) {
|
||||
LogUtils.d(TAG, "cacheBitmap: 硬引用缓存命中,引用计数+1 - " + imagePath);
|
||||
// 引用计数+1
|
||||
increaseRefCount(imagePath);
|
||||
// 持久化当前路径到 SP
|
||||
saveLastCachePathToSp(imagePath);
|
||||
return hardCacheBitmap;
|
||||
}
|
||||
|
||||
// 压缩加载 Bitmap(避免OOM)
|
||||
// 高压缩比加载 Bitmap(强制缓存核心:通过降低分辨率减少单张Bitmap内存占用)
|
||||
Bitmap bitmap = decodeCompressedBitmap(imagePath);
|
||||
if (bitmap != null) {
|
||||
// 存入缓存容器
|
||||
mBitmapCacheMap.put(imagePath, bitmap);
|
||||
// 持久化当前路径到 SP(更新最后缓存路径)
|
||||
// 强制存入硬引用缓存,不转软引用
|
||||
mHardCacheMap.put(imagePath, bitmap);
|
||||
// 初始化引用计数为1
|
||||
mRefCountMap.put(imagePath, 1);
|
||||
// 持久化当前路径到 SP
|
||||
saveLastCachePathToSp(imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径 - " + imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径(强制保持) - " + imagePath);
|
||||
} else {
|
||||
LogUtils.e(TAG, "cacheBitmap: 图片解码失败 - " + imagePath);
|
||||
}
|
||||
@@ -114,46 +147,103 @@ public class BitmapCacheUtils {
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
return null;
|
||||
}
|
||||
Bitmap bitmap = mBitmapCacheMap.get(imagePath);
|
||||
// 校验Bitmap是否有效
|
||||
if (bitmap != null && bitmap.isRecycled()) {
|
||||
mBitmapCacheMap.remove(imagePath);
|
||||
return null;
|
||||
|
||||
// 仅从硬引用缓存获取,无软引用 fallback
|
||||
Bitmap hardCacheBitmap = mHardCacheMap.get(imagePath);
|
||||
if (isBitmapValid(hardCacheBitmap)) {
|
||||
return hardCacheBitmap;
|
||||
}
|
||||
return bitmap;
|
||||
|
||||
// 缓存未命中或Bitmap已失效
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有 Bitmap 缓存(释放内存),并清空 SP 中保存的最后缓存路径
|
||||
* 新增接口:增加指定路径Bitmap的引用计数
|
||||
* @param imagePath 图片绝对路径
|
||||
*/
|
||||
public void increaseRefCount(String imagePath) {
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
return;
|
||||
}
|
||||
synchronized (mRefCountMap) {
|
||||
Integer count = mRefCountMap.get(imagePath);
|
||||
if (count == null) {
|
||||
mRefCountMap.put(imagePath, 1);
|
||||
} else {
|
||||
mRefCountMap.put(imagePath, count + 1);
|
||||
}
|
||||
LogUtils.d(TAG, "increaseRefCount: " + imagePath + " 引用计数变为 " + mRefCountMap.get(imagePath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增接口:减少指定路径Bitmap的引用计数,计数为0时仅标记不回收(强制缓存策略)
|
||||
* @param imagePath 图片绝对路径
|
||||
*/
|
||||
public void decreaseRefCount(String imagePath) {
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
return;
|
||||
}
|
||||
synchronized (mRefCountMap) {
|
||||
Integer count = mRefCountMap.get(imagePath);
|
||||
if (count == null || count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int newCount = count - 1;
|
||||
if (newCount <= 0) {
|
||||
// 强制缓存策略:引用计数为0时仅移除计数,不回收Bitmap
|
||||
mRefCountMap.remove(imagePath);
|
||||
LogUtils.d(TAG, "decreaseRefCount: " + imagePath + " 引用计数为0,保留Bitmap(强制缓存)");
|
||||
} else {
|
||||
mRefCountMap.put(imagePath, newCount);
|
||||
LogUtils.d(TAG, "decreaseRefCount: " + imagePath + " 引用计数变为 " + newCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有 Bitmap 缓存(仅手动调用时执行,内存紧张时不自动执行)
|
||||
*/
|
||||
public void clearAllCache() {
|
||||
synchronized (mBitmapCacheMap) {
|
||||
for (Bitmap bitmap : mBitmapCacheMap.values()) {
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle(); // 主动回收 Bitmap
|
||||
}
|
||||
LogUtils.d(TAG, "clearAllCache: 手动清空所有缓存(强制缓存策略:仅手动触发)");
|
||||
|
||||
// 清空硬引用缓存并回收Bitmap
|
||||
for (Bitmap bitmap : mHardCacheMap.values()) {
|
||||
if (isBitmapValid(bitmap)) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
mBitmapCacheMap.clear();
|
||||
}
|
||||
mHardCacheMap.clear();
|
||||
|
||||
// 清空引用计数
|
||||
mRefCountMap.clear();
|
||||
|
||||
// 清空 SP 中保存的最后缓存路径
|
||||
clearLastCachePathInSp();
|
||||
LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空,SP 路径已清除");
|
||||
|
||||
LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空");
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定路径的 Bitmap 缓存
|
||||
* 移除指定路径的 Bitmap 缓存(仅手动调用时执行,内存紧张时不自动执行)
|
||||
* @param imagePath 图片绝对路径
|
||||
*/
|
||||
public void removeCachedBitmap(String imagePath) {
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
return;
|
||||
}
|
||||
synchronized (mBitmapCacheMap) {
|
||||
Bitmap bitmap = mBitmapCacheMap.remove(imagePath);
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
LogUtils.d(TAG, "removeCachedBitmap: 移除并回收缓存 - " + imagePath);
|
||||
|
||||
synchronized (mRefCountMap) {
|
||||
// 手动移除时才回收Bitmap
|
||||
Bitmap hardBitmap = mHardCacheMap.remove(imagePath);
|
||||
if (isBitmapValid(hardBitmap)) {
|
||||
hardBitmap.recycle();
|
||||
LogUtils.d(TAG, "removeCachedBitmap: 手动回收硬引用缓存 - " + imagePath);
|
||||
}
|
||||
mRefCountMap.remove(imagePath);
|
||||
|
||||
// 若移除的是最后缓存的路径,清空 SP
|
||||
String lastPath = getLastCachePathFromSp();
|
||||
if (imagePath.equals(lastPath)) {
|
||||
@@ -164,7 +254,7 @@ public class BitmapCacheUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩解码 Bitmap(按最大尺寸缩放,避免OOM)
|
||||
* 高压缩比解码 Bitmap(强制缓存核心:通过降低分辨率+RGB_565减少单张Bitmap内存占用)
|
||||
* @param imagePath 图片绝对路径
|
||||
* @return 解码后的 Bitmap / null(文件无效/解码失败)
|
||||
*/
|
||||
@@ -187,20 +277,21 @@ public class BitmapCacheUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 计算缩放比例
|
||||
int sampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);
|
||||
// 计算高压缩比缩放比例(强制缓存核心:尽可能降低分辨率)
|
||||
int sampleSize = calculateHighCompressSampleSize(options, MAX_WIDTH, MAX_HEIGHT);
|
||||
|
||||
// 第二步:加载压缩后的 Bitmap
|
||||
// 第二步:加载高压缩比的 Bitmap
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = sampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存(比ARGB_8888少一半内存)
|
||||
options.inPurgeable = true;
|
||||
options.inInputShareable = true;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565; // 强制使用RGB_565,比ARGB_8888少一半内存
|
||||
options.inPurgeable = false; // 关闭可清除标志,强制保持内存
|
||||
options.inInputShareable = false;
|
||||
|
||||
try {
|
||||
return BitmapFactory.decodeFile(imagePath, options);
|
||||
} catch (OutOfMemoryError e) {
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: OOM异常 - " + imagePath);
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: OOM异常(已启用高压缩比) - " + imagePath);
|
||||
// 强制缓存策略:OOM时仅记录日志,不清理已缓存的Bitmap
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: 解码异常 - " + imagePath, e);
|
||||
@@ -209,23 +300,29 @@ public class BitmapCacheUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 Bitmap 缩放比例
|
||||
* 计算高压缩比缩放比例(强制缓存核心:优先保证不超过最大尺寸,尽可能压缩)
|
||||
*/
|
||||
private int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
|
||||
private int calculateHighCompressSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
|
||||
int rawWidth = options.outWidth;
|
||||
int rawHeight = options.outHeight;
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (rawWidth > maxWidth || rawHeight > maxHeight) {
|
||||
int halfWidth = rawWidth / 2;
|
||||
int halfHeight = rawHeight / 2;
|
||||
while ((halfWidth / inSampleSize) >= maxWidth && (halfHeight / inSampleSize) >= maxHeight) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
// 高压缩比逻辑:只要超过最大尺寸,就持续放大采样率
|
||||
while (rawWidth / inSampleSize > maxWidth || rawHeight / inSampleSize > maxHeight) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "calculateHighCompressSampleSize: 高压缩比缩放比例为 " + inSampleSize);
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:判断Bitmap是否有效(非空且未被回收)
|
||||
*/
|
||||
private boolean isBitmapValid(Bitmap bitmap) {
|
||||
return bitmap != null && !bitmap.isRecycled();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 SP 中获取最后一次缓存的图片路径
|
||||
* @return 最后缓存的路径 / null(未保存)
|
||||
@@ -266,12 +363,44 @@ public class BitmapCacheUtils {
|
||||
// 调用 cacheBitmap 预加载(内部已做文件校验和缓存判断)
|
||||
Bitmap bitmap = cacheBitmap(lastPath);
|
||||
if (bitmap != null) {
|
||||
LogUtils.d(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径成功 - " + lastPath);
|
||||
LogUtils.d(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径成功(强制保持) - " + lastPath);
|
||||
} else {
|
||||
LogUtils.w(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径失败,清空无效路径 - " + lastPath);
|
||||
// 预加载失败,清空 SP 中无效路径
|
||||
clearLastCachePathInSp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册内存状态监听(仅记录日志,不清理缓存,强制缓存策略)
|
||||
*/
|
||||
private void registerMemoryStatusListener() {
|
||||
if (Build.VERSION.SDK_INT >= 14) {
|
||||
App.getInstance().registerComponentCallbacks(new MemoryStatusCallback());
|
||||
LogUtils.d(TAG, "registerMemoryStatusListener: 内存状态监听已注册(仅记录日志,不清理缓存)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存状态回调(仅记录日志,不清理缓存,强制缓存策略)
|
||||
*/
|
||||
private class MemoryStatusCallback implements android.content.ComponentCallbacks2 {
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
// 强制缓存策略:内存紧张时仅记录日志,不清理任何缓存
|
||||
LogUtils.w(TAG, "onTrimMemory: 内存紧张级别 - " + level + ",强制保持所有Bitmap缓存");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
// 强制缓存策略:低内存时仅记录日志,不清理任何缓存
|
||||
LogUtils.w(TAG, "onLowMemory: 系统低内存,强制保持所有Bitmap缓存(已启用高压缩比)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(android.content.res.Configuration newConfig) {
|
||||
// 配置变化时无需处理
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.NotificationMessage;
|
||||
|
||||
/**
|
||||
* 通知工具类:统一管理前台服务/电池提醒通知
|
||||
* 通知工具类:统一管理前台服务/电池提醒/应用配置信息通知
|
||||
* 适配:API19-30 | Java7 | 小米手机
|
||||
* 特性:前台服务无铃声、提醒通知系统默认铃声、API分级适配、内存泄漏防护
|
||||
* 特性:前台服务无铃声、提醒通知系统默认铃声、配置通知低优先级无打扰、API分级适配、内存泄漏防护
|
||||
*/
|
||||
public class NotificationManagerUtils {
|
||||
// ================================== 静态常量(置顶统一管理,杜绝魔法值)=================================
|
||||
@@ -30,9 +30,11 @@ public class NotificationManagerUtils {
|
||||
// 通知渠道ID(API26+ 必需,区分通知类型)
|
||||
public static final String CHANNEL_ID_FOREGROUND = "cc.winboll.studio.powerbell.channel.foreground";
|
||||
public static final String CHANNEL_ID_REMIND = "cc.winboll.studio.powerbell.channel.remind";
|
||||
public static final String CHANNEL_ID_CONFIG = "cc.winboll.studio.powerbell.channel.config"; // 新增:应用配置信息渠道
|
||||
// 通知ID(唯一标识,避免重复)
|
||||
public static final int NOTIFY_ID_FOREGROUND_SERVICE = 1001;
|
||||
public static final int NOTIFY_ID_REMIND = 1002;
|
||||
public static final int NOTIFY_ID_CONFIG = 1003; // 新增:应用配置信息通知ID
|
||||
// 低版本兼容:默认通知图标(API<21 避免显示异常)
|
||||
private static final int NOTIFICATION_DEFAULT_ICON = R.drawable.ic_launcher;
|
||||
// 通知内容兜底常量
|
||||
@@ -40,9 +42,12 @@ public class NotificationManagerUtils {
|
||||
private static final String FOREGROUND_NOTIFY_CONTENT_DEFAULT = "后台监测电池状态";
|
||||
private static final String REMIND_NOTIFY_TITLE_DEFAULT = "电池状态提醒";
|
||||
private static final String REMIND_NOTIFY_CONTENT_DEFAULT = "电池状态异常,请及时处理";
|
||||
private static final String CONFIG_NOTIFY_TITLE_DEFAULT = "应用配置更新"; // 新增:配置通知默认标题
|
||||
private static final String CONFIG_NOTIFY_CONTENT_DEFAULT = "配置信息已更新,生效中"; // 新增:配置通知默认内容
|
||||
// PendingIntent请求码
|
||||
private static final int PENDING_INTENT_REQUEST_CODE_FOREGROUND = 0;
|
||||
private static final int PENDING_INTENT_REQUEST_CODE_REMIND = 1;
|
||||
private static final int PENDING_INTENT_REQUEST_CODE_CONFIG = 2; // 新增:配置通知请求码
|
||||
|
||||
// ================================== 成员变量(私有封装,按依赖优先级排序)=================================
|
||||
// 核心上下文(应用级,避免内存泄漏)
|
||||
@@ -54,33 +59,35 @@ public class NotificationManagerUtils {
|
||||
|
||||
// ================================== 构造方法(初始化核心资源,前置校验)=================================
|
||||
public NotificationManagerUtils(Context context) {
|
||||
LogUtils.d(TAG, "构造方法执行");
|
||||
LogUtils.d(TAG, "NotificationManagerUtils: 构造方法执行 | context=" + context);
|
||||
// 前置校验:Context非空
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "构造失败:context is null");
|
||||
LogUtils.e(TAG, "NotificationManagerUtils: 构造失败:context is null");
|
||||
return;
|
||||
}
|
||||
// 初始化核心资源
|
||||
this.mContext = context.getApplicationContext();
|
||||
this.mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
LogUtils.d(TAG, "NotificationManagerUtils: 核心资源初始化完成 | mContext=" + mContext + " | mNotificationManager=" + mNotificationManager);
|
||||
// 初始化通知渠道(API26+ 必需)
|
||||
initNotificationChannels();
|
||||
LogUtils.d(TAG, "构造完成:核心资源初始化成功");
|
||||
LogUtils.d(TAG, "NotificationManagerUtils: 构造完成");
|
||||
}
|
||||
|
||||
// ================================== 核心初始化方法(通知渠道,API分级适配)=================================
|
||||
/**
|
||||
* 初始化通知渠道:前台服务渠道(无铃声+无振动)、提醒渠道(系统默认铃声+无振动)
|
||||
* 初始化通知渠道:前台服务渠道(无铃声+无振动)、提醒渠道(系统默认铃声+无振动)、配置信息渠道(低优先级无打扰)
|
||||
*/
|
||||
private void initNotificationChannels() {
|
||||
LogUtils.d(TAG, "initNotificationChannels: 执行通知渠道初始化");
|
||||
// API<26 无渠道机制,直接返回
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
LogUtils.d(TAG, "initNotificationChannels:API<26,无需创建渠道");
|
||||
LogUtils.d(TAG, "initNotificationChannels: API<26,无需创建渠道");
|
||||
return;
|
||||
}
|
||||
// 通知服务为空,避免空指针
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "initNotificationChannels失败:NotificationManager is null");
|
||||
LogUtils.e(TAG, "initNotificationChannels: 失败:NotificationManager is null");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,6 +103,7 @@ public class NotificationManagerUtils {
|
||||
foregroundChannel.setSound(null, null); // 强制无铃声
|
||||
foregroundChannel.setShowBadge(false);
|
||||
foregroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
|
||||
LogUtils.d(TAG, "initNotificationChannels: 前台服务渠道配置完成");
|
||||
|
||||
// 2. 电池提醒渠道(中优先级,系统默认铃声,无振动)
|
||||
NotificationChannel remindChannel = new NotificationChannel(
|
||||
@@ -109,11 +117,27 @@ public class NotificationManagerUtils {
|
||||
remindChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), Notification.AUDIO_ATTRIBUTES_DEFAULT);
|
||||
remindChannel.setShowBadge(false);
|
||||
remindChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
LogUtils.d(TAG, "initNotificationChannels: 电池提醒渠道配置完成");
|
||||
|
||||
// 3. 应用配置信息渠道(新增:最低优先级,无铃声无振动,仅提示不打扰)
|
||||
NotificationChannel configChannel = new NotificationChannel(
|
||||
CHANNEL_ID_CONFIG,
|
||||
"应用配置信息",
|
||||
NotificationManager.IMPORTANCE_MIN
|
||||
);
|
||||
configChannel.setDescription("应用配置更新、参数变更等提示,无声音、无振动");
|
||||
configChannel.enableLights(false);
|
||||
configChannel.enableVibration(false);
|
||||
configChannel.setSound(null, null);
|
||||
configChannel.setShowBadge(false);
|
||||
configChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
LogUtils.d(TAG, "initNotificationChannels: 应用配置信息渠道配置完成");
|
||||
|
||||
// 注册渠道到系统
|
||||
mNotificationManager.createNotificationChannel(foregroundChannel);
|
||||
mNotificationManager.createNotificationChannel(remindChannel);
|
||||
LogUtils.d(TAG, "initNotificationChannels成功:创建前台服务+电池提醒渠道");
|
||||
mNotificationManager.createNotificationChannel(configChannel); // 注册新增渠道
|
||||
LogUtils.d(TAG, "initNotificationChannels: 成功:创建前台服务+电池提醒+应用配置信息渠道");
|
||||
}
|
||||
|
||||
// ================================== 对外核心方法(前台服务通知:启动/更新/取消)=================================
|
||||
@@ -121,26 +145,26 @@ public class NotificationManagerUtils {
|
||||
* 启动前台服务通知(API30适配,无铃声)
|
||||
*/
|
||||
public void startForegroundServiceNotify(Service service, NotificationMessage message) {
|
||||
LogUtils.d(TAG, "startForegroundServiceNotify执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
LogUtils.d(TAG, "startForegroundServiceNotify: 执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE + " | service=" + service + " | message=" + message);
|
||||
// 前置校验:参数非空
|
||||
if (service == null || message == null || mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify失败:param is null | service=" + service + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify: 失败:param is null | service=" + service + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建前台通知
|
||||
mForegroundServiceNotify = buildForegroundNotification(message);
|
||||
if (mForegroundServiceNotify == null) {
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify失败:构建通知为空");
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify: 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动前台服务(API30无FOREGROUND_SERVICE_TYPE限制,全版本通用)
|
||||
try {
|
||||
service.startForeground(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
|
||||
LogUtils.d(TAG, "startForegroundServiceNotify成功");
|
||||
LogUtils.d(TAG, "startForegroundServiceNotify: 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify异常", e);
|
||||
LogUtils.e(TAG, "startForegroundServiceNotify: 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,23 +172,23 @@ public class NotificationManagerUtils {
|
||||
* 更新前台服务通知内容(复用通知ID,保持无铃声)
|
||||
*/
|
||||
public void updateForegroundServiceNotify(NotificationMessage message) {
|
||||
LogUtils.d(TAG, "updateForegroundServiceNotify执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
LogUtils.d(TAG, "updateForegroundServiceNotify: 执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE + " | message=" + message);
|
||||
if (message == null || mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify失败:param is null | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify: 失败:param is null | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
return;
|
||||
}
|
||||
|
||||
mForegroundServiceNotify = buildForegroundNotification(message);
|
||||
if (mForegroundServiceNotify == null) {
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify失败:构建通知为空");
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify: 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mNotificationManager.notify(NOTIFY_ID_FOREGROUND_SERVICE, mForegroundServiceNotify);
|
||||
LogUtils.d(TAG, "updateForegroundServiceNotify成功");
|
||||
LogUtils.d(TAG, "updateForegroundServiceNotify: 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify异常", e);
|
||||
LogUtils.e(TAG, "updateForegroundServiceNotify: 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +196,10 @@ public class NotificationManagerUtils {
|
||||
* 取消前台服务通知(Service销毁时调用)
|
||||
*/
|
||||
public void cancelForegroundServiceNotify() {
|
||||
LogUtils.d(TAG, "cancelForegroundServiceNotify执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
LogUtils.d(TAG, "cancelForegroundServiceNotify: 执行 | notifyId=" + NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
cancelNotification(NOTIFY_ID_FOREGROUND_SERVICE);
|
||||
mForegroundServiceNotify = null; // 置空释放
|
||||
LogUtils.d(TAG, "cancelForegroundServiceNotify成功");
|
||||
LogUtils.d(TAG, "cancelForegroundServiceNotify: 成功");
|
||||
}
|
||||
|
||||
// ================================== 对外核心方法(电池提醒通知:发送)=================================
|
||||
@@ -183,23 +207,48 @@ public class NotificationManagerUtils {
|
||||
* 发送电池提醒通知(系统默认铃声,无振动)
|
||||
*/
|
||||
public void showRemindNotification(Context context, NotificationMessage message) {
|
||||
LogUtils.d(TAG, "showRemindNotification执行 | notifyId=" + NOTIFY_ID_REMIND);
|
||||
LogUtils.d(TAG, "showRemindNotification: 执行 | notifyId=" + NOTIFY_ID_REMIND + " | context=" + context + " | message=" + message);
|
||||
if (context == null || message == null || mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "showRemindNotification失败:param is null | context=" + context + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
LogUtils.e(TAG, "showRemindNotification: 失败:param is null | context=" + context + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
return;
|
||||
}
|
||||
|
||||
Notification remindNotify = buildRemindNotification(context, message);
|
||||
if (remindNotify == null) {
|
||||
LogUtils.e(TAG, "showRemindNotification失败:构建通知为空");
|
||||
LogUtils.e(TAG, "showRemindNotification: 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mNotificationManager.notify(NOTIFY_ID_REMIND, remindNotify);
|
||||
LogUtils.d(TAG, "showRemindNotification成功");
|
||||
LogUtils.d(TAG, "showRemindNotification: 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "showRemindNotification异常", e);
|
||||
LogUtils.e(TAG, "showRemindNotification: 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================== 对外核心方法(应用配置信息通知:发送)=================================
|
||||
/**
|
||||
* 发送应用配置信息通知(新增:低优先级无铃声,仅提示不打扰)
|
||||
*/
|
||||
public void showConfigNotification(Context context, NotificationMessage message) {
|
||||
LogUtils.d(TAG, "showConfigNotification: 执行 | notifyId=" + NOTIFY_ID_CONFIG + " | context=" + context + " | message=" + message);
|
||||
if (context == null || message == null || mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "showConfigNotification: 失败:param is null | context=" + context + " | message=" + message + " | mNotificationManager=" + mNotificationManager);
|
||||
return;
|
||||
}
|
||||
|
||||
Notification configNotify = buildConfigNotification(context, message);
|
||||
if (configNotify == null) {
|
||||
LogUtils.e(TAG, "showConfigNotification: 失败:构建通知为空");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mNotificationManager.notify(NOTIFY_ID_CONFIG, configNotify);
|
||||
LogUtils.d(TAG, "showConfigNotification: 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "showConfigNotification: 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,16 +257,16 @@ public class NotificationManagerUtils {
|
||||
* 取消指定ID的通知
|
||||
*/
|
||||
public void cancelNotification(int notifyId) {
|
||||
LogUtils.d(TAG, "cancelNotification执行 | notifyId=" + notifyId);
|
||||
LogUtils.d(TAG, "cancelNotification: 执行 | notifyId=" + notifyId);
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "cancelNotification失败:NotificationManager is null");
|
||||
LogUtils.e(TAG, "cancelNotification: 失败:NotificationManager is null");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mNotificationManager.cancel(notifyId);
|
||||
LogUtils.d(TAG, "cancelNotification成功 | notifyId=" + notifyId);
|
||||
LogUtils.d(TAG, "cancelNotification: 成功 | notifyId=" + notifyId);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "cancelNotification异常 | notifyId=" + notifyId, e);
|
||||
LogUtils.e(TAG, "cancelNotification: 异常 | notifyId=" + notifyId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,16 +274,16 @@ public class NotificationManagerUtils {
|
||||
* 取消所有通知(兜底场景使用)
|
||||
*/
|
||||
public void cancelAllNotifications() {
|
||||
LogUtils.d(TAG, "cancelAllNotifications执行");
|
||||
LogUtils.d(TAG, "cancelAllNotifications: 执行");
|
||||
if (mNotificationManager == null) {
|
||||
LogUtils.e(TAG, "cancelAllNotifications失败:NotificationManager is null");
|
||||
LogUtils.e(TAG, "cancelAllNotifications: 失败:NotificationManager is null");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mNotificationManager.cancelAll();
|
||||
LogUtils.d(TAG, "cancelAllNotifications成功");
|
||||
LogUtils.d(TAG, "cancelAllNotifications: 成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "cancelAllNotifications异常", e);
|
||||
LogUtils.e(TAG, "cancelAllNotifications: 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,28 +292,30 @@ public class NotificationManagerUtils {
|
||||
* 构建前台服务通知(全版本无铃声+无振动)
|
||||
*/
|
||||
private Notification buildForegroundNotification(NotificationMessage message) {
|
||||
LogUtils.d(TAG, "buildForegroundNotification执行");
|
||||
LogUtils.d(TAG, "buildForegroundNotification: 执行 | message=" + message);
|
||||
if (message == null || mContext == null) {
|
||||
LogUtils.e(TAG, "buildForegroundNotification失败:param is null | message=" + message + " | mContext=" + mContext);
|
||||
LogUtils.e(TAG, "buildForegroundNotification: 失败:param is null | message=" + message + " | mContext=" + mContext);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 内容兜底
|
||||
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : FOREGROUND_NOTIFY_TITLE_DEFAULT;
|
||||
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : FOREGROUND_NOTIFY_CONTENT_DEFAULT;
|
||||
LogUtils.d(TAG, "buildForegroundNotification:title=" + title + " | content=" + content);
|
||||
LogUtils.d(TAG, "buildForegroundNotification: 内容兜底完成 | title=" + title + " | content=" + content);
|
||||
|
||||
Notification.Builder builder;
|
||||
// API分级构建
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// API26+:绑定前台渠道(渠道已配置无铃声)
|
||||
builder = new Notification.Builder(mContext, CHANNEL_ID_FOREGROUND);
|
||||
LogUtils.d(TAG, "buildForegroundNotification: 使用API26+渠道构建");
|
||||
} else {
|
||||
// API<26:直接构建,手动禁用铃声振动
|
||||
builder = new Notification.Builder(mContext);
|
||||
builder.setSound(null);
|
||||
builder.setVibrate(new long[]{0});
|
||||
builder.setDefaults(0);
|
||||
LogUtils.d(TAG, "buildForegroundNotification: 使用API<26手动配置");
|
||||
}
|
||||
|
||||
// 通用配置
|
||||
@@ -281,9 +332,12 @@ public class NotificationManagerUtils {
|
||||
builder.setLargeIcon(getAppIcon(mContext))
|
||||
.setColor(mContext.getResources().getColor(R.color.colorPrimary))
|
||||
.setPriority(Notification.PRIORITY_LOW);
|
||||
LogUtils.d(TAG, "buildForegroundNotification: 补充API21+配置");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
Notification notification = builder.build();
|
||||
LogUtils.d(TAG, "buildForegroundNotification: 成功构建前台通知");
|
||||
return notification;
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(通知构建:电池提醒通知)=================================
|
||||
@@ -291,28 +345,30 @@ public class NotificationManagerUtils {
|
||||
* 构建电池提醒通知(全版本系统默认铃声+无振动)
|
||||
*/
|
||||
private Notification buildRemindNotification(Context context, NotificationMessage message) {
|
||||
LogUtils.d(TAG, "buildRemindNotification执行");
|
||||
LogUtils.d(TAG, "buildRemindNotification: 执行 | context=" + context + " | message=" + message);
|
||||
if (context == null || message == null) {
|
||||
LogUtils.e(TAG, "buildRemindNotification失败:param is null | context=" + context + " | message=" + message);
|
||||
LogUtils.e(TAG, "buildRemindNotification: 失败:param is null | context=" + context + " | message=" + message);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 内容兜底
|
||||
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : REMIND_NOTIFY_TITLE_DEFAULT;
|
||||
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : REMIND_NOTIFY_CONTENT_DEFAULT;
|
||||
LogUtils.d(TAG, "buildRemindNotification:title=" + title + " | content=" + content);
|
||||
LogUtils.d(TAG, "buildRemindNotification: 内容兜底完成 | title=" + title + " | content=" + content);
|
||||
|
||||
Notification.Builder builder;
|
||||
// API分级构建
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// API26+:绑定提醒渠道(渠道已配置默认铃声)
|
||||
builder = new Notification.Builder(context, CHANNEL_ID_REMIND);
|
||||
LogUtils.d(TAG, "buildRemindNotification: 使用API26+渠道构建");
|
||||
} else {
|
||||
// API<26:手动配置默认铃声,关闭振动
|
||||
builder = new Notification.Builder(context);
|
||||
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI) // 显式默认铃声
|
||||
.setVibrate(new long[]{0})
|
||||
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_SOUND);
|
||||
LogUtils.d(TAG, "buildRemindNotification: 使用API<26手动配置");
|
||||
}
|
||||
|
||||
// 通用配置
|
||||
@@ -330,9 +386,65 @@ public class NotificationManagerUtils {
|
||||
builder.setLargeIcon(getAppIcon(context))
|
||||
.setColor(context.getResources().getColor(R.color.colorPrimary))
|
||||
.setPriority(Notification.PRIORITY_DEFAULT);
|
||||
LogUtils.d(TAG, "buildRemindNotification: 补充API21+配置");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
Notification notification = builder.build();
|
||||
LogUtils.d(TAG, "buildRemindNotification: 成功构建提醒通知");
|
||||
return notification;
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(通知构建:应用配置信息通知)=================================
|
||||
/**
|
||||
* 构建应用配置信息通知(新增:全版本无铃声+无振动,低优先级)
|
||||
*/
|
||||
private Notification buildConfigNotification(Context context, NotificationMessage message) {
|
||||
LogUtils.d(TAG, "buildConfigNotification: 执行 | context=" + context + " | message=" + message);
|
||||
if (context == null || message == null) {
|
||||
LogUtils.e(TAG, "buildConfigNotification: 失败:param is null | context=" + context + " | message=" + message);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 内容兜底
|
||||
String title = message.getTitle() != null && !message.getTitle().isEmpty() ? message.getTitle() : CONFIG_NOTIFY_TITLE_DEFAULT;
|
||||
String content = message.getContent() != null && !message.getContent().isEmpty() ? message.getContent() : CONFIG_NOTIFY_CONTENT_DEFAULT;
|
||||
LogUtils.d(TAG, "buildConfigNotification: 内容兜底完成 | title=" + title + " | content=" + content);
|
||||
|
||||
Notification.Builder builder;
|
||||
// API分级构建
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// API26+:绑定配置渠道(渠道已配置无铃声)
|
||||
builder = new Notification.Builder(context, CHANNEL_ID_CONFIG);
|
||||
LogUtils.d(TAG, "buildConfigNotification: 使用API26+渠道构建");
|
||||
} else {
|
||||
// API<26:直接构建,手动禁用铃声振动
|
||||
builder = new Notification.Builder(context);
|
||||
builder.setSound(null);
|
||||
builder.setVibrate(new long[]{0});
|
||||
builder.setDefaults(0);
|
||||
LogUtils.d(TAG, "buildConfigNotification: 使用API<26手动配置");
|
||||
}
|
||||
|
||||
// 通用配置
|
||||
builder.setSmallIcon(NOTIFICATION_DEFAULT_ICON)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setAutoCancel(true) // 点击关闭
|
||||
.setOngoing(false)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentIntent(createJumpPendingIntent(context, PENDING_INTENT_REQUEST_CODE_CONFIG));
|
||||
|
||||
// API21+ 新增大图标+主题色
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
builder.setLargeIcon(getAppIcon(context))
|
||||
.setColor(context.getResources().getColor(R.color.colorPrimary))
|
||||
.setPriority(Notification.PRIORITY_MIN); // 最低优先级
|
||||
LogUtils.d(TAG, "buildConfigNotification: 补充API21+配置");
|
||||
}
|
||||
|
||||
Notification notification = builder.build();
|
||||
LogUtils.d(TAG, "buildConfigNotification: 成功构建配置信息通知");
|
||||
return notification;
|
||||
}
|
||||
|
||||
// ================================== 内部辅助方法(创建跳转PendingIntent,API30安全适配)=================================
|
||||
@@ -340,19 +452,20 @@ public class NotificationManagerUtils {
|
||||
* 创建跳转MainActivity的PendingIntent,API23+ 添加IMMUTABLE标记(避免安全异常)
|
||||
*/
|
||||
private PendingIntent createJumpPendingIntent(Context context, int requestCode) {
|
||||
LogUtils.d(TAG, "createJumpPendingIntent执行 | requestCode=" + requestCode);
|
||||
LogUtils.d(TAG, "createJumpPendingIntent: 执行 | requestCode=" + requestCode + " | context=" + context);
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
LogUtils.d(TAG, "createJumpPendingIntent: 跳转Intent配置完成");
|
||||
|
||||
// API23+ 必需添加IMMUTABLE,适配API30安全规范
|
||||
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
flags |= PendingIntent.FLAG_IMMUTABLE;
|
||||
LogUtils.d(TAG, "createJumpPendingIntent:添加FLAG_IMMUTABLE标记");
|
||||
LogUtils.d(TAG, "createJumpPendingIntent: 添加FLAG_IMMUTABLE标记(API23+)");
|
||||
}
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, flags);
|
||||
LogUtils.d(TAG, "createJumpPendingIntent成功 | requestCode=" + requestCode);
|
||||
LogUtils.d(TAG, "createJumpPendingIntent: 成功 | requestCode=" + requestCode);
|
||||
return pendingIntent;
|
||||
}
|
||||
|
||||
@@ -361,33 +474,33 @@ public class NotificationManagerUtils {
|
||||
* 获取APP图标,失败返回默认图标
|
||||
*/
|
||||
private Bitmap getAppIcon(Context context) {
|
||||
LogUtils.d(TAG, "getAppIcon执行");
|
||||
LogUtils.d(TAG, "getAppIcon: 执行 | context=" + context);
|
||||
try {
|
||||
PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
Bitmap appIcon = BitmapFactory.decodeResource(context.getResources(), pkgInfo.applicationInfo.icon);
|
||||
LogUtils.d(TAG, "getAppIcon成功:获取应用图标");
|
||||
LogUtils.d(TAG, "getAppIcon: 成功:获取应用图标");
|
||||
return appIcon;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.e(TAG, "getAppIcon异常:获取应用图标失败,使用默认图标", e);
|
||||
LogUtils.e(TAG, "getAppIcon: 异常:获取应用图标失败,使用默认图标", e);
|
||||
return BitmapFactory.decodeResource(context.getResources(), NOTIFICATION_DEFAULT_ICON);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================== 对外 getter 方法(仅前台通知实例,只读)=================================
|
||||
public Notification getForegroundServiceNotify() {
|
||||
return mForegroundServiceNotify;
|
||||
}
|
||||
|
||||
// ================================== 资源释放方法(避免内存泄漏)=================================
|
||||
/**
|
||||
* 释放资源,销毁时调用
|
||||
*/
|
||||
public void release() {
|
||||
LogUtils.d(TAG, "release执行");
|
||||
LogUtils.d(TAG, "release: 执行资源释放");
|
||||
cancelForegroundServiceNotify();
|
||||
mNotificationManager = null;
|
||||
mContext = null;
|
||||
LogUtils.d(TAG, "release成功:所有资源已释放");
|
||||
LogUtils.d(TAG, "release: 成功:所有资源已释放");
|
||||
}
|
||||
|
||||
// ================================== 对外 getter 方法(仅前台通知实例,只读)=================================
|
||||
public Notification getForegroundServiceNotify() {
|
||||
return mForegroundServiceNotify;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ package cc.winboll.studio.powerbell.views;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
@@ -20,11 +22,12 @@ import java.io.File;
|
||||
/**
|
||||
* 基于Java7的BackgroundView(LinearLayout+ImageView,保持原图比例居中平铺)
|
||||
* 核心:ImageView保持原图比例,在LinearLayout中居中平铺,无拉伸、无裁剪
|
||||
* 改进:强化Bitmap生命周期管理,防止已回收Bitmap绘制崩溃
|
||||
*/
|
||||
public class BackgroundView extends RelativeLayout {
|
||||
|
||||
public static final String TAG = "BackgroundView";
|
||||
// 新增:记录当前已缓存的图片路径
|
||||
// 记录当前已缓存的图片路径
|
||||
private String mCurrentCachedPath = "";
|
||||
|
||||
private Context mContext;
|
||||
@@ -103,11 +106,11 @@ public class BackgroundView extends RelativeLayout {
|
||||
LogUtils.d(TAG, "=== initImageView 完成 ===");
|
||||
}
|
||||
|
||||
public void loadBackgroundBean(BackgroundBean bean) {
|
||||
loadBackgroundBean(bean, false);
|
||||
public void loadByBackgroundBean(BackgroundBean bean) {
|
||||
loadByBackgroundBean(bean, false);
|
||||
}
|
||||
|
||||
public void loadBackgroundBean(BackgroundBean bean, boolean isRefresh) {
|
||||
public void loadByBackgroundBean(BackgroundBean bean, boolean isRefresh) {
|
||||
if (!bean.isUseBackgroundFile()) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
@@ -116,22 +119,26 @@ public class BackgroundView extends RelativeLayout {
|
||||
? bean.getBackgroundScaledCompressFilePath()
|
||||
: bean.getBackgroundFilePath();
|
||||
|
||||
if (!(new File(targetPath).exists())) {
|
||||
LogUtils.d(TAG, String.format("视图控件图片不存在:%s", targetPath));
|
||||
return;
|
||||
}
|
||||
if (!(new File(targetPath).exists())) {
|
||||
LogUtils.d(TAG, String.format("视图控件图片不存在:%s", targetPath));
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用带路径判断的loadImage方法
|
||||
if (isRefresh) {
|
||||
App._mBitmapCacheUtils.removeCachedBitmap(targetPath);
|
||||
App._mBitmapCacheUtils.cacheBitmap(targetPath);
|
||||
}
|
||||
loadImage(targetPath);
|
||||
// 调用带路径判断的loadImage方法
|
||||
if (isRefresh) {
|
||||
App.sBitmapCacheUtils.removeCachedBitmap(targetPath);
|
||||
// 刷新时直接解码,避免缓存旧数据
|
||||
Bitmap newBitmap = decodeBitmapWithCompress(new File(targetPath), 1080, 1920);
|
||||
if (newBitmap != null) {
|
||||
App.sBitmapCacheUtils.cacheBitmap(targetPath, newBitmap);
|
||||
}
|
||||
}
|
||||
loadImage(targetPath);
|
||||
}
|
||||
|
||||
// ====================================== 对外方法 ======================================
|
||||
/**
|
||||
* 改造后:添加路径判断,路径更新时同步更新缓存;缓存Bitmap为null时提示并加载透明背景
|
||||
* 改进版:强化Bitmap有效性校验,增加缓存重加载机制,防止已回收Bitmap崩溃
|
||||
* @param imagePath 图片绝对路径
|
||||
*/
|
||||
public void loadImage(String imagePath) {
|
||||
@@ -150,35 +157,31 @@ public class BackgroundView extends RelativeLayout {
|
||||
|
||||
mIvBackground.setVisibility(View.GONE);
|
||||
|
||||
// ======================== 新增:路径判断逻辑 ========================
|
||||
// 1. 路径未变化:直接使用缓存
|
||||
// ======================== 路径判断逻辑(改进版) ========================
|
||||
// 1. 路径未变化:校验缓存有效性,无效则重加载
|
||||
if (imagePath.equals(mCurrentCachedPath)) {
|
||||
Bitmap cachedBitmap = App._mBitmapCacheUtils.getCachedBitmap(imagePath);
|
||||
// 核心修改:判断缓存Bitmap是否为null
|
||||
if (cachedBitmap != null && !cachedBitmap.isRecycled()) {
|
||||
LogUtils.d(TAG, "loadImage: 路径未变,使用缓存 Bitmap");
|
||||
mImageAspectRatio = (float) cachedBitmap.getWidth() / cachedBitmap.getHeight();
|
||||
mIvBackground.setImageBitmap(cachedBitmap);
|
||||
adjustImageViewSize();
|
||||
mIvBackground.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
} else {
|
||||
// 缓存Bitmap为空或已回收,提示并加载透明背景
|
||||
LogUtils.e(TAG, "loadImage: 全局位图缓存为空或已回收 - " + imagePath);
|
||||
ToastUtils.show("全局位图缓存为空,无法加载图片");
|
||||
setDefaultTransparentBackground();
|
||||
Bitmap cachedBitmap = App.sBitmapCacheUtils.getCachedBitmap(imagePath);
|
||||
if (isBitmapValid(cachedBitmap)) {
|
||||
LogUtils.d(TAG, "loadImage: 路径未变,使用有效缓存 Bitmap");
|
||||
mImageAspectRatio = (float) cachedBitmap.getWidth() / cachedBitmap.getHeight();
|
||||
mIvBackground.setImageBitmap(cachedBitmap);
|
||||
adjustImageViewSize();
|
||||
return;
|
||||
} else {
|
||||
LogUtils.e(TAG, "loadImage: 缓存Bitmap无效,尝试重加载 - " + imagePath);
|
||||
// 缓存无效,移除旧缓存并强制重加载
|
||||
App.sBitmapCacheUtils.removeCachedBitmap(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 路径已更新:移除旧缓存,加载新图片并更新缓存
|
||||
// 2. 路径已更新:移除旧缓存
|
||||
if (!TextUtils.isEmpty(mCurrentCachedPath)) {
|
||||
App._mBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath);
|
||||
App.sBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath);
|
||||
LogUtils.d(TAG, "loadImage: 路径已更新,移除旧缓存 - " + mCurrentCachedPath);
|
||||
}
|
||||
// ======================== 路径判断逻辑结束 ========================
|
||||
|
||||
// 无缓存/路径更新:走原有逻辑加载图片
|
||||
// 无缓存/缓存无效/路径更新:重新加载图片
|
||||
if (!calculateImageAspectRatio(imageFile)) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
@@ -193,17 +196,24 @@ public class BackgroundView extends RelativeLayout {
|
||||
}
|
||||
|
||||
// 缓存新图片,并更新当前缓存路径记录
|
||||
App._mBitmapCacheUtils.cacheBitmap(imagePath);
|
||||
App.sBitmapCacheUtils.cacheBitmap(imagePath, bitmap);
|
||||
mCurrentCachedPath = imagePath;
|
||||
LogUtils.d(TAG, "loadImage: 加载新图片并更新缓存 - " + imagePath);
|
||||
|
||||
mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap));
|
||||
// 改进:直接使用setImageBitmap,避免BitmapDrawable包装的引用风险
|
||||
mIvBackground.setImageBitmap(bitmap);
|
||||
adjustImageViewSize();
|
||||
mIvBackground.setVisibility(View.VISIBLE);
|
||||
LogUtils.d(TAG, "=== loadImage 完成 ===");
|
||||
}
|
||||
|
||||
// ====================================== 内部工具方法 ======================================
|
||||
/**
|
||||
* 工具方法:判断Bitmap是否有效(非空且未被回收)
|
||||
*/
|
||||
private boolean isBitmapValid(Bitmap bitmap) {
|
||||
return bitmap != null && !bitmap.isRecycled();
|
||||
}
|
||||
|
||||
private boolean calculateImageAspectRatio(File file) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
@@ -232,14 +242,17 @@ public class BackgroundView extends RelativeLayout {
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
|
||||
int scaleX = options.outWidth / maxWidth;
|
||||
int scaleY = options.outHeight / maxHeight;
|
||||
// 改进:更精准的采样率计算(避免过度压缩)
|
||||
int scaleX = (int) Math.ceil((float) options.outWidth / maxWidth);
|
||||
int scaleY = (int) Math.ceil((float) options.outHeight / maxHeight);
|
||||
int inSampleSize = Math.max(scaleX, scaleY);
|
||||
if (inSampleSize <= 0) inSampleSize = 1;
|
||||
inSampleSize = Math.max(1, inSampleSize); // 确保采样率≥1
|
||||
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = inSampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存
|
||||
options.inPurgeable = true; // 允许系统在内存紧张时回收
|
||||
options.inInputShareable = true;
|
||||
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "压缩解码失败:" + e.getMessage());
|
||||
@@ -255,39 +268,86 @@ public class BackgroundView extends RelativeLayout {
|
||||
int llWidth = mLlContainer.getWidth();
|
||||
int llHeight = mLlContainer.getHeight();
|
||||
|
||||
if (llWidth != 0 && llHeight != 0) {
|
||||
int ivWidth, ivHeight;
|
||||
if (mImageAspectRatio >= 1.0f) {
|
||||
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
|
||||
ivHeight = (int) (ivWidth / mImageAspectRatio);
|
||||
} else {
|
||||
ivHeight = Math.min((int) (llWidth / mImageAspectRatio), llHeight);
|
||||
ivWidth = (int) (ivHeight * mImageAspectRatio);
|
||||
}
|
||||
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
|
||||
params.width = ivWidth;
|
||||
params.height = ivHeight;
|
||||
mIvBackground.setLayoutParams(params);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER);
|
||||
mIvBackground.setVisibility(View.VISIBLE);
|
||||
if (llWidth == 0 || llHeight == 0) {
|
||||
LogUtils.w(TAG, "adjustImageViewSize: 容器尺寸未初始化,延迟调整");
|
||||
// 延迟调整(容器尺寸未就绪时)
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adjustImageViewSize();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
int ivWidth, ivHeight;
|
||||
if (mImageAspectRatio >= 1.0f) {
|
||||
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
|
||||
ivHeight = (int) (ivWidth / mImageAspectRatio);
|
||||
} else {
|
||||
ivHeight = Math.min((int) (llWidth / mImageAspectRatio), llHeight);
|
||||
ivWidth = (int) (ivHeight * mImageAspectRatio);
|
||||
}
|
||||
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
|
||||
params.width = ivWidth;
|
||||
params.height = ivHeight;
|
||||
mIvBackground.setLayoutParams(params);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER);
|
||||
mIvBackground.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setDefaultTransparentBackground() {
|
||||
mIvBackground.setImageBitmap(null);
|
||||
// 改进:先清空Drawable,避免残留已回收Bitmap
|
||||
mIvBackground.setImageDrawable(null);
|
||||
mIvBackground.setBackgroundColor(0x00000000);
|
||||
mImageAspectRatio = 1.0f;
|
||||
// 清空缓存路径记录
|
||||
mCurrentCachedPath = "";
|
||||
mIvBackground.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// ====================================== 重写方法 ======================================
|
||||
// ====================================== 重写方法(核心改进) ======================================
|
||||
/**
|
||||
* 重写:绘制前强制校验Bitmap有效性,防止已回收Bitmap崩溃
|
||||
*/
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
Drawable drawable = mIvBackground.getDrawable();
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
|
||||
Bitmap bitmap = bitmapDrawable.getBitmap();
|
||||
if (!isBitmapValid(bitmap)) {
|
||||
LogUtils.e(TAG, "onDraw: 检测到已回收Bitmap,清空绘制");
|
||||
mIvBackground.setImageDrawable(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写:View从窗口移除时主动释放资源,避免内存泄漏和无效引用
|
||||
*/
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
LogUtils.d(TAG, "onDetachedFromWindow: 释放Bitmap资源");
|
||||
// 清空ImageView的Drawable,释放Bitmap引用
|
||||
mIvBackground.setImageDrawable(null);
|
||||
// 清空当前缓存路径,避免后续错误引用
|
||||
mCurrentCachedPath = "";
|
||||
// 可选:如果当前View是唯一使用者,移除全局缓存
|
||||
// if (!TextUtils.isEmpty(mCurrentCachedPath)) {
|
||||
// App.sBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写:恢复尺寸调整逻辑,确保View尺寸变化时正确显示
|
||||
*/
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
adjustImageViewSize(); // 尺寸变化时重新调整
|
||||
adjustImageViewSize(); // 恢复尺寸调整
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.DialogInterface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -14,6 +15,7 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.App;
|
||||
import cc.winboll.studio.powerbell.R;
|
||||
import cc.winboll.studio.powerbell.models.ControlCenterServiceBean;
|
||||
import cc.winboll.studio.powerbell.services.ControlCenterService;
|
||||
@@ -24,12 +26,55 @@ import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||
* @Date 2025/12/17 13:14
|
||||
* @Describe 主页面核心视图封装类:统一管理视图绑定、数据更新、事件监听,解耦 Activity 逻辑
|
||||
* 适配:Java7 | API30 | 小米手机,优化性能与资源回收,杜绝内存泄漏,配置变更确认对话框
|
||||
* 新增:拖动进度条时实时预览 sbUsageReminder 与 sbChargeReminder 比值
|
||||
*/
|
||||
public class MainContentView {
|
||||
// ======================== 静态常量(置顶,唯一标识)========================
|
||||
public static final String TAG = "MainContentView";
|
||||
|
||||
// ======================== 核心成员变量(按「依赖→视图→内部资源→对话框」排序)========================
|
||||
// 变更类型常量(区分不同控件,精准处理逻辑)
|
||||
private static final int CHANGE_TYPE_CHARGE_SWITCH = 1;
|
||||
private static final int CHANGE_TYPE_USAGE_SWITCH = 2;
|
||||
private static final int CHANGE_TYPE_SERVICE_SWITCH = 3;
|
||||
private static final int CHANGE_TYPE_CHARGE_SEEKBAR = 4;
|
||||
private static final int CHANGE_TYPE_USAGE_SEEKBAR = 5;
|
||||
|
||||
// ======================== 内部静态类(临时数据载体,避免外部依赖)========================
|
||||
/**
|
||||
* 临时配置数据实体(缓存变更信息,取消时恢复)
|
||||
*/
|
||||
private static class TempConfigData {
|
||||
int changeType;
|
||||
boolean originalBooleanValue;
|
||||
int originalIntValue;
|
||||
boolean newBooleanValue;
|
||||
int newIntValue;
|
||||
|
||||
// 构造方法(开关类型)
|
||||
TempConfigData(int changeType, boolean originalValue, boolean newValue) {
|
||||
this.changeType = changeType;
|
||||
this.originalBooleanValue = originalValue;
|
||||
this.newBooleanValue = newValue;
|
||||
}
|
||||
|
||||
// 构造方法(进度条类型)
|
||||
TempConfigData(int changeType, int originalValue, int newValue) {
|
||||
this.changeType = changeType;
|
||||
this.originalIntValue = originalValue;
|
||||
this.newIntValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 事件回调接口(解耦视图与业务,提升扩展性)========================
|
||||
public interface OnViewActionListener {
|
||||
void onChargeReminderSwitchChanged(boolean isChecked);
|
||||
void onUsageReminderSwitchChanged(boolean isChecked);
|
||||
void onServiceSwitchChanged(boolean isChecked);
|
||||
void onChargeReminderProgressChanged(int progress);
|
||||
void onUsageReminderProgressChanged(int progress);
|
||||
}
|
||||
|
||||
// ======================== 成员变量(按功能分类,避免混乱)========================
|
||||
// 外部依赖实例(生命周期关联,优先声明)
|
||||
private Context mContext;
|
||||
private AppConfigUtils mAppConfigUtils;
|
||||
@@ -38,7 +83,8 @@ public class MainContentView {
|
||||
// 视图控件(按「布局→开关→文本→进度条→图标」功能归类)
|
||||
// 基础布局控件
|
||||
public RelativeLayout mainLayout;
|
||||
public BackgroundView backgroundView;
|
||||
public MemoryCachedBackgroundView backgroundView;
|
||||
LinearLayout mllBackgroundView;
|
||||
// 容器布局控件
|
||||
public LinearLayout llLeftSeekBar;
|
||||
public LinearLayout llRightSeekBar;
|
||||
@@ -59,6 +105,10 @@ public class MainContentView {
|
||||
public ImageView ivChargeReminderBattery;
|
||||
public ImageView ivUsageReminderBattery;
|
||||
|
||||
// 进度缓存(用于实时计算比值,避免频繁调用 getProgress())
|
||||
private int mCurrentChargeProgress;
|
||||
private int mCurrentUsageProgress;
|
||||
|
||||
// 内部复用资源(避免重复创建,优化性能)
|
||||
private BatteryDrawable mCurrentBatteryDrawable;
|
||||
private BatteryDrawable mChargeReminderBatteryDrawable;
|
||||
@@ -66,62 +116,27 @@ public class MainContentView {
|
||||
|
||||
// 配置变更确认对话框(单例复用,避免重复创建)
|
||||
private AlertDialog mConfigConfirmDialog;
|
||||
// 对话框 Builder(核心新增:解决 setMessage 不生效问题)
|
||||
private AlertDialog.Builder mDialogBuilder;
|
||||
// 临时存储变更数据(对话框确认前缓存,取消时恢复)
|
||||
private TempConfigData mTempConfigData;
|
||||
// 对话框状态锁(避免快速点击重复弹窗)
|
||||
private boolean isDialogShowing = false;
|
||||
|
||||
// ======================== 临时配置数据实体(缓存变更信息,取消时恢复)========================
|
||||
private static class TempConfigData {
|
||||
// 变更类型(区分开关/进度条,避免混淆)
|
||||
int changeType;
|
||||
// 原始值(取消时恢复用)
|
||||
boolean originalBooleanValue;
|
||||
int originalIntValue;
|
||||
// 变更后的值(确认时保存用)
|
||||
boolean newBooleanValue;
|
||||
int newIntValue;
|
||||
|
||||
// 构造方法(开关类型)
|
||||
TempConfigData(int changeType, boolean originalValue, boolean newValue) {
|
||||
this.changeType = changeType;
|
||||
this.originalBooleanValue = originalValue;
|
||||
this.newBooleanValue = newValue;
|
||||
}
|
||||
|
||||
// 构造方法(进度条类型)
|
||||
TempConfigData(int changeType, int originalValue, int newValue) {
|
||||
this.changeType = changeType;
|
||||
this.originalIntValue = originalValue;
|
||||
this.newIntValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 变更类型常量(区分不同控件,精准处理逻辑)
|
||||
private static final int CHANGE_TYPE_CHARGE_SWITCH = 1;
|
||||
private static final int CHANGE_TYPE_USAGE_SWITCH = 2;
|
||||
private static final int CHANGE_TYPE_SERVICE_SWITCH = 3;
|
||||
private static final int CHANGE_TYPE_CHARGE_SEEKBAR = 4;
|
||||
private static final int CHANGE_TYPE_USAGE_SEEKBAR = 5;
|
||||
|
||||
// ======================== 构造方法(初始化入口,逻辑闭环)========================
|
||||
public MainContentView(Context context, View rootView, OnViewActionListener actionListener) {
|
||||
LogUtils.d(TAG, "constructor: 开始初始化");
|
||||
LogUtils.d(TAG, "MainContentView() | context=" + context + " | rootView=" + rootView + " | actionListener=" + actionListener);
|
||||
// 初始化外部依赖
|
||||
this.mContext = context;
|
||||
this.mActionListener = actionListener;
|
||||
this.mAppConfigUtils = AppConfigUtils.getInstance(context.getApplicationContext());
|
||||
LogUtils.d(TAG, "constructor: 外部依赖初始化完成");
|
||||
|
||||
// 执行核心初始化流程(按顺序执行,避免依赖空指针)
|
||||
bindViews(rootView);
|
||||
initBatteryDrawables();
|
||||
initConfirmDialog(); // 先初始化对话框,再绑定监听
|
||||
initConfirmDialog();
|
||||
bindViewListeners();
|
||||
|
||||
LogUtils.d(TAG, "constructor: 整体初始化完成");
|
||||
LogUtils.d(TAG, "MainContentView 初始化完成");
|
||||
}
|
||||
|
||||
// ======================== 私有初始化方法(封装内部逻辑,仅暴露入口)========================
|
||||
@@ -129,10 +144,16 @@ public class MainContentView {
|
||||
* 绑定视图控件(显式强转适配 Java7,适配 API30 视图加载机制)
|
||||
*/
|
||||
private void bindViews(View rootView) {
|
||||
LogUtils.d(TAG, "bindViews: 开始绑定视图");
|
||||
LogUtils.d(TAG, "bindViews() | rootView=" + rootView);
|
||||
// 基础布局绑定
|
||||
mainLayout = (RelativeLayout) rootView.findViewById(R.id.activitymainRelativeLayout1);
|
||||
backgroundView = (BackgroundView) rootView.findViewById(R.id.fragmentmainviewBackgroundView1);
|
||||
//backgroundView = (BackgroundView) rootView.findViewById(R.id.fragmentmainviewBackgroundView1);
|
||||
mllBackgroundView = (LinearLayout) rootView.findViewById(R.id.ll_backgroundview);
|
||||
backgroundView = App.sMemoryCachedBackgroundView.getLastInstance(mContext);
|
||||
if (backgroundView.getParent() != null) {
|
||||
((ViewGroup) backgroundView.getParent()).removeView(backgroundView);
|
||||
}
|
||||
mllBackgroundView.addView(backgroundView);
|
||||
// 容器布局绑定
|
||||
llLeftSeekBar = (LinearLayout) rootView.findViewById(R.id.fragmentmainviewLinearLayout1);
|
||||
llRightSeekBar = (LinearLayout) rootView.findViewById(R.id.fragmentmainviewLinearLayout2);
|
||||
@@ -153,17 +174,20 @@ public class MainContentView {
|
||||
ivChargeReminderBattery = (ImageView) rootView.findViewById(R.id.fragmentandroidviewImageView3);
|
||||
ivUsageReminderBattery = (ImageView) rootView.findViewById(R.id.fragmentandroidviewImageView2);
|
||||
|
||||
// 初始化进度缓存(从配置读取初始值)
|
||||
mCurrentChargeProgress = mAppConfigUtils.getChargeReminderValue();
|
||||
mCurrentUsageProgress = mAppConfigUtils.getUsageReminderValue();
|
||||
|
||||
// 关键视图绑定校验(仅保留核心控件错误日志,精简冗余)
|
||||
if (mainLayout == null) LogUtils.e(TAG, "bindViews: mainLayout 绑定失败");
|
||||
if (backgroundView == null) LogUtils.e(TAG, "bindViews: backgroundView 绑定失败");
|
||||
LogUtils.d(TAG, "bindViews: 视图绑定完成");
|
||||
if (mainLayout == null) LogUtils.e(TAG, "mainLayout 绑定失败");
|
||||
if (backgroundView == null) LogUtils.e(TAG, "backgroundView 绑定失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化电池 Drawable(集成 BatteryDrawable,默认能量风格,适配小米机型渲染)
|
||||
*/
|
||||
private void initBatteryDrawables() {
|
||||
LogUtils.d(TAG, "initBatteryDrawables: 开始初始化电池 Drawable");
|
||||
LogUtils.d(TAG, "initBatteryDrawables()");
|
||||
// 当前电量 Drawable(颜色从资源读取,适配 API30 主题)
|
||||
int colorCurrent = getResourceColor(R.color.colorCurrent);
|
||||
mCurrentBatteryDrawable = new BatteryDrawable(colorCurrent);
|
||||
@@ -173,17 +197,15 @@ public class MainContentView {
|
||||
// 耗电提醒 Drawable
|
||||
int colorUsage = getResourceColor(R.color.colorUsege);
|
||||
mUsageReminderBatteryDrawable = new BatteryDrawable(colorUsage);
|
||||
|
||||
LogUtils.d(TAG, "initBatteryDrawables: 电池 Drawable 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置变更确认对话框(核心优化:保存 Builder 实例,解决消息不生效问题)
|
||||
*/
|
||||
private void initConfirmDialog() {
|
||||
LogUtils.d(TAG, "initConfirmDialog: 开始初始化确认对话框");
|
||||
LogUtils.d(TAG, "initConfirmDialog()");
|
||||
if (mContext == null) {
|
||||
LogUtils.e(TAG, "initConfirmDialog: Context 为空,初始化失败");
|
||||
LogUtils.e(TAG, "Context 为空,初始化失败");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -196,7 +218,7 @@ public class MainContentView {
|
||||
mDialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
confirmConfigChange(); // 确认变更,保存配置
|
||||
confirmConfigChange();
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
@@ -205,7 +227,7 @@ public class MainContentView {
|
||||
mDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
cancelConfigChange(); // 取消变更,恢复原始值
|
||||
cancelConfigChange();
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
@@ -214,7 +236,7 @@ public class MainContentView {
|
||||
mDialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
cancelConfigChange(); // 取消变更,恢复原始值
|
||||
cancelConfigChange();
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
@@ -223,22 +245,22 @@ public class MainContentView {
|
||||
mConfigConfirmDialog = mDialogBuilder.create();
|
||||
mConfigConfirmDialog.setCancelable(true);
|
||||
mConfigConfirmDialog.setCanceledOnTouchOutside(true);
|
||||
LogUtils.d(TAG, "initConfirmDialog: 确认对话框初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定视图事件监听(Java7 显式实现接口,适配 API30 事件分发,修复进度条弹窗失效)
|
||||
*/
|
||||
private void bindViewListeners() {
|
||||
LogUtils.d(TAG, "bindViewListeners: 开始绑定事件监听");
|
||||
LogUtils.d(TAG, "bindViewListeners()");
|
||||
// 依赖校验,避免空指针
|
||||
if (mAppConfigUtils == null || mActionListener == null || mConfigConfirmDialog == null) {
|
||||
LogUtils.e(TAG, "bindViewListeners: 依赖实例为空,跳过监听绑定");
|
||||
LogUtils.e(TAG, "依赖实例为空,跳过监听绑定");
|
||||
return;
|
||||
}
|
||||
|
||||
// 充电提醒进度条监听(使用 VerticalSeekBar 专属接口,确保弹窗100%触发)
|
||||
if (sbChargeReminder != null) {
|
||||
// 原有:触摸抬起/取消监听(用于配置确认)
|
||||
sbChargeReminder.setOnVerticalSeekBarTouchListener(new VerticalSeekBar.OnVerticalSeekBarTouchListener() {
|
||||
@Override
|
||||
public void onTouchUp(VerticalSeekBar seekBar, int progress) {
|
||||
@@ -250,9 +272,9 @@ public class MainContentView {
|
||||
}
|
||||
// 缓存变更数据,显示确认对话框
|
||||
mTempConfigData = new TempConfigData(CHANGE_TYPE_CHARGE_SEEKBAR, originalValue, progress);
|
||||
updateDialogMessageByChangeType(); // 更新提示语
|
||||
updateDialogMessageByChangeType();
|
||||
showConfigConfirmDialog();
|
||||
LogUtils.d(TAG, "ChargeReminderSeekBar: 触摸抬起触发变更,原始值=" + originalValue + ", 新进度=" + progress);
|
||||
LogUtils.d(TAG, "ChargeReminderSeekBar触摸抬起 | 原始值=" + originalValue + " | 新进度=" + progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -265,10 +287,34 @@ public class MainContentView {
|
||||
tvChargeReminderValue.setText(originalValue + "%");
|
||||
}
|
||||
seekBar.setProgress(originalValue);
|
||||
LogUtils.d(TAG, "ChargeReminderSeekBar: 触摸取消,进度回滚至=" + originalValue);
|
||||
// 恢复进度缓存
|
||||
mCurrentChargeProgress = originalValue;
|
||||
LogUtils.d(TAG, "ChargeReminderSeekBar触摸取消 | 进度回滚至=" + originalValue);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "bindViewListeners: 充电提醒进度条专属监听绑定完成");
|
||||
|
||||
// 新增:实时进度变化监听(用于比值预览)
|
||||
sbChargeReminder.setOnVerticalSeekBarChangeListener(new VerticalSeekBar.OnVerticalSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
mCurrentChargeProgress = progress;
|
||||
// 同步更新进度文本和电池图标(保持UI一致性)
|
||||
if (tvChargeReminderValue != null && mChargeReminderBatteryDrawable != null && ivChargeReminderBattery != null) {
|
||||
mChargeReminderBatteryDrawable.setBatteryValue(progress);
|
||||
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
|
||||
tvChargeReminderValue.setText(progress + "%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(VerticalSeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(VerticalSeekBar seekBar) {}
|
||||
});
|
||||
LogUtils.d(TAG, "充电提醒进度条专属监听绑定完成");
|
||||
}
|
||||
|
||||
// 充电提醒开关监听
|
||||
@@ -282,16 +328,17 @@ public class MainContentView {
|
||||
if (originalValue == newValue) return;
|
||||
// 缓存变更数据,显示确认对话框
|
||||
mTempConfigData = new TempConfigData(CHANGE_TYPE_CHARGE_SWITCH, originalValue, newValue);
|
||||
updateDialogMessageByChangeType(); // 更新提示语
|
||||
updateDialogMessageByChangeType();
|
||||
showConfigConfirmDialog();
|
||||
LogUtils.d(TAG, "cbEnableChargeReminder: 触发变更,原始值=" + originalValue + ", 变更后=" + newValue);
|
||||
LogUtils.d(TAG, "cbEnableChargeReminder点击 | 原始值=" + originalValue + " | 变更后=" + newValue);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "bindViewListeners: 充电提醒开关监听绑定完成");
|
||||
LogUtils.d(TAG, "充电提醒开关监听绑定完成");
|
||||
}
|
||||
|
||||
// 耗电提醒进度条监听(使用 VerticalSeekBar 专属接口,确保弹窗100%触发)
|
||||
if (sbUsageReminder != null) {
|
||||
// 原有:触摸抬起/取消监听(用于配置确认)
|
||||
sbUsageReminder.setOnVerticalSeekBarTouchListener(new VerticalSeekBar.OnVerticalSeekBarTouchListener() {
|
||||
@Override
|
||||
public void onTouchUp(VerticalSeekBar seekBar, int progress) {
|
||||
@@ -303,9 +350,9 @@ public class MainContentView {
|
||||
}
|
||||
// 缓存变更数据,显示确认对话框
|
||||
mTempConfigData = new TempConfigData(CHANGE_TYPE_USAGE_SEEKBAR, originalValue, progress);
|
||||
updateDialogMessageByChangeType(); // 更新提示语
|
||||
updateDialogMessageByChangeType();
|
||||
showConfigConfirmDialog();
|
||||
LogUtils.d(TAG, "UsageReminderSeekBar: 触摸抬起触发变更,原始值=" + originalValue + ", 新进度=" + progress);
|
||||
LogUtils.d(TAG, "UsageReminderSeekBar触摸抬起 | 原始值=" + originalValue + " | 新进度=" + progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -318,10 +365,34 @@ public class MainContentView {
|
||||
tvUsageReminderValue.setText(originalValue + "%");
|
||||
}
|
||||
seekBar.setProgress(originalValue);
|
||||
LogUtils.d(TAG, "UsageReminderSeekBar: 触摸取消,进度回滚至=" + originalValue);
|
||||
// 恢复进度缓存
|
||||
mCurrentUsageProgress = originalValue;
|
||||
LogUtils.d(TAG, "UsageReminderSeekBar触摸取消 | 进度回滚至=" + originalValue);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "bindViewListeners: 耗电提醒进度条专属监听绑定完成");
|
||||
|
||||
// 新增:实时进度变化监听(用于比值预览)
|
||||
sbUsageReminder.setOnVerticalSeekBarChangeListener(new VerticalSeekBar.OnVerticalSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
mCurrentUsageProgress = progress;
|
||||
// 同步更新进度文本和电池图标(保持UI一致性)
|
||||
if (tvUsageReminderValue != null && mUsageReminderBatteryDrawable != null && ivUsageReminderBattery != null) {
|
||||
mUsageReminderBatteryDrawable.setBatteryValue(progress);
|
||||
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
|
||||
tvUsageReminderValue.setText(progress + "%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(VerticalSeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(VerticalSeekBar seekBar) {}
|
||||
});
|
||||
LogUtils.d(TAG, "耗电提醒进度条专属监听绑定完成");
|
||||
}
|
||||
|
||||
// 耗电提醒开关监听
|
||||
@@ -335,12 +406,12 @@ public class MainContentView {
|
||||
if (originalValue == newValue) return;
|
||||
// 缓存变更数据,显示确认对话框
|
||||
mTempConfigData = new TempConfigData(CHANGE_TYPE_USAGE_SWITCH, originalValue, newValue);
|
||||
updateDialogMessageByChangeType(); // 更新提示语
|
||||
updateDialogMessageByChangeType();
|
||||
showConfigConfirmDialog();
|
||||
LogUtils.d(TAG, "cbEnableUsageReminder: 触发变更,原始值=" + originalValue + ", 变更后=" + newValue);
|
||||
LogUtils.d(TAG, "cbEnableUsageReminder点击 | 原始值=" + originalValue + " | 变更后=" + newValue);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "bindViewListeners: 耗电提醒开关监听绑定完成");
|
||||
LogUtils.d(TAG, "耗电提醒开关监听绑定完成");
|
||||
}
|
||||
|
||||
// 服务总开关监听(核心优化:逻辑与其他控件完全对齐)
|
||||
@@ -359,13 +430,13 @@ public class MainContentView {
|
||||
updateDialogMessageByChangeType();
|
||||
// 显示确认对话框
|
||||
showConfigConfirmDialog();
|
||||
LogUtils.d(TAG, "swEnableService: 触发变更,原始值=" + originalValue + ", 变更后=" + newValue);
|
||||
LogUtils.d(TAG, "swEnableService点击 | 原始值=" + originalValue + " | 变更后=" + newValue);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "bindViewListeners: 服务总开关监听绑定完成");
|
||||
LogUtils.d(TAG, "服务总开关监听绑定完成");
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "bindViewListeners: 所有事件监听绑定完成");
|
||||
LogUtils.d(TAG, "所有事件监听绑定完成");
|
||||
}
|
||||
|
||||
// ======================== 对外暴露核心方法(业务入口,精简参数,明确职责)========================
|
||||
@@ -374,9 +445,9 @@ public class MainContentView {
|
||||
* @param frameDrawable 进度条背景 Drawable(外部传入,适配主题切换)
|
||||
*/
|
||||
public void updateViewData(Drawable frameDrawable) {
|
||||
LogUtils.d(TAG, "updateViewData: 开始更新视图数据");
|
||||
LogUtils.d(TAG, "updateViewData() | frameDrawable=" + frameDrawable);
|
||||
if (mAppConfigUtils == null) {
|
||||
LogUtils.e(TAG, "updateViewData: AppConfigUtils 为空,跳过更新");
|
||||
LogUtils.e(TAG, "AppConfigUtils 为空,跳过更新");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -388,13 +459,15 @@ public class MainContentView {
|
||||
boolean usageEnable = mAppConfigUtils.isUsageReminderEnabled();
|
||||
// 从服务控制Bean读取状态,确保UI与实际一致
|
||||
boolean serviceEnable = getServiceEnableState();
|
||||
LogUtils.d(TAG, "updateViewData: 配置数据读取完成,charge=" + chargeVal + ", usage=" + usageVal + ", current=" + currentVal + ", serviceEnable=" + serviceEnable);
|
||||
// 更新进度缓存
|
||||
mCurrentChargeProgress = chargeVal;
|
||||
mCurrentUsageProgress = usageVal;
|
||||
LogUtils.d(TAG, "配置数据读取完成 | charge=" + chargeVal + " | usage=" + usageVal + " | current=" + currentVal + " | serviceEnable=" + serviceEnable);
|
||||
|
||||
// 进度条背景更新
|
||||
if (frameDrawable != null) {
|
||||
if (llLeftSeekBar != null) llLeftSeekBar.setBackground(frameDrawable);
|
||||
if (llRightSeekBar != null) llRightSeekBar.setBackground(frameDrawable);
|
||||
LogUtils.d(TAG, "updateViewData: 进度条背景更新完成");
|
||||
}
|
||||
|
||||
// 当前电量更新(联动 BatteryDrawable,实时刷新图标)
|
||||
@@ -406,7 +479,6 @@ public class MainContentView {
|
||||
tvCurrentBatteryValue.setTextColor(getResourceColor(R.color.colorCurrent));
|
||||
tvCurrentBatteryValue.setText(currentVal + "%");
|
||||
}
|
||||
LogUtils.d(TAG, "updateViewData: 当前电量视图更新完成");
|
||||
|
||||
// 充电提醒视图更新
|
||||
if (ivChargeReminderBattery != null && mChargeReminderBatteryDrawable != null) {
|
||||
@@ -419,7 +491,6 @@ public class MainContentView {
|
||||
}
|
||||
if (sbChargeReminder != null) sbChargeReminder.setProgress(chargeVal);
|
||||
if (cbEnableChargeReminder != null) cbEnableChargeReminder.setChecked(chargeEnable);
|
||||
LogUtils.d(TAG, "updateViewData: 充电提醒视图更新完成");
|
||||
|
||||
// 耗电提醒视图更新
|
||||
if (ivUsageReminderBattery != null && mUsageReminderBatteryDrawable != null) {
|
||||
@@ -432,7 +503,6 @@ public class MainContentView {
|
||||
}
|
||||
if (sbUsageReminder != null) sbUsageReminder.setProgress(usageVal);
|
||||
if (cbEnableUsageReminder != null) cbEnableUsageReminder.setChecked(usageEnable);
|
||||
LogUtils.d(TAG, "updateViewData: 耗电提醒视图更新完成");
|
||||
|
||||
// 服务开关+提示文本更新(确保状态准确)
|
||||
if (swEnableService != null) {
|
||||
@@ -440,9 +510,8 @@ public class MainContentView {
|
||||
swEnableService.setText(mContext.getString(R.string.txt_aboveswitch));
|
||||
}
|
||||
if (tvTips != null) tvTips.setText(mContext.getString(R.string.txt_aboveswitchtips));
|
||||
LogUtils.d(TAG, "updateViewData: 服务开关及提示文本更新完成");
|
||||
|
||||
LogUtils.d(TAG, "updateViewData: 所有视图数据更新完成");
|
||||
LogUtils.d(TAG, "所有视图数据更新完成");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,10 +519,10 @@ public class MainContentView {
|
||||
* @param value 电量值(自动校准 0-100,避免异常值)
|
||||
*/
|
||||
public void updateCurrentBattery(int value) {
|
||||
LogUtils.d(TAG, "updateCurrentBattery: 开始更新,原始值=" + value);
|
||||
LogUtils.d(TAG, "updateCurrentBattery() | 原始值=" + value);
|
||||
// 核心依赖校验
|
||||
if (tvCurrentBatteryValue == null || mCurrentBatteryDrawable == null || ivCurrentBattery == null) {
|
||||
LogUtils.e(TAG, "updateCurrentBattery: 视图/Drawable 为空,跳过更新");
|
||||
LogUtils.e(TAG, "视图/Drawable 为空,跳过更新");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -464,14 +533,14 @@ public class MainContentView {
|
||||
ivCurrentBattery.setImageDrawable(mCurrentBatteryDrawable);
|
||||
tvCurrentBatteryValue.setText(validValue + "%");
|
||||
|
||||
LogUtils.d(TAG, "updateCurrentBattery: 更新完成,校准后值=" + validValue);
|
||||
LogUtils.d(TAG, "更新完成 | 校准后值=" + validValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放资源(主动回收,适配 API30 资源管控机制,优化小米手机内存占用)
|
||||
*/
|
||||
public void releaseResources() {
|
||||
LogUtils.d(TAG, "releaseResources: 开始释放资源");
|
||||
LogUtils.d(TAG, "releaseResources()");
|
||||
// 释放对话框资源(安全销毁,避免内存泄漏)
|
||||
if (mConfigConfirmDialog != null) {
|
||||
if (mConfigConfirmDialog.isShowing()) {
|
||||
@@ -514,7 +583,7 @@ public class MainContentView {
|
||||
mAppConfigUtils = null;
|
||||
mActionListener = null;
|
||||
|
||||
LogUtils.d(TAG, "releaseResources: 所有资源释放完成");
|
||||
LogUtils.d(TAG, "所有资源释放完成");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,9 +591,9 @@ public class MainContentView {
|
||||
* @param enabled 服务启用状态
|
||||
*/
|
||||
public void setServiceSwitchChecked(boolean enabled) {
|
||||
LogUtils.d(TAG, "setServiceSwitchChecked() | enabled=" + enabled);
|
||||
if (swEnableService != null) {
|
||||
swEnableService.setChecked(enabled);
|
||||
LogUtils.d(TAG, "setServiceSwitchChecked: 服务开关UI更新为=" + enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,9 +602,9 @@ public class MainContentView {
|
||||
* @param enabled 是否允许点击
|
||||
*/
|
||||
public void setServiceSwitchEnabled(boolean enabled) {
|
||||
LogUtils.d(TAG, "setServiceSwitchEnabled() | enabled=" + enabled);
|
||||
if (swEnableService != null) {
|
||||
swEnableService.setEnabled(enabled);
|
||||
LogUtils.d(TAG, "setServiceSwitchEnabled: 服务开关点击状态更新为=" + enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,21 +613,22 @@ public class MainContentView {
|
||||
* 显示配置变更确认对话框(确保 Activity 处于前台,避免异常,防止重复弹窗)
|
||||
*/
|
||||
private void showConfigConfirmDialog() {
|
||||
LogUtils.d(TAG, "showConfigConfirmDialog() | isDialogShowing=" + isDialogShowing);
|
||||
// 对话框状态锁:正在显示则跳过,避免重复触发
|
||||
if (isDialogShowing) {
|
||||
LogUtils.d(TAG, "showConfigConfirmDialog: 对话框已显示,跳过重复调用");
|
||||
LogUtils.d(TAG, "对话框已显示,跳过重复调用");
|
||||
return;
|
||||
}
|
||||
// 基础校验:对话框/上下文/Builder 为空
|
||||
if (mDialogBuilder == null || mContext == null) {
|
||||
LogUtils.e(TAG, "showConfigConfirmDialog: 对话框Builder/上下文异常,无法显示");
|
||||
LogUtils.e(TAG, "对话框Builder/上下文异常,无法显示");
|
||||
if (mTempConfigData != null) cancelConfigChange();
|
||||
return;
|
||||
}
|
||||
// Activity 状态校验:避免销毁后弹窗崩溃(适配 API30)
|
||||
Activity activity = (Activity) mContext;
|
||||
if (activity.isFinishing() || activity.isDestroyed()) {
|
||||
LogUtils.e(TAG, "showConfigConfirmDialog: Activity 已销毁,无法显示对话框");
|
||||
LogUtils.e(TAG, "Activity 已销毁,无法显示对话框");
|
||||
if (mTempConfigData != null) cancelConfigChange();
|
||||
return;
|
||||
}
|
||||
@@ -575,15 +645,16 @@ public class MainContentView {
|
||||
mConfigConfirmDialog.setOnDismissListener(null);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "showConfigConfirmDialog: 确认对话框显示成功");
|
||||
LogUtils.d(TAG, "确认对话框显示成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认配置变更(保存数据+回调监听+更新视图)
|
||||
*/
|
||||
private void confirmConfigChange() {
|
||||
LogUtils.d(TAG, "confirmConfigChange() | mTempConfigData=" + mTempConfigData);
|
||||
if (mTempConfigData == null || mAppConfigUtils == null || mActionListener == null) {
|
||||
LogUtils.e(TAG, "confirmConfigChange: 依赖数据为空,确认失败");
|
||||
LogUtils.e(TAG, "依赖数据为空,确认失败");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -592,40 +663,40 @@ public class MainContentView {
|
||||
case CHANGE_TYPE_CHARGE_SWITCH:
|
||||
mAppConfigUtils.setChargeReminderEnabled(mTempConfigData.newBooleanValue);
|
||||
mActionListener.onChargeReminderSwitchChanged(mTempConfigData.newBooleanValue);
|
||||
LogUtils.d(TAG, "confirmConfigChange: 充电提醒开关确认,值=" + mTempConfigData.newBooleanValue);
|
||||
LogUtils.d(TAG, "充电提醒开关确认 | 值=" + mTempConfigData.newBooleanValue);
|
||||
break;
|
||||
// 耗电提醒开关
|
||||
case CHANGE_TYPE_USAGE_SWITCH:
|
||||
mAppConfigUtils.setUsageReminderEnabled(mTempConfigData.newBooleanValue);
|
||||
mActionListener.onUsageReminderSwitchChanged(mTempConfigData.newBooleanValue);
|
||||
LogUtils.d(TAG, "confirmConfigChange: 耗电提醒开关确认,值=" + mTempConfigData.newBooleanValue);
|
||||
LogUtils.d(TAG, "耗电提醒开关确认 | 值=" + mTempConfigData.newBooleanValue);
|
||||
break;
|
||||
// 服务总开关(核心:持久化配置+触发 Activity 回调)
|
||||
case CHANGE_TYPE_SERVICE_SWITCH:
|
||||
// 1. 设置服务启停
|
||||
if (mTempConfigData.newBooleanValue) {
|
||||
ControlCenterService.startControlCenterService(mContext);
|
||||
} else {
|
||||
ControlCenterService.stopControlCenterService(mContext);
|
||||
}
|
||||
if (mTempConfigData.newBooleanValue) {
|
||||
ControlCenterService.startControlCenterService(mContext);
|
||||
} else {
|
||||
ControlCenterService.stopControlCenterService(mContext);
|
||||
}
|
||||
// 2. 强制触发 Activity 回调,执行服务启停逻辑
|
||||
mActionListener.onServiceSwitchChanged(mTempConfigData.newBooleanValue);
|
||||
LogUtils.d(TAG, "confirmConfigChange: 服务开关确认,值=" + mTempConfigData.newBooleanValue + ",已持久化配置");
|
||||
LogUtils.d(TAG, "服务开关确认 | 值=" + mTempConfigData.newBooleanValue + ",已持久化配置");
|
||||
break;
|
||||
// 充电提醒进度条
|
||||
case CHANGE_TYPE_CHARGE_SEEKBAR:
|
||||
mAppConfigUtils.setChargeReminderValue(mTempConfigData.newIntValue);
|
||||
mActionListener.onChargeReminderProgressChanged(mTempConfigData.newIntValue);
|
||||
LogUtils.d(TAG, "confirmConfigChange: 充电提醒进度确认,值=" + mTempConfigData.newIntValue);
|
||||
LogUtils.d(TAG, "充电提醒进度确认 | 值=" + mTempConfigData.newIntValue);
|
||||
break;
|
||||
// 耗电提醒进度条
|
||||
case CHANGE_TYPE_USAGE_SEEKBAR:
|
||||
mAppConfigUtils.setUsageReminderValue(mTempConfigData.newIntValue);
|
||||
mActionListener.onUsageReminderProgressChanged(mTempConfigData.newIntValue);
|
||||
LogUtils.d(TAG, "confirmConfigChange: 耗电提醒进度确认,值=" + mTempConfigData.newIntValue);
|
||||
LogUtils.d(TAG, "耗电提醒进度确认 | 值=" + mTempConfigData.newIntValue);
|
||||
break;
|
||||
default:
|
||||
LogUtils.w(TAG, "confirmConfigChange: 未知变更类型,跳过");
|
||||
LogUtils.w(TAG, "未知变更类型,跳过");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -637,8 +708,9 @@ public class MainContentView {
|
||||
* 取消配置变更(恢复原始值+刷新视图,确保 UI 与配置一致)
|
||||
*/
|
||||
private void cancelConfigChange() {
|
||||
LogUtils.d(TAG, "cancelConfigChange() | mTempConfigData=" + mTempConfigData);
|
||||
if (mTempConfigData == null || mAppConfigUtils == null) {
|
||||
LogUtils.e(TAG, "cancelConfigChange: 依赖数据为空,取消失败");
|
||||
LogUtils.e(TAG, "依赖数据为空,取消失败");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -647,19 +719,19 @@ public class MainContentView {
|
||||
if (cbEnableChargeReminder != null) {
|
||||
cbEnableChargeReminder.setChecked(mTempConfigData.originalBooleanValue);
|
||||
}
|
||||
LogUtils.d(TAG, "cancelConfigChange: 充电提醒开关取消,恢复值=" + mTempConfigData.originalBooleanValue);
|
||||
LogUtils.d(TAG, "充电提醒开关取消 | 恢复值=" + mTempConfigData.originalBooleanValue);
|
||||
break;
|
||||
case CHANGE_TYPE_USAGE_SWITCH:
|
||||
if (cbEnableUsageReminder != null) {
|
||||
cbEnableUsageReminder.setChecked(mTempConfigData.originalBooleanValue);
|
||||
}
|
||||
LogUtils.d(TAG, "cancelConfigChange: 耗电提醒开关取消,恢复值=" + mTempConfigData.originalBooleanValue);
|
||||
LogUtils.d(TAG, "耗电提醒开关取消 | 恢复值=" + mTempConfigData.originalBooleanValue);
|
||||
break;
|
||||
case CHANGE_TYPE_SERVICE_SWITCH:
|
||||
if (swEnableService != null) {
|
||||
swEnableService.setChecked(mTempConfigData.originalBooleanValue);
|
||||
}
|
||||
LogUtils.d(TAG, "cancelConfigChange: 服务开关取消,恢复值=" + mTempConfigData.originalBooleanValue);
|
||||
LogUtils.d(TAG, "服务开关取消 | 恢复值=" + mTempConfigData.originalBooleanValue);
|
||||
break;
|
||||
case CHANGE_TYPE_CHARGE_SEEKBAR:
|
||||
if (sbChargeReminder != null) {
|
||||
@@ -670,7 +742,7 @@ public class MainContentView {
|
||||
ivChargeReminderBattery.setImageDrawable(mChargeReminderBatteryDrawable);
|
||||
tvChargeReminderValue.setText(mTempConfigData.originalIntValue + "%");
|
||||
}
|
||||
LogUtils.d(TAG, "cancelConfigChange: 充电提醒进度取消,恢复值=" + mTempConfigData.originalIntValue);
|
||||
LogUtils.d(TAG, "充电提醒进度取消 | 恢复值=" + mTempConfigData.originalIntValue);
|
||||
break;
|
||||
case CHANGE_TYPE_USAGE_SEEKBAR:
|
||||
if (sbUsageReminder != null) {
|
||||
@@ -681,10 +753,10 @@ public class MainContentView {
|
||||
ivUsageReminderBattery.setImageDrawable(mUsageReminderBatteryDrawable);
|
||||
tvUsageReminderValue.setText(mTempConfigData.originalIntValue + "%");
|
||||
}
|
||||
LogUtils.d(TAG, "cancelConfigChange: 耗电提醒进度取消,恢复值=" + mTempConfigData.originalIntValue);
|
||||
LogUtils.d(TAG, "耗电提醒进度取消 | 恢复值=" + mTempConfigData.originalIntValue);
|
||||
break;
|
||||
default:
|
||||
LogUtils.w(TAG, "cancelConfigChange: 未知变更类型,跳过");
|
||||
LogUtils.w(TAG, "未知变更类型,跳过");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -692,40 +764,11 @@ public class MainContentView {
|
||||
mTempConfigData = null;
|
||||
}
|
||||
|
||||
// ======================== 内部工具方法(封装重复逻辑,提升复用性)========================
|
||||
/**
|
||||
* 获取资源颜色(适配 API30 主题颜色读取机制,兼容低版本,优化小米机型颜色显示,防御空指针)
|
||||
* @param colorResId 颜色资源 ID
|
||||
* @return 校准后的颜色值
|
||||
*/
|
||||
private int getResourceColor(int colorResId) {
|
||||
// 空指针防御:Context 为空返回默认黑色
|
||||
if (mContext == null) {
|
||||
LogUtils.e(TAG, "getResourceColor: Context 为空,返回默认黑色");
|
||||
return 0xFF000000;
|
||||
}
|
||||
// 适配 API30 主题颜色读取
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return mContext.getResources().getColor(colorResId, mContext.getTheme());
|
||||
} else {
|
||||
return mContext.getResources().getColor(colorResId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务启用状态(统一从服务控制Bean读取,确保全链路状态一致)
|
||||
* @return 服务启用状态(true=启用,false=禁用)
|
||||
*/
|
||||
private boolean getServiceEnableState() {
|
||||
ControlCenterServiceBean serviceBean = ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class);
|
||||
// 本地无配置时,默认禁用服务(与服务初始化逻辑对齐)
|
||||
return serviceBean != null && serviceBean.isEnableService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据变更类型更新对话框提示语(核心优化:通过 Builder 更新,确保生效)
|
||||
*/
|
||||
private void updateDialogMessageByChangeType() {
|
||||
LogUtils.d(TAG, "updateDialogMessageByChangeType() | mTempConfigData=" + mTempConfigData);
|
||||
if (mDialogBuilder == null || mTempConfigData == null) return;
|
||||
String message;
|
||||
if (mTempConfigData.changeType == CHANGE_TYPE_SERVICE_SWITCH) {
|
||||
@@ -741,13 +784,61 @@ public class MainContentView {
|
||||
mDialogBuilder.setMessage(message);
|
||||
}
|
||||
|
||||
// ======================== 事件回调接口(解耦视图与业务,提升扩展性)========================
|
||||
public interface OnViewActionListener {
|
||||
void onChargeReminderSwitchChanged(boolean isChecked);
|
||||
void onUsageReminderSwitchChanged(boolean isChecked);
|
||||
void onServiceSwitchChanged(boolean isChecked);
|
||||
void onChargeReminderProgressChanged(int progress);
|
||||
void onUsageReminderProgressChanged(int progress);
|
||||
// ======================== 内部工具方法(封装重复逻辑,提升复用性)========================
|
||||
/**
|
||||
* 实时计算并更新比值预览(sbUsageReminder / sbChargeReminder)
|
||||
* 处理除数为0的情况,避免崩溃
|
||||
*/
|
||||
// private void updateRatioPreview() {
|
||||
// if (mTvRatioPreview == null) return;
|
||||
// float ratio;
|
||||
// // 处理除数为0:充电进度为0时显示0(可根据需求改为“--”)
|
||||
// if (mCurrentChargeProgress == 0) {
|
||||
// ratio = 0.0f;
|
||||
// } else {
|
||||
// ratio = (float) mCurrentUsageProgress / mCurrentChargeProgress;
|
||||
// }
|
||||
// // 格式化比值:保留1位小数,适配本地化(解决小米手机小数分隔符问题)
|
||||
// String ratioText = String.format(Locale.getDefault(), "比值:%.1f", ratio);
|
||||
// mTvRatioPreview.setText(ratioText);
|
||||
// // 触发比值变化回调
|
||||
// if (mActionListener != null) {
|
||||
// mActionListener.onRatioChanged(ratio);
|
||||
// }
|
||||
// LogUtils.d(TAG, "比值预览更新 | usage=" + mCurrentUsageProgress + " | charge=" + mCurrentChargeProgress + " | ratio=" + ratio);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取资源颜色(适配 API30 主题颜色读取机制,兼容低版本,优化小米机型颜色显示,防御空指针)
|
||||
* @param colorResId 颜色资源 ID
|
||||
* @return 校准后的颜色值
|
||||
*/
|
||||
private int getResourceColor(int colorResId) {
|
||||
LogUtils.d(TAG, "getResourceColor() | colorResId=" + colorResId);
|
||||
// 空指针防御:Context 为空返回默认黑色
|
||||
if (mContext == null) {
|
||||
LogUtils.e(TAG, "Context 为空,返回默认黑色");
|
||||
return 0xFF000000;
|
||||
}
|
||||
// 适配 API30 主题颜色读取
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return mContext.getResources().getColor(colorResId, mContext.getTheme());
|
||||
} else {
|
||||
return mContext.getResources().getColor(colorResId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务启用状态(统一从服务控制Bean读取,确保全链路状态一致)
|
||||
* @return 服务启用状态(true=启用,false=禁用)
|
||||
*/
|
||||
private boolean getServiceEnableState() {
|
||||
LogUtils.d(TAG, "getServiceEnableState()");
|
||||
ControlCenterServiceBean serviceBean = ControlCenterServiceBean.loadBean(mContext, ControlCenterServiceBean.class);
|
||||
// 本地无配置时,默认禁用服务(与服务初始化逻辑对齐)
|
||||
boolean state = serviceBean != null && serviceBean.isEnableService();
|
||||
LogUtils.d(TAG, "服务启用状态获取完成 | state=" + state);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
package cc.winboll.studio.powerbell.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.powerbell.models.BackgroundBean;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/21 20:43
|
||||
* @Describe 单实例缓存版背景视图控件(基于Java7)
|
||||
* 核心:通过静态属性保存当前缓存路径和实例,支持强制重载图片
|
||||
* 新增:SP持久化最后加载路径、获取最后加载实例功能
|
||||
*/
|
||||
public class MemoryCachedBackgroundView extends BackgroundView {
|
||||
public static final String TAG = "MemoryCachedBackgroundView";
|
||||
// 静态属性:保存当前缓存的路径和实例(替代原Map,仅维护单实例)
|
||||
private static String sCachedImagePath;
|
||||
private static MemoryCachedBackgroundView sCachedView;
|
||||
// SP相关常量
|
||||
private static final String SP_NAME = "MemoryCachedBackgroundView_SP";
|
||||
private static final String KEY_LAST_LOAD_IMAGE_PATH = "last_load_image_path";
|
||||
|
||||
// ====================================== 构造器(继承并兼容父类) ======================================
|
||||
private MemoryCachedBackgroundView(Context context) {
|
||||
super(context);
|
||||
LogUtils.d(TAG, "构造器1:创建MemoryCachedBackgroundView实例");
|
||||
}
|
||||
|
||||
private MemoryCachedBackgroundView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
LogUtils.d(TAG, "构造器2:创建MemoryCachedBackgroundView实例");
|
||||
}
|
||||
|
||||
private MemoryCachedBackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
LogUtils.d(TAG, "构造器3:创建MemoryCachedBackgroundView实例");
|
||||
}
|
||||
|
||||
// ====================================== 核心静态方法:获取/创建缓存实例 ======================================
|
||||
/**
|
||||
* 从缓存获取或创建MemoryCachedBackgroundView实例
|
||||
* @param context 上下文
|
||||
* @param imagePath 图片绝对路径(作为缓存标识)
|
||||
* @param isReload 是否强制重新加载图片(路径匹配时仍刷新)
|
||||
* @return 缓存/新创建的MemoryCachedBackgroundView实例
|
||||
*/
|
||||
public static MemoryCachedBackgroundView getInstance(Context context, String imagePath, boolean isReload) {
|
||||
LogUtils.d(TAG, "getInstance() 调用 | 图片路径:" + imagePath + " | 是否重载:" + isReload);
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
LogUtils.e(TAG, "getInstance():图片路径为空,创建空实例");
|
||||
return new MemoryCachedBackgroundView(context);
|
||||
}
|
||||
|
||||
// 1. 路径匹配缓存 → 判断是否强制重载
|
||||
if (imagePath.equals(sCachedImagePath) && sCachedView != null) {
|
||||
LogUtils.d(TAG, "getInstance():路径已缓存,当前缓存实例有效");
|
||||
if (isReload) {
|
||||
LogUtils.d(TAG, "getInstance():强制重载图片 | " + imagePath);
|
||||
sCachedView.loadImage(imagePath);
|
||||
} else {
|
||||
LogUtils.d(TAG, "getInstance():使用缓存实例,无需重载 | " + imagePath);
|
||||
}
|
||||
return sCachedView;
|
||||
}
|
||||
|
||||
// 2. 路径不匹配/无缓存 → 新建实例并更新静态缓存
|
||||
LogUtils.d(TAG, "getInstance():路径未缓存,新建实例 | " + imagePath);
|
||||
sCachedView = new MemoryCachedBackgroundView(context);
|
||||
sCachedImagePath = imagePath;
|
||||
sCachedView.loadImage(imagePath);
|
||||
return sCachedView;
|
||||
}
|
||||
|
||||
// ====================================== 新增功能:获取最后加载的实例 ======================================
|
||||
/**
|
||||
* 获取最后一次loadImage的路径对应的实例
|
||||
* 无实例则创建并加载图片,同时更新静态缓存
|
||||
* @param context 上下文
|
||||
* @return 最后加载路径对应的实例
|
||||
*/
|
||||
public static MemoryCachedBackgroundView getLastInstance(Context context) {
|
||||
LogUtils.d(TAG, "getLastInstance() 调用");
|
||||
// 1. 从SP获取最后加载的路径
|
||||
String lastPath = getLastLoadImagePath(context);
|
||||
if (TextUtils.isEmpty(lastPath)) {
|
||||
LogUtils.e(TAG, "getLastInstance():无最后加载路径,创建空实例");
|
||||
return new MemoryCachedBackgroundView(context);
|
||||
}
|
||||
|
||||
// 2. 路径匹配当前缓存 → 直接返回
|
||||
if (lastPath.equals(sCachedImagePath) && sCachedView != null) {
|
||||
LogUtils.d(TAG, "getLastInstance():使用最后路径缓存实例 | " + lastPath);
|
||||
return sCachedView;
|
||||
}
|
||||
|
||||
// 3. 路径不匹配 → 新建实例并更新缓存
|
||||
LogUtils.d(TAG, "getLastInstance():最后路径未缓存,新建实例并加载 | " + lastPath);
|
||||
sCachedView = new MemoryCachedBackgroundView(context);
|
||||
sCachedImagePath = lastPath;
|
||||
sCachedView.loadImage(lastPath);
|
||||
return sCachedView;
|
||||
}
|
||||
|
||||
// ====================================== 工具方法:SP持久化最后加载路径 ======================================
|
||||
/**
|
||||
* 保存最后一次loadImage的路径到SP
|
||||
* @param context 上下文
|
||||
* @param imagePath 图片路径
|
||||
*/
|
||||
private static void saveLastLoadImagePath(Context context, String imagePath) {
|
||||
if (TextUtils.isEmpty(imagePath) || context == null) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().putString(KEY_LAST_LOAD_IMAGE_PATH, imagePath).apply();
|
||||
LogUtils.d(TAG, "saveLastLoadImagePath():已保存最后路径 | " + imagePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SP获取最后一次loadImage的路径
|
||||
* @param context 上下文
|
||||
* @return 最后加载的图片路径,空则返回null
|
||||
*/
|
||||
public static String getLastLoadImagePath(Context context) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
String lastPath = sp.getString(KEY_LAST_LOAD_IMAGE_PATH, null);
|
||||
LogUtils.d(TAG, "getLastLoadImagePath():获取最后路径 | " + lastPath);
|
||||
return lastPath;
|
||||
}
|
||||
|
||||
// ====================================== 工具方法:缓存管理 ======================================
|
||||
/**
|
||||
* 清除当前缓存实例和路径
|
||||
*/
|
||||
public static void clearCache() {
|
||||
LogUtils.d(TAG, "clearCache() 调用 | 当前缓存路径:" + sCachedImagePath);
|
||||
sCachedView = null;
|
||||
sCachedImagePath = null;
|
||||
LogUtils.d(TAG, "clearCache():已清除当前缓存实例");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定路径的缓存(仅当路径匹配当前缓存时生效)
|
||||
* @param imagePath 图片路径
|
||||
*/
|
||||
public static void removeCache(String imagePath) {
|
||||
LogUtils.d(TAG, "removeCache() 调用 | 图片路径:" + imagePath);
|
||||
if (TextUtils.isEmpty(imagePath)) {
|
||||
LogUtils.e(TAG, "removeCache():图片路径为空,清除失败");
|
||||
return;
|
||||
}
|
||||
if (imagePath.equals(sCachedImagePath)) {
|
||||
clearCache();
|
||||
// 同步删除SP中最后路径记录
|
||||
clearLastLoadImagePath(getContextFromCache());
|
||||
LogUtils.d(TAG, "removeCache():已清除匹配路径的缓存 | " + imagePath);
|
||||
} else {
|
||||
LogUtils.d(TAG, "removeCache():路径不匹配当前缓存,无需清除 | " + imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有缓存(同clearCache,保持方法兼容性)
|
||||
*/
|
||||
public static void clearAllCache() {
|
||||
LogUtils.d(TAG, "clearAllCache() 调用");
|
||||
clearCache();
|
||||
clearLastLoadImagePath(getContextFromCache());
|
||||
LogUtils.d(TAG, "clearAllCache():已清除所有缓存及最后路径记录");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在缓存实例
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
public static boolean hasCache() {
|
||||
return sCachedView != null && !TextUtils.isEmpty(sCachedImagePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除SP中最后加载的路径记录
|
||||
* @param context 上下文
|
||||
*/
|
||||
public static void clearLastLoadImagePath(Context context) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().remove(KEY_LAST_LOAD_IMAGE_PATH).apply();
|
||||
LogUtils.d(TAG, "clearLastLoadImagePath():已清除最后路径记录");
|
||||
}
|
||||
|
||||
// ====================================== 辅助方法:从缓存获取上下文 ======================================
|
||||
/**
|
||||
* 从缓存实例中获取上下文(用于无外部上下文时的SP操作)
|
||||
* @return 上下文实例,无则返回null
|
||||
*/
|
||||
private static Context getContextFromCache() {
|
||||
return sCachedView != null ? sCachedView.getContext() : null;
|
||||
}
|
||||
|
||||
// ====================================== 重写父类方法:增强日志+SP持久化 ======================================
|
||||
@Override
|
||||
public void loadImage(String imagePath) {
|
||||
LogUtils.d(TAG, "loadImage() 重载方法调用 | 图片路径:" + imagePath);
|
||||
super.loadImage(imagePath);
|
||||
// 保存最后加载路径到SP
|
||||
saveLastLoadImagePath(getContext(), imagePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadByBackgroundBean(BackgroundBean bean) {
|
||||
LogUtils.d(TAG, "loadBackgroundBean() 重载方法调用 | BackgroundBean:" + (bean == null ? "null" : bean.toString()));
|
||||
super.loadByBackgroundBean(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadByBackgroundBean(BackgroundBean bean, boolean isRefresh) {
|
||||
LogUtils.d(TAG, "loadBackgroundBean() 重载方法调用 | BackgroundBean:" + (bean == null ? "null" : bean.toString()) + " | 是否刷新:" + isRefresh);
|
||||
super.loadByBackgroundBean(bean, isRefresh);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,49 +5,19 @@ import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/12/17 14:11
|
||||
* @Describe 垂直进度条控件,适配 API30,支持逆时针旋转(0在下,100在上),修复滑块同步+弹窗触发bug
|
||||
* 新增:实时进度变化监听接口,支持拖动时实时回调进度
|
||||
*/
|
||||
public class VerticalSeekBar extends SeekBar {
|
||||
// ======================== 静态常量 =========================
|
||||
private static final String TAG = VerticalSeekBar.class.getSimpleName();
|
||||
|
||||
// ======================== 成员变量 =========================
|
||||
private volatile int mProgress = -1; // 当前进度缓存,修复滑块同步问题
|
||||
private OnVerticalSeekBarTouchListener mTouchListener; // 触摸事件回调接口(核心新增)
|
||||
|
||||
// ======================== 构造方法 =========================
|
||||
public VerticalSeekBar(Context context) {
|
||||
super(context);
|
||||
initView();
|
||||
LogUtils.d(TAG, "VerticalSeekBar: 单参数构造方法初始化完成");
|
||||
}
|
||||
|
||||
public VerticalSeekBar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView();
|
||||
LogUtils.d(TAG, "VerticalSeekBar: 双参数构造方法初始化完成");
|
||||
}
|
||||
|
||||
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initView();
|
||||
LogUtils.d(TAG, "VerticalSeekBar: 三参数构造方法初始化完成");
|
||||
}
|
||||
|
||||
// ======================== 初始化方法 =========================
|
||||
private void initView() {
|
||||
// 移除水平默认阴影,优化垂直显示效果,减少 API30 不必要的绘制开销
|
||||
setBackgroundDrawable(null);
|
||||
LogUtils.d(TAG, "initView: 视图初始化完成,移除默认背景阴影");
|
||||
}
|
||||
|
||||
// ======================== 核心接口(新增,用于弹窗触发回调)========================
|
||||
// ======================== 接口定义(前置,便于外部调用)========================
|
||||
/**
|
||||
* 垂直进度条触摸事件回调接口,解决原生 OnSeekBarChangeListener 回调失效问题
|
||||
* 直接在触摸抬起时回调,确保配置变更对话框100%触发
|
||||
@@ -68,6 +38,67 @@ public class VerticalSeekBar extends SeekBar {
|
||||
void onTouchCancel(VerticalSeekBar seekBar, int progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 垂直进度条实时进度变化监听接口
|
||||
* 支持拖动过程中实时回调进度,用于比值预览等实时UI更新场景
|
||||
*/
|
||||
public interface OnVerticalSeekBarChangeListener {
|
||||
/**
|
||||
* 进度变化时回调
|
||||
* @param seekBar 当前垂直进度条实例
|
||||
* @param progress 当前进度(0~100)
|
||||
* @param fromUser 是否是用户触摸导致的进度变化
|
||||
*/
|
||||
void onProgressChanged(VerticalSeekBar seekBar, int progress, boolean fromUser);
|
||||
|
||||
/**
|
||||
* 开始触摸进度条时回调
|
||||
* @param seekBar 当前垂直进度条实例
|
||||
*/
|
||||
void onStartTrackingTouch(VerticalSeekBar seekBar);
|
||||
|
||||
/**
|
||||
* 停止触摸进度条时回调
|
||||
* @param seekBar 当前垂直进度条实例
|
||||
*/
|
||||
void onStopTrackingTouch(VerticalSeekBar seekBar);
|
||||
}
|
||||
|
||||
// ======================== 成员变量 =========================
|
||||
// 核心状态:当前进度缓存,修复滑块同步问题(volatile 保证多线程可见性)
|
||||
private volatile int mProgress = -1;
|
||||
// 监听接口:触摸事件回调(原有,用于弹窗触发)
|
||||
private OnVerticalSeekBarTouchListener mTouchListener;
|
||||
// 监听接口:实时进度变化回调(新增,用于比值计算)
|
||||
private OnVerticalSeekBarChangeListener mProgressChangeListener;
|
||||
|
||||
// ======================== 构造方法 =========================
|
||||
public VerticalSeekBar(Context context) {
|
||||
super(context);
|
||||
initView();
|
||||
LogUtils.d(TAG, "VerticalSeekBar(Context) 初始化");
|
||||
}
|
||||
|
||||
public VerticalSeekBar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView();
|
||||
LogUtils.d(TAG, "VerticalSeekBar(Context, AttributeSet) 初始化");
|
||||
}
|
||||
|
||||
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initView();
|
||||
LogUtils.d(TAG, "VerticalSeekBar(Context, AttributeSet, int) 初始化");
|
||||
}
|
||||
|
||||
// ======================== 初始化方法 =========================
|
||||
private void initView() {
|
||||
// 移除水平默认阴影,优化垂直显示效果,减少 API30 不必要的绘制开销
|
||||
setBackgroundDrawable(null);
|
||||
LogUtils.d(TAG, "initView: 移除默认背景阴影,完成视图初始化");
|
||||
}
|
||||
|
||||
// ======================== 对外设置方法(监听接口绑定)========================
|
||||
/**
|
||||
* 设置触摸事件监听器(给外部调用,如 MainContentView 绑定)
|
||||
* @param listener 触摸事件回调实例
|
||||
@@ -77,7 +108,16 @@ public class VerticalSeekBar extends SeekBar {
|
||||
LogUtils.d(TAG, "setOnVerticalSeekBarTouchListener: 触摸监听器绑定完成");
|
||||
}
|
||||
|
||||
// ======================== 重写测量/布局/绘制方法 =========================
|
||||
/**
|
||||
* 设置实时进度变化监听器(给外部调用,如 MainContentView 绑定)
|
||||
* @param listener 实时进度变化回调实例
|
||||
*/
|
||||
public void setOnVerticalSeekBarChangeListener(OnVerticalSeekBarChangeListener listener) {
|
||||
this.mProgressChangeListener = listener;
|
||||
LogUtils.d(TAG, "setOnVerticalSeekBarChangeListener: 实时进度监听器绑定完成");
|
||||
}
|
||||
|
||||
// ======================== 重写系统方法(测量/布局/绘制)========================
|
||||
@Override
|
||||
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
|
||||
@@ -97,51 +137,13 @@ public class VerticalSeekBar extends SeekBar {
|
||||
canvas.rotate(-90);
|
||||
canvas.translate(-getHeight(), 0);
|
||||
super.onDraw(canvas);
|
||||
LogUtils.v(TAG, "onDraw: 垂直绘制完成,旋转角度=-90°");
|
||||
LogUtils.v(TAG, "onDraw: 完成垂直绘制,旋转角度=-90°");
|
||||
}
|
||||
|
||||
// ======================== 重写触摸事件(优化事件透传,新增接口回调)========================
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// 先调用父类方法,保留原生监听器兼容性,同时强制透传事件
|
||||
super.onTouchEvent(event);
|
||||
boolean handled = true; // 强制消费事件,避免事件被拦截导致回调丢失
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
LogUtils.d(TAG, "onTouchEvent: 触摸按下,坐标Y=" + event.getY());
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
calculateProgress(event.getY());
|
||||
setProgress(mProgress);
|
||||
LogUtils.v(TAG, "onTouchEvent: 触摸滑动,进度更新为=" + mProgress);
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
calculateProgress(event.getY());
|
||||
setProgress(mProgress);
|
||||
LogUtils.d(TAG, "onTouchEvent: 触摸抬起,触发弹窗回调,进度=" + mProgress);
|
||||
// 核心:调用新增接口,直接通知外部触发配置变更对话框
|
||||
if (mTouchListener != null) {
|
||||
mTouchListener.onTouchUp(this, mProgress);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
LogUtils.d(TAG, "onTouchEvent: 触摸取消,进度保持=" + getProgress());
|
||||
// 可选:触摸取消时回调,外部可做进度回滚处理
|
||||
if (mTouchListener != null) {
|
||||
mTouchListener.onTouchCancel(this, getProgress());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
// ======================== 重写进度设置方法 =========================
|
||||
// ======================== 重写进度设置方法(修复滑块同步+新增实时回调)========================
|
||||
/**
|
||||
* 重写进度设置,调用尺寸变化方法强制刷新,解决 setProgress 滑块不跟随问题
|
||||
* 新增:支持外部调用 setProgress 时触发实时进度回调
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setProgress(int progress) {
|
||||
@@ -149,7 +151,68 @@ public class VerticalSeekBar extends SeekBar {
|
||||
// 强制触发尺寸变化,同步刷新滑块位置(核心bug修复逻辑)
|
||||
onSizeChanged(getWidth(), getHeight(), 0, 0);
|
||||
mProgress = progress;
|
||||
LogUtils.d(TAG, "setProgress: 进度设置完成,进度=" + progress + ", 滑块同步刷新");
|
||||
LogUtils.d(TAG, "setProgress: 进度设置为" + progress + ",滑块同步刷新");
|
||||
// 触发实时进度监听(外部调用 setProgress 时 fromUser 为 false)
|
||||
if (mProgressChangeListener != null) {
|
||||
mProgressChangeListener.onProgressChanged(this, progress, false);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 重写触摸事件(优化事件透传+实时进度回调)========================
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// 先调用父类方法,保留原生监听器兼容性,同时强制透传事件
|
||||
super.onTouchEvent(event);
|
||||
boolean handled = true; // 强制消费事件,避免事件被拦截导致回调丢失
|
||||
boolean fromUser = true; // 标记是否是用户触摸导致的进度变化
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
LogUtils.d(TAG, "onTouchEvent: 触摸按下,Y坐标=" + event.getY());
|
||||
// 触发实时进度监听:开始触摸
|
||||
if (mProgressChangeListener != null) {
|
||||
mProgressChangeListener.onStartTrackingTouch(this);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
calculateProgress(event.getY());
|
||||
setProgress(mProgress);
|
||||
LogUtils.v(TAG, "onTouchEvent: 触摸滑动,进度更新为" + mProgress);
|
||||
// 触发实时进度监听:进度变化
|
||||
if (mProgressChangeListener != null) {
|
||||
mProgressChangeListener.onProgressChanged(this, mProgress, fromUser);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
calculateProgress(event.getY());
|
||||
setProgress(mProgress);
|
||||
LogUtils.d(TAG, "onTouchEvent: 触摸抬起,进度=" + mProgress + ",触发弹窗回调");
|
||||
// 触发实时进度监听:停止触摸
|
||||
if (mProgressChangeListener != null) {
|
||||
mProgressChangeListener.onProgressChanged(this, mProgress, fromUser);
|
||||
mProgressChangeListener.onStopTrackingTouch(this);
|
||||
}
|
||||
// 核心:调用原有触摸接口,通知外部触发配置变更对话框
|
||||
if (mTouchListener != null) {
|
||||
mTouchListener.onTouchUp(this, mProgress);
|
||||
}
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
LogUtils.d(TAG, "onTouchEvent: 触摸取消,当前进度=" + getProgress());
|
||||
// 触发实时进度监听:停止触摸
|
||||
if (mProgressChangeListener != null) {
|
||||
mProgressChangeListener.onStopTrackingTouch(this);
|
||||
}
|
||||
// 可选:触摸取消时回调,外部可做进度回滚处理
|
||||
if (mTouchListener != null) {
|
||||
mTouchListener.onTouchCancel(this, getProgress());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
// ======================== 内部工具方法 =========================
|
||||
@@ -162,7 +225,7 @@ public class VerticalSeekBar extends SeekBar {
|
||||
mProgress = getMax() - (int) (getMax() * touchY / getHeight());
|
||||
// 校准进度范围,防止超出 0~100(兼容 API30 进度边界校验)
|
||||
mProgress = Math.max(0, Math.min(mProgress, getMax()));
|
||||
LogUtils.v(TAG, "calculateProgress: 进度计算完成,触摸Y=" + touchY + ", 计算进度=" + mProgress);
|
||||
LogUtils.v(TAG, "calculateProgress: 触摸Y=" + touchY + ",计算进度=" + mProgress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
powerbell/src/main/res/drawable/blank100x100.png
Normal file
BIN
powerbell/src/main/res/drawable/blank100x100.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
Binary file not shown.
|
Before Width: | Height: | Size: 100 B |
@@ -40,17 +40,17 @@
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="Origin BG"
|
||||
android:id="@+id/activitybackgroundpictureAButton5"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_margin="5dp"/>
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundsettingsAButton1"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="36dp"
|
||||
android:text="Received BG"
|
||||
android:id="@+id/activitybackgroundpictureAButton4"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_margin="5dp"/>
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundsettingsAButton2"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
android:text="◎"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton1"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton3"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
@@ -74,7 +74,7 @@
|
||||
android:text="☑"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton2"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton4"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
@@ -82,8 +82,7 @@
|
||||
android:text="♾"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton9"
|
||||
android:onClick="onNetworkBackgroundDialog"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton5"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -99,7 +98,7 @@
|
||||
android:text="[+]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton3"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton6"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
@@ -107,7 +106,7 @@
|
||||
android:text="[+~]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton6"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton7"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
@@ -115,7 +114,7 @@
|
||||
android:text="[◐]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton7"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton8"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
@@ -123,8 +122,8 @@
|
||||
android:text="[©]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundsettingsAButton1"
|
||||
android:onClick="onColorPaletteDialog"/>
|
||||
android:onClick="onColorPaletteDialog"
|
||||
android:id="@+id/activitybackgroundsettingsAButton9"/>
|
||||
|
||||
<cc.winboll.studio.libaes.views.AButton
|
||||
android:layout_width="50dp"
|
||||
@@ -132,7 +131,7 @@
|
||||
android:text="[○]"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:id="@+id/activitybackgroundpictureAButton8"/>
|
||||
android:id="@+id/activitybackgroundsettingsAButton10"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,259 +1,247 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 顶部Toolbar(首屏核心,同步加载,保留原有ASupportToolbar) -->
|
||||
<cc.winboll.studio.libaes.views.ASupportToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_height"
|
||||
android:id="@+id/toolbar"
|
||||
android:gravity="center_vertical"
|
||||
style="@style/DefaultAToolbar"/>
|
||||
<cc.winboll.studio.libaes.views.ASupportToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_height"
|
||||
android:id="@+id/toolbar"
|
||||
android:gravity="center_vertical"
|
||||
style="@style/DefaultAToolbar"/>
|
||||
|
||||
<!-- 主内容区(优化层级,减少冗余RelativeLayout) -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<!-- 首屏核心容器(合并原冗余RelativeLayout,减少层级) -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitymainRelativeLayout1">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/activitymainRelativeLayout1">
|
||||
|
||||
<!-- 1. 背景视图(首屏核心,同步加载,保留原有) -->
|
||||
<cc.winboll.studio.powerbell.views.BackgroundView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/fragmentmainviewBackgroundView1"/>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/ll_backgroundview">
|
||||
|
||||
<!-- 2. 功能控件容器(首屏核心,同步加载,保留原有结构) -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 服务总开关布局 -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentmainviewLinearLayout3"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="10dp">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/bg_frame">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentmainviewLinearLayout3"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<Switch
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewSwitch1"
|
||||
android:padding="10dp"
|
||||
android:layout_weight="1.0"
|
||||
android:textSize="@dimen/text_title_size"/>
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@drawable/bg_frame">
|
||||
|
||||
</LinearLayout>
|
||||
<Switch
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewSwitch1"
|
||||
android:padding="10dp"
|
||||
android:layout_weight="1.0"
|
||||
android:textSize="@dimen/text_title_size"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 电量控制核心布局(SeekBar+图标) -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp">
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp">
|
||||
|
||||
<!-- 耗电提醒布局 -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:id="@+id/fragmentmainviewLinearLayout1">
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/usege"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:id="@+id/fragmentmainviewLinearLayout1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:id="@+id/fragmentmainviewCheckBox2"/>
|
||||
<ImageView
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/usege"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
<cc.winboll.studio.powerbell.views.VerticalSeekBar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewVerticalSeekBar2"
|
||||
android:progressTint="@color/colorUsege"
|
||||
android:progressBackgroundTint="@color/colorUsege"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_margin="10dp"/>
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:id="@+id/fragmentmainviewCheckBox2"/>
|
||||
|
||||
</LinearLayout>
|
||||
<cc.winboll.studio.powerbell.views.VerticalSeekBar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewVerticalSeekBar2"
|
||||
android:progressTint="@color/colorUsege"
|
||||
android:progressBackgroundTint="@color/colorUsege"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
<!-- 耗电提醒数值+图标 -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent">
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="100%"
|
||||
android:textSize="@dimen/text_title_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/fragmentandroidviewTextView3"
|
||||
android:gravity="center_horizontal"/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewImageView2"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="100%"
|
||||
android:textSize="@dimen/text_title_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/fragmentandroidviewTextView3"
|
||||
android:gravity="center_horizontal"/>
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewImageView2"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
<!-- 当前电量数值+图标 -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="100%"
|
||||
android:textSize="@dimen/text_title_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal"
|
||||
android:id="@+id/fragmentandroidviewTextView4"/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewImageView1"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="100%"
|
||||
android:textSize="@dimen/text_title_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal"
|
||||
android:id="@+id/fragmentandroidviewTextView4"/>
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewImageView1"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
<!-- 充电提醒数值+图标 -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent">
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="100%"
|
||||
android:textSize="@dimen/text_title_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/fragmentandroidviewTextView2"
|
||||
android:gravity="center_horizontal"/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewImageView3"
|
||||
android:layout_weight="1.0"/>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="100%"
|
||||
android:textSize="@dimen/text_title_size"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:id="@+id/fragmentandroidviewTextView2"
|
||||
android:gravity="center_horizontal"/>
|
||||
|
||||
</LinearLayout>
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewImageView3"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
<!-- 充电提醒布局 -->
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:id="@+id/fragmentmainviewLinearLayout2">
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/charge"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"/>
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:id="@+id/fragmentmainviewLinearLayout2">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:id="@+id/fragmentmainviewCheckBox1"/>
|
||||
<ImageView
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@drawable/charge"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
<cc.winboll.studio.powerbell.views.VerticalSeekBar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewVerticalSeekBar1"
|
||||
android:progressTint="@color/colorCharge"
|
||||
android:progressBackgroundTint="@color/colorCharge"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_margin="10dp"/>
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="10dp"
|
||||
android:id="@+id/fragmentmainviewCheckBox1"/>
|
||||
|
||||
</LinearLayout>
|
||||
<cc.winboll.studio.powerbell.views.VerticalSeekBar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/fragmentandroidviewVerticalSeekBar1"
|
||||
android:progressTint="@color/colorCharge"
|
||||
android:progressBackgroundTint="@color/colorCharge"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Tips文本 -->
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tips"
|
||||
android:textSize="@dimen/text_content_size"
|
||||
android:id="@+id/fragmentandroidviewTextView1"
|
||||
android:background="@drawable/bg_frame"
|
||||
android:padding="10dp"/>
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tips"
|
||||
android:textSize="@dimen/text_content_size"
|
||||
android:id="@+id/fragmentandroidviewTextView1"
|
||||
android:background="@drawable/bg_frame"
|
||||
android:padding="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 3. 广告视图:关键优化→用ViewStub延迟加载(替代原直接加载的ADsBannerView) -->
|
||||
<!-- 首次启动仅占位(1px),不inflate真实广告视图,减少首次耗时 -->
|
||||
<ViewStub
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/stub_ads_banner"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout="@layout/view_ads_banner"/> <!-- 广告视图独立布局文件 -->
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
<ViewStub
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/stub_ads_banner"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout="@layout/view_ads_banner"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
50
powerbell/src/main/res/layout/activity_mainunittest2.xml
Normal file
50
powerbell/src/main/res/layout/activity_mainunittest2.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FF0C6BBF">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/ll_backgroundview">
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#AF4FDA4E">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Main"
|
||||
android:id="@+id/btn_main_activity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestCropImage"
|
||||
android:id="@+id/btn_test_cropimage"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -13,11 +13,20 @@
|
||||
android:id="@+id/tv_dialog_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="网络后台提示"
|
||||
android:text="网络图片资源下载"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_dialog_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="请输入网络图片地址:"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@android:color/darker_gray"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
@@ -54,15 +63,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_dialog_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="应用正在后台使用网络,是否继续允许?"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@android:color/darker_gray"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -74,7 +74,7 @@
|
||||
android:id="@+id/btn_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="取消"
|
||||
android:text="关闭返回"
|
||||
android:textSize="14sp"
|
||||
android:background="@android:drawable/btn_default_small"
|
||||
android:layout_marginRight="8dp"/>
|
||||
@@ -83,7 +83,7 @@
|
||||
android:id="@+id/btn_confirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="允许"
|
||||
android:text="使用图片"
|
||||
android:textSize="14sp"
|
||||
android:background="@android:drawable/btn_default_small"/>
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/action_unittestactivity"
|
||||
android:title="@string/item_mainunittestactivity"/>
|
||||
|
||||
android:title="MainUnitTestActivity"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_unittest2activity"
|
||||
android:title="MainUnitTest2Activity"/>
|
||||
|
||||
</menu>
|
||||
|
||||
Reference in New Issue
Block a user