From a2a61bbf0b384e12c0583b417223befe1c3f28e8 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Thu, 11 Dec 2025 03:02:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0BitmapCacheUtils=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=85=A8=E5=B1=80=E4=BD=8D=E5=9B=BE=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E3=80=82=E5=8A=A0=E9=80=9F=E4=BD=8D?= =?UTF-8?q?=E5=9B=BE=E5=8A=A0=E8=BD=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../java/cc/winboll/studio/powerbell/App.java | 100 +++++--- .../BackgroundSettingsActivity.java | 230 ++++++++++-------- .../powerbell/utils/BitmapCacheUtils.java | 165 +++++++++++++ .../powerbell/views/BackgroundView.java | 124 +++++----- 5 files changed, 428 insertions(+), 195 deletions(-) create mode 100644 powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java diff --git a/powerbell/build.properties b/powerbell/build.properties index f094d45..a50c893 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Dec 10 20:37:42 HKT 2025 +#Wed Dec 10 18:59:56 GMT 2025 stageCount=13 libraryProject= baseVersion=15.12 publishVersion=15.12.12 -buildCount=0 +buildCount=10 baseBetaVersion=15.12.13 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java index 842142b..9b6900e 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java @@ -2,28 +2,36 @@ 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.model.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 java.io.File; 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"; + 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; + GlobalApplicationReceiver mReceiver; static String szTempDir = ""; @@ -34,25 +42,16 @@ public class App extends GlobalApplication { @Override public void onCreate() { super.onCreate(); - setIsDebugging(BuildConfig.DEBUG); - //setIsDebugging(false); + setIsDebugging(BuildConfig.DEBUG); - // 初始化活动窗口管理 - WinBoLLActivityManager.init(this); + // 初始化活动窗口管理 + WinBoLLActivityManager.init(this); // 初始化 Toast 框架 ToastUtils.init(this); - - // 临时文件夹方案1 - // 获取Pictures文件夹路径(Android 10及以上推荐使用MediaStore,此处为传统方式) + + // 临时文件夹初始化(保持原有逻辑) File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); - // 定义目标文件路径(在Pictures目录下创建"PowerBell"子文件夹及文件) File powerBellDir = new File(picturesDir, "PowerBell"); - - // 临时文件夹方案2 <图片保存失败> - // 获取Pictures文件夹路径(Android 10及以上推荐使用MediaStore,此处为传统方式) - //File powerBellDir = getExternalFilesDir("TempDir"); - - // 先创建文件夹(如果不存在) if (!powerBellDir.exists()) { powerBellDir.mkdirs(); } @@ -61,11 +60,54 @@ public class App extends GlobalApplication { // 设置数据配置存储工具 _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); @@ -84,12 +126,14 @@ public class App extends GlobalApplication { _mAppCacheUtils.clearBatteryHistory(); } - @Override - public void onTerminate() { - super.onTerminate(); - ToastUtils.release(); - } - - + @Override + public void onTerminate() { + super.onTerminate(); + ToastUtils.release(); + // 可选:App 终止时清空 Bitmap 缓存,释放内存 + if (_mBitmapCacheUtils != null) { + _mBitmapCacheUtils.clearAllCache(); + } + } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java index 43d0839..6134bad 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -14,6 +15,7 @@ import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; import android.view.View; +import android.widget.ImageView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; @@ -52,7 +54,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private BackgroundView mBackgroundView; private File mfTakePhoto; volatile boolean isCommitSettings = false; - volatile boolean isPreviewBackgroundChanged = false; + volatile boolean isPreviewBackgroundChanged = false; @Override public Activity getActivity() { @@ -71,49 +73,49 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mBackgroundView = (BackgroundView) findViewById(R.id.background_view); mBgSourceUtils = BackgroundSourceUtils.getInstance(this); - mBgSourceUtils.loadSettings(); + mBgSourceUtils.loadSettings(); mPermissionUtils = PermissionUtils.getInstance(); -// File tempDir = new File(App.getTempDirPath()); -// if (!tempDir.exists()) { -// tempDir.mkdirs(); -// } -// mfTakePhoto = new File(tempDir, "TakePhoto.jpg"); -// -// File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp"); -// if (!selectTempDir.exists()) { -// selectTempDir.mkdirs(); -// LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath()); -// } + File tempDir = new File(App.getTempDirPath()); + if (!tempDir.exists()) { + tempDir.mkdirs(); + } + mfTakePhoto = new File(tempDir, "TakePhoto.jpg"); + + File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp"); + if (!selectTempDir.exists()) { + selectTempDir.mkdirs(); + LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath()); + } initToolbar(); initClickListeners(); if (handleShareIntent()) { - ToastUtils.show("handleShareIntent"); - } else { - mBgSourceUtils.setCurrentSourceToPreview(); - } + ToastUtils.show("handleShareIntent"); + } else { + mBgSourceUtils.setCurrentSourceToPreview(); + } - Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath())); - // 创建预览数据剪裁环境 - mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean()); - doubleRefreshPreview(); + Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath())); + // 创建预览数据剪裁环境 + mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean()); + doubleRefreshPreview(); LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成"); } - @Override - protected void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - doubleRefreshPreview(); - } + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + doubleRefreshPreview(); + } private void initToolbar() { - mToolbar = findViewById(R.id.toolbar); + mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); - mToolbar.setSubtitle(getTag()); + mToolbar.setSubtitle(getTag()); mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -143,10 +145,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this); dlg.show(); LogUtils.d(TAG, "【分享处理】收到分享图片意图"); - return true; + return true; } } - return false; + return false; } boolean isImageType(String lowerMimeType) { @@ -255,8 +257,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mBackgroundView.getWidth(), mBackgroundView.getHeight(), false, - REQUEST_CROP_IMAGE - ); + REQUEST_CROP_IMAGE); } }; @@ -270,8 +271,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg 0, 0, true, - REQUEST_CROP_IMAGE - ); + REQUEST_CROP_IMAGE); } }; @@ -429,8 +429,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mBackgroundView.getWidth(), mBackgroundView.getHeight(), false, - REQUEST_CROP_IMAGE - ); + REQUEST_CROP_IMAGE); LogUtils.d(TAG, "【拍照完成】已启动裁剪"); } @@ -470,50 +469,52 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return null; } - private void handleSelectPictureResult(int resultCode, Intent data) { - if (resultCode != RESULT_OK || data == null) { - handleOperationCancelOrFail(); - return; - } + private void handleSelectPictureResult(int resultCode, Intent data) { + if (resultCode != RESULT_OK || data == null) { + handleOperationCancelOrFail(); + return; + } - Uri selectedImage = data.getData(); - if (selectedImage == null) { - ToastUtils.show("图片Uri为空"); - return; - } - LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString()); + Uri selectedImage = data.getData(); + if (selectedImage == null) { + ToastUtils.show("图片Uri为空"); + return; + } + LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - getContentResolver().takePersistableUriPermission( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + getContentResolver().takePersistableUriPermission( selectedImage, Intent.FLAG_GRANT_READ_URI_PERMISSION - ); - LogUtils.d(TAG, "【选图权限】已添加持久化权限"); - } + ); + LogUtils.d(TAG, "【选图权限】已添加持久化权限"); + } - LogUtils.d(TAG, "【选图同步】路径绑定完成"); - // 选图后启动固定比例裁剪(调用工具类) - putUriFileToPreviewSource(selectedImage); - ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, + LogUtils.d(TAG, "【选图同步】路径绑定完成"); + // 选图后启动固定比例裁剪(调用工具类) + putUriFileToPreviewSource(selectedImage); + ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this, mBgSourceUtils.getPreviewBackgroundBean(), mBackgroundView.getWidth(), mBackgroundView.getHeight(), false, - REQUEST_CROP_IMAGE - ); - } + REQUEST_CROP_IMAGE); + } - boolean putUriFileToPreviewSource(Uri srcUriFile) { - File srcFile = new File(UriUtil.getFilePathFromUri(this, srcUriFile)); - return putUriFileToPreviewSource(srcFile); - } + boolean putUriFileToPreviewSource(Uri srcUriFile) { + File srcFile = new File(UriUtil.getFilePathFromUri(this, srcUriFile)); + return putUriFileToPreviewSource(srcFile); + } - boolean putUriFileToPreviewSource(File srcFile) { - mBgSourceUtils.loadSettings(); - File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); - return FileUtils.copyFile(srcFile, dstFile); - } + boolean putUriFileToPreviewSource(File srcFile) { + mBgSourceUtils.loadSettings(); + File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()); + return FileUtils.copyFile(srcFile, dstFile); + } + /** + * 核心修改:裁剪成功后调用双重刷新,确保最新图片加载 + */ private void handleCropImageResult(int requestCode, int resultCode, Intent data) { LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果"); File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath()); @@ -523,9 +524,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg boolean isCropSuccess = (resultCode == RESULT_OK) && isFileExist && isFileReadable && fileSize > 100; if (isCropSuccess) { - isPreviewBackgroundChanged = true; + isPreviewBackgroundChanged = true; LogUtils.d(TAG, "handleCropImageResult: 裁剪成功"); - BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); + final BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); previewBean.setIsUseBackgroundFile(true); previewBean.setIsUseBackgroundScaledCompressFile(true); mBgSourceUtils.saveSettings(); @@ -546,15 +547,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } + // 关键修改:延迟300ms调用双重刷新,确保文件写入完成 new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { - if (mBackgroundView != null && !isFinishing()) { - mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); - LogUtils.d(TAG, "handleCropImageResult: 裁剪图片加载完成"); + if (!isFinishing()) { + doubleRefreshPreview(); + LogUtils.d(TAG, "handleCropImageResult: 触发双重刷新"); } } - }, 50); + }, 300); } else { handleOperationCancelOrFail(); } @@ -697,10 +699,42 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return cropBitmap; } + /** + * 核心修改:添加缓存清理 + 控件 Bitmap 清空逻辑 + */ private void doubleRefreshPreview() { LogUtils.d(TAG, "【双重刷新】开始"); + // 1. 清空缓存工具类的旧数据 + if (mBgSourceUtils != null) { + BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean(); + if (previewBean != null && previewBean.isUseBackgroundFile()) { + String bgPath = previewBean.isUseBackgroundScaledCompressFile() + ? previewBean.getBackgroundScaledCompressFilePath() + : previewBean.getBackgroundFilePath(); + // 强制清除缓存 + App._mBitmapCacheUtils.removeCachedBitmap(bgPath); + LogUtils.d(TAG, "【双重刷新】已清空工具类缓存:" + bgPath); + } + } + + // 2. 清空 BackgroundView 自身的 Bitmap 引用 +// if (mBackgroundView != null) { +// // 清空 ImageView 持有的 Bitmap +// if (mBackgroundView instanceof BackgroundView) { +// ((BackgroundView) mBackgroundView).setImageBitmap(null); +// } +// // 清空背景 Drawable 引用 +// Drawable drawable = mBackgroundView.getBackground(); +// if (drawable != null) { +// drawable.setCallback(null); +// mBackgroundView.setBackground(null); +// } +// LogUtils.d(TAG, "【双重刷新】已清空控件 Bitmap 引用"); +// } + + // 3. 重新加载最新数据 if (mBackgroundView != null && !isFinishing()) { - mBgSourceUtils.loadSettings(); + mBgSourceUtils.loadSettings(); mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean()); LogUtils.d(TAG, "【双重刷新】第一重完成"); } else { @@ -708,6 +742,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg return; } + // 4. 延长延迟到200ms,确保文件完全加载 new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { @@ -717,7 +752,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg LogUtils.d(TAG, "【双重刷新】第二重完成"); } } - }, 50); + }, 200); } @@ -725,37 +760,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mBgSourceUtils.setCurrentSourceToPreview(); LogUtils.d(TAG, "【操作回调】取消或失败"); ToastUtils.show("【操作回调】取消或失败"); - doubleRefreshPreview(); + doubleRefreshPreview(); } - - /** - * 启动系统裁剪工具 - * @param activity 上下文 - * @param srcFile 裁剪原图 - * @param aspectX 裁剪宽比例(自由裁剪传0) - * @param aspectY 裁剪高比例(自由裁剪传0) - * @param isFreeCrop 是否自由裁剪 - * @param requestCode 裁剪请求码 - */ - /** - * 启动系统裁剪工具 - * @param activity 上下文 - * @param srcFile 裁剪原图 - * @param aspectX 裁剪宽比例(自由裁剪传0) - * @param aspectY 裁剪高比例(自由裁剪传0) - * @param isFreeCrop 是否自由裁剪 - * @param requestCode 裁剪请求码 - */ - - /** * 获取FileProvider Uri(复用方法,避免重复代码) */ public Uri getFileProviderUri(File file) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider"; + String FILE_PROVIDER_AUTHORITY = getPackageName() + ".fileprovider"; return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, file); } else { @@ -779,9 +793,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg if (isCommitSettings) { super.finish(); } else { - // 如果预览背景改变过就提示是否更换背景 - if (isPreviewBackgroundChanged) { - YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){ + // 如果预览背景改变过就提示是否更换背景 + if (isPreviewBackgroundChanged) { + YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener() { @Override public void onYes() { mBgSourceUtils.commitPreviewSourceToCurrent(); @@ -795,11 +809,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg finish(); } }); - } else { - // 如果预览背景未改变就直接退出 - isCommitSettings = true; - finish(); - } + } else { + // 如果预览背景未改变就直接退出 + isCommitSettings = true; + finish(); + } } } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java new file mode 100644 index 0000000..aee1d81 --- /dev/null +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java @@ -0,0 +1,165 @@ +package cc.winboll.studio.powerbell.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.text.TextUtils; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import cc.winboll.studio.libappbase.LogUtils; + +/** + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/12/11 01:57 + * @Describe 单例 Bitmap 缓存工具类(Java 7 兼容) + * 功能:内存缓存 Bitmap,支持路径关联缓存、全局获取、缓存清空 + * 特点:1. 单例模式 2. 压缩加载避免OOM 3. 路径- Bitmap 映射 4. 线程安全 + */ +public class BitmapCacheUtils { + private static final String TAG = "BitmapCacheUtils"; + // 最大图片尺寸(适配1080P屏幕,可根据需求调整) + private static final int MAX_WIDTH = 1080; + private static final int MAX_HEIGHT = 1920; + + // 单例实例(volatile 保证多线程可见性) + private static volatile BitmapCacheUtils sInstance; + // 路径-Bitmap 缓存容器(内存缓存) + private final Map mBitmapCacheMap; + + // 私有构造器(单例模式) + private BitmapCacheUtils() { + mBitmapCacheMap = new HashMap<>(); + } + + /** + * 获取单例实例(双重校验锁,线程安全) + */ + public static BitmapCacheUtils getInstance() { + if (sInstance == null) { + synchronized (BitmapCacheUtils.class) { + if (sInstance == null) { + sInstance = new BitmapCacheUtils(); + } + } + } + return sInstance; + } + + /** + * 核心接口:根据图片路径缓存 Bitmap 到内存 + * @param imagePath 图片绝对路径 + * @return 缓存成功的 Bitmap / null(路径无效/文件不存在) + */ + public Bitmap cacheBitmap(String imagePath) { + if (TextUtils.isEmpty(imagePath)) { + LogUtils.e(TAG, "cacheBitmap: 图片路径为空"); + return null; + } + + File imageFile = new File(imagePath); + if (!imageFile.exists() || !imageFile.isFile()) { + LogUtils.e(TAG, "cacheBitmap: 图片文件不存在 - " + imagePath); + return null; + } + + // 已缓存则直接返回,避免重复加载 + if (mBitmapCacheMap.containsKey(imagePath)) { + LogUtils.d(TAG, "cacheBitmap: 图片已缓存,直接返回 - " + imagePath); + return mBitmapCacheMap.get(imagePath); + } + + // 压缩加载 Bitmap(避免OOM) + Bitmap bitmap = decodeCompressedBitmap(imagePath); + if (bitmap != null) { + // 存入缓存容器 + mBitmapCacheMap.put(imagePath, bitmap); + LogUtils.d(TAG, "cacheBitmap: 图片缓存成功 - " + imagePath); + } else { + LogUtils.e(TAG, "cacheBitmap: 图片解码失败 - " + imagePath); + } + return bitmap; + } + + /** + * 核心接口:根据路径获取缓存的 Bitmap + * @param imagePath 图片绝对路径 + * @return 缓存的 Bitmap / null + */ + public Bitmap getCachedBitmap(String imagePath) { + if (TextUtils.isEmpty(imagePath)) { + return null; + } + return mBitmapCacheMap.get(imagePath); + } + + /** + * 清空所有 Bitmap 缓存(释放内存) + */ + public void clearAllCache() { + for (Bitmap bitmap : mBitmapCacheMap.values()) { + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); // 主动回收 Bitmap + } + } + mBitmapCacheMap.clear(); + LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空"); + } + + /** + * 移除指定路径的 Bitmap 缓存 + * @param imagePath 图片绝对路径 + */ + public void removeCachedBitmap(String imagePath) { + if (TextUtils.isEmpty(imagePath)) { + return; + } + Bitmap bitmap = mBitmapCacheMap.remove(imagePath); + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + LogUtils.d(TAG, "removeCachedBitmap: 移除并回收缓存 - " + imagePath); + } + } + + /** + * 压缩解码 Bitmap(按最大尺寸缩放,避免OOM) + */ + private Bitmap decodeCompressedBitmap(String imagePath) { + BitmapFactory.Options options = new BitmapFactory.Options(); + // 第一步:只获取图片尺寸,不加载像素 + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(imagePath, options); + + // 计算缩放比例 + int sampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT); + + // 第二步:加载压缩后的 Bitmap + options.inJustDecodeBounds = false; + options.inSampleSize = sampleSize; + options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存(比ARGB_8888少一半内存) + options.inPurgeable = true; + options.inInputShareable = true; + + return BitmapFactory.decodeFile(imagePath, options); + } + + /** + * 计算 Bitmap 缩放比例 + */ + private int calculateInSampleSize(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; + } + } + return inSampleSize; + } +} + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java index 4826514..27d6e8e 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java @@ -12,6 +12,8 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.RelativeLayout; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.libappbase.ToastUtils; +import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.model.BackgroundBean; import java.io.File; @@ -22,6 +24,8 @@ import java.io.File; public class BackgroundView extends RelativeLayout { public static final String TAG = "BackgroundView"; + // 新增:记录当前已缓存的图片路径 + private String mCurrentCachedPath = ""; private Context mContext; private LinearLayout mLlContainer; // 主容器LinearLayout @@ -55,8 +59,6 @@ public class BackgroundView extends RelativeLayout { LogUtils.d(TAG, "=== initView 启动 ==="); // 1. 配置当前控件:全屏+透明 setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - //setBackgroundColor(0x00000000); - //setBackground(new ColorDrawable(0x00000000)); // 2. 初始化主容器LinearLayout initLinearLayout(); @@ -64,7 +66,7 @@ public class BackgroundView extends RelativeLayout { // 3. 初始化ImageView initImageView(); - // 初始设置透明背景 + // 初始设置透明背景 setDefaultTransparentBackground(); LogUtils.d(TAG, "=== initView 完成 ==="); @@ -101,22 +103,21 @@ public class BackgroundView extends RelativeLayout { LogUtils.d(TAG, "=== initImageView 完成 ==="); } - public void loadBackgroundBean(BackgroundBean bean) { - if (!bean.isUseBackgroundFile()) { - setDefaultTransparentBackground(); - return; - } - if (bean.isUseBackgroundScaledCompressFile()) { - loadImage(bean.getBackgroundScaledCompressFilePath()); - } else { - - loadImage(bean.getBackgroundFilePath()); - } - } + public void loadBackgroundBean(BackgroundBean bean) { + if (!bean.isUseBackgroundFile()) { + setDefaultTransparentBackground(); + return; + } + String targetPath = bean.isUseBackgroundScaledCompressFile() + ? bean.getBackgroundScaledCompressFilePath() + : bean.getBackgroundFilePath(); + // 调用带路径判断的loadImage方法 + loadImage(targetPath); + } // ====================================== 对外方法 ====================================== /** - * 加载图片(保持原图比例,在LinearLayout中居中平铺) + * 改造后:添加路径判断,路径更新时同步更新缓存 * @param imagePath 图片绝对路径 */ public void loadImage(String imagePath) { @@ -133,24 +134,50 @@ public class BackgroundView extends RelativeLayout { return; } - mIvBackground.setVisibility(View.GONE); + mIvBackground.setVisibility(View.GONE); - // 计算原图比例 + // ======================== 新增:路径判断逻辑 ======================== + // 1. 路径未变化:直接使用缓存 + if (imagePath.equals(mCurrentCachedPath)) { + //ToastUtils.show("isReload == false"); + Bitmap cachedBitmap = App._mBitmapCacheUtils.getCachedBitmap(imagePath); + 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; + } + } + + // 2. 路径已更新:移除旧缓存,加载新图片并更新缓存 + if (!TextUtils.isEmpty(mCurrentCachedPath)) { + App._mBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath); + LogUtils.d(TAG, "loadImage: 路径已更新,移除旧缓存 - " + mCurrentCachedPath); + } + // ======================== 路径判断逻辑结束 ======================== + + // 无缓存/路径更新:走原有逻辑加载图片 if (!calculateImageAspectRatio(imageFile)) { setDefaultTransparentBackground(); return; } - // 压缩加载Bitmap Bitmap bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920); if (bitmap == null) { setDefaultTransparentBackground(); return; } - // 设置图片 + // 缓存新图片,并更新当前缓存路径记录 + App._mBitmapCacheUtils.cacheBitmap(imagePath); + mCurrentCachedPath = imagePath; + LogUtils.d(TAG, "loadImage: 加载新图片并更新缓存 - " + imagePath); + mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap)); - adjustImageViewSize(); // 调整尺寸 + adjustImageViewSize(); + mIvBackground.setVisibility(View.VISIBLE); LogUtils.d(TAG, "=== loadImage 完成 ==="); } @@ -198,57 +225,39 @@ public class BackgroundView extends RelativeLayout { } } - /** - * 调整ImageView尺寸(保持原图比例,在LinearLayout中居中平铺) - */ private void adjustImageViewSize() { - //LogUtils.d(TAG, "=== adjustImageViewSize 启动 ==="); if (mLlContainer == null || mIvBackground == null) { - //LogUtils.e(TAG, "控件为空"); return; } - // 获取LinearLayout尺寸 int llWidth = mLlContainer.getWidth(); int llHeight = mLlContainer.getHeight(); -// if (llWidth == 0 || llHeight == 0) { -// postDelayed(new Runnable() { -// @Override -// public void run() { -// adjustImageViewSize(); -// } -// }, 100); -// return; -// } - if (llWidth != 0 && llHeight != 0) { - // 计算ImageView尺寸(保持比例,不超出LinearLayout) - 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); - } + 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); - - //LogUtils.d(TAG, "ImageView尺寸:" + ivWidth + "x" + ivHeight); - //LogUtils.d(TAG, "=== adjustImageViewSize 完成 ==="); - } + 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); mIvBackground.setBackgroundColor(0x00000000); mImageAspectRatio = 1.0f; + // 清空缓存路径记录 + mCurrentCachedPath = ""; } // ====================================== 重写方法 ====================================== @@ -258,3 +267,4 @@ public class BackgroundView extends RelativeLayout { adjustImageViewSize(); // 尺寸变化时重新调整 } } +