添加BitmapCacheUtils应用全局位图缓存工具类。加速位图加载。
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#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
|
stageCount=13
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.12
|
baseVersion=15.12
|
||||||
publishVersion=15.12.12
|
publishVersion=15.12.12
|
||||||
buildCount=0
|
buildCount=10
|
||||||
baseBetaVersion=15.12.13
|
baseBetaVersion=15.12.13
|
||||||
|
|||||||
@@ -2,16 +2,21 @@ package cc.winboll.studio.powerbell;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
import cc.winboll.studio.libappbase.ToastUtils;
|
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.receivers.GlobalApplicationReceiver;
|
||||||
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
|
import cc.winboll.studio.powerbell.utils.AppCacheUtils;
|
||||||
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
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;
|
import java.io.File;
|
||||||
|
|
||||||
public class App extends GlobalApplication {
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
public static final String TAG = "App";
|
public static final String TAG = "App";
|
||||||
|
|
||||||
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
||||||
@@ -24,6 +29,9 @@ public class App extends GlobalApplication {
|
|||||||
// 数据配置存储工具
|
// 数据配置存储工具
|
||||||
static AppConfigUtils _mAppConfigUtils;
|
static AppConfigUtils _mAppConfigUtils;
|
||||||
static AppCacheUtils _mAppCacheUtils;
|
static AppCacheUtils _mAppCacheUtils;
|
||||||
|
// 新增:全局 Bitmap 缓存工具(常驻内存)
|
||||||
|
public static BitmapCacheUtils _mBitmapCacheUtils;
|
||||||
|
|
||||||
GlobalApplicationReceiver mReceiver;
|
GlobalApplicationReceiver mReceiver;
|
||||||
static String szTempDir = "";
|
static String szTempDir = "";
|
||||||
|
|
||||||
@@ -35,24 +43,15 @@ public class App extends GlobalApplication {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
setIsDebugging(BuildConfig.DEBUG);
|
setIsDebugging(BuildConfig.DEBUG);
|
||||||
//setIsDebugging(false);
|
|
||||||
|
|
||||||
// 初始化活动窗口管理
|
// 初始化活动窗口管理
|
||||||
WinBoLLActivityManager.init(this);
|
WinBoLLActivityManager.init(this);
|
||||||
// 初始化 Toast 框架
|
// 初始化 Toast 框架
|
||||||
ToastUtils.init(this);
|
ToastUtils.init(this);
|
||||||
|
|
||||||
// 临时文件夹方案1
|
// 临时文件夹初始化(保持原有逻辑)
|
||||||
// 获取Pictures文件夹路径(Android 10及以上推荐使用MediaStore,此处为传统方式)
|
|
||||||
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||||
// 定义目标文件路径(在Pictures目录下创建"PowerBell"子文件夹及文件)
|
|
||||||
File powerBellDir = new File(picturesDir, "PowerBell");
|
File powerBellDir = new File(picturesDir, "PowerBell");
|
||||||
|
|
||||||
// 临时文件夹方案2 <图片保存失败>
|
|
||||||
// 获取Pictures文件夹路径(Android 10及以上推荐使用MediaStore,此处为传统方式)
|
|
||||||
//File powerBellDir = getExternalFilesDir("TempDir");
|
|
||||||
|
|
||||||
// 先创建文件夹(如果不存在)
|
|
||||||
if (!powerBellDir.exists()) {
|
if (!powerBellDir.exists()) {
|
||||||
powerBellDir.mkdirs();
|
powerBellDir.mkdirs();
|
||||||
}
|
}
|
||||||
@@ -61,11 +60,54 @@ public class App extends GlobalApplication {
|
|||||||
// 设置数据配置存储工具
|
// 设置数据配置存储工具
|
||||||
_mAppConfigUtils = getAppConfigUtils(this);
|
_mAppConfigUtils = getAppConfigUtils(this);
|
||||||
_mAppCacheUtils = getAppCacheUtils(this);
|
_mAppCacheUtils = getAppCacheUtils(this);
|
||||||
|
// 初始化全局 Bitmap 缓存工具(关键:App 启动时初始化,常驻内存)
|
||||||
|
_mBitmapCacheUtils = BitmapCacheUtils.getInstance();
|
||||||
|
|
||||||
mReceiver = new GlobalApplicationReceiver(this);
|
mReceiver = new GlobalApplicationReceiver(this);
|
||||||
mReceiver.registerAction();
|
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) {
|
public static AppConfigUtils getAppConfigUtils(Context context) {
|
||||||
if (_mAppConfigUtils == null) {
|
if (_mAppConfigUtils == null) {
|
||||||
_mAppConfigUtils = AppConfigUtils.getInstance(context);
|
_mAppConfigUtils = AppConfigUtils.getInstance(context);
|
||||||
@@ -88,8 +130,10 @@ public class App extends GlobalApplication {
|
|||||||
public void onTerminate() {
|
public void onTerminate() {
|
||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
ToastUtils.release();
|
ToastUtils.release();
|
||||||
}
|
// 可选:App 终止时清空 Bitmap 缓存,释放内存
|
||||||
|
if (_mBitmapCacheUtils != null) {
|
||||||
|
_mBitmapCacheUtils.clearAllCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Bitmap.CompressFormat;
|
import android.graphics.Bitmap.CompressFormat;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -14,6 +15,7 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
@@ -74,17 +76,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
mBgSourceUtils.loadSettings();
|
mBgSourceUtils.loadSettings();
|
||||||
mPermissionUtils = PermissionUtils.getInstance();
|
mPermissionUtils = PermissionUtils.getInstance();
|
||||||
|
|
||||||
// File tempDir = new File(App.getTempDirPath());
|
File tempDir = new File(App.getTempDirPath());
|
||||||
// if (!tempDir.exists()) {
|
if (!tempDir.exists()) {
|
||||||
// tempDir.mkdirs();
|
tempDir.mkdirs();
|
||||||
// }
|
}
|
||||||
// mfTakePhoto = new File(tempDir, "TakePhoto.jpg");
|
mfTakePhoto = new File(tempDir, "TakePhoto.jpg");
|
||||||
//
|
|
||||||
// File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
|
File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
|
||||||
// if (!selectTempDir.exists()) {
|
if (!selectTempDir.exists()) {
|
||||||
// selectTempDir.mkdirs();
|
selectTempDir.mkdirs();
|
||||||
// LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath());
|
LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath());
|
||||||
// }
|
}
|
||||||
|
|
||||||
initToolbar();
|
initToolbar();
|
||||||
initClickListeners();
|
initClickListeners();
|
||||||
@@ -255,8 +257,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
mBackgroundView.getWidth(),
|
mBackgroundView.getWidth(),
|
||||||
mBackgroundView.getHeight(),
|
mBackgroundView.getHeight(),
|
||||||
false,
|
false,
|
||||||
REQUEST_CROP_IMAGE
|
REQUEST_CROP_IMAGE);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -270,8 +271,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
REQUEST_CROP_IMAGE
|
REQUEST_CROP_IMAGE);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -429,8 +429,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
mBackgroundView.getWidth(),
|
mBackgroundView.getWidth(),
|
||||||
mBackgroundView.getHeight(),
|
mBackgroundView.getHeight(),
|
||||||
false,
|
false,
|
||||||
REQUEST_CROP_IMAGE
|
REQUEST_CROP_IMAGE);
|
||||||
);
|
|
||||||
LogUtils.d(TAG, "【拍照完成】已启动裁剪");
|
LogUtils.d(TAG, "【拍照完成】已启动裁剪");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,8 +498,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
mBackgroundView.getWidth(),
|
mBackgroundView.getWidth(),
|
||||||
mBackgroundView.getHeight(),
|
mBackgroundView.getHeight(),
|
||||||
false,
|
false,
|
||||||
REQUEST_CROP_IMAGE
|
REQUEST_CROP_IMAGE);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean putUriFileToPreviewSource(Uri srcUriFile) {
|
boolean putUriFileToPreviewSource(Uri srcUriFile) {
|
||||||
@@ -514,6 +512,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
return FileUtils.copyFile(srcFile, dstFile);
|
return FileUtils.copyFile(srcFile, dstFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心修改:裁剪成功后调用双重刷新,确保最新图片加载
|
||||||
|
*/
|
||||||
private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
|
private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
|
||||||
LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果");
|
LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果");
|
||||||
File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
|
File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
|
||||||
@@ -525,7 +526,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
if (isCropSuccess) {
|
if (isCropSuccess) {
|
||||||
isPreviewBackgroundChanged = true;
|
isPreviewBackgroundChanged = true;
|
||||||
LogUtils.d(TAG, "handleCropImageResult: 裁剪成功");
|
LogUtils.d(TAG, "handleCropImageResult: 裁剪成功");
|
||||||
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
|
final BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
|
||||||
previewBean.setIsUseBackgroundFile(true);
|
previewBean.setIsUseBackgroundFile(true);
|
||||||
previewBean.setIsUseBackgroundScaledCompressFile(true);
|
previewBean.setIsUseBackgroundScaledCompressFile(true);
|
||||||
mBgSourceUtils.saveSettings();
|
mBgSourceUtils.saveSettings();
|
||||||
@@ -546,15 +547,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关键修改:延迟300ms调用双重刷新,确保文件写入完成
|
||||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (mBackgroundView != null && !isFinishing()) {
|
if (!isFinishing()) {
|
||||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
doubleRefreshPreview();
|
||||||
LogUtils.d(TAG, "handleCropImageResult: 裁剪图片加载完成");
|
LogUtils.d(TAG, "handleCropImageResult: 触发双重刷新");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 300);
|
||||||
} else {
|
} else {
|
||||||
handleOperationCancelOrFail();
|
handleOperationCancelOrFail();
|
||||||
}
|
}
|
||||||
@@ -697,8 +699,40 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
return cropBitmap;
|
return cropBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心修改:添加缓存清理 + 控件 Bitmap 清空逻辑
|
||||||
|
*/
|
||||||
private void doubleRefreshPreview() {
|
private void doubleRefreshPreview() {
|
||||||
LogUtils.d(TAG, "【双重刷新】开始");
|
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()) {
|
if (mBackgroundView != null && !isFinishing()) {
|
||||||
mBgSourceUtils.loadSettings();
|
mBgSourceUtils.loadSettings();
|
||||||
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
|
||||||
@@ -708,6 +742,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. 延长延迟到200ms,确保文件完全加载
|
||||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -717,7 +752,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
LogUtils.d(TAG, "【双重刷新】第二重完成");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -728,27 +763,6 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
|
|||||||
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(复用方法,避免重复代码)
|
* 获取FileProvider Uri(复用方法,避免重复代码)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @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<String, Bitmap> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -12,6 +12,8 @@ import android.widget.ImageView.ScaleType;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import cc.winboll.studio.libappbase.LogUtils;
|
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 cc.winboll.studio.powerbell.model.BackgroundBean;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -22,6 +24,8 @@ import java.io.File;
|
|||||||
public class BackgroundView extends RelativeLayout {
|
public class BackgroundView extends RelativeLayout {
|
||||||
|
|
||||||
public static final String TAG = "BackgroundView";
|
public static final String TAG = "BackgroundView";
|
||||||
|
// 新增:记录当前已缓存的图片路径
|
||||||
|
private String mCurrentCachedPath = "";
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private LinearLayout mLlContainer; // 主容器LinearLayout
|
private LinearLayout mLlContainer; // 主容器LinearLayout
|
||||||
@@ -55,8 +59,6 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
LogUtils.d(TAG, "=== initView 启动 ===");
|
LogUtils.d(TAG, "=== initView 启动 ===");
|
||||||
// 1. 配置当前控件:全屏+透明
|
// 1. 配置当前控件:全屏+透明
|
||||||
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||||||
//setBackgroundColor(0x00000000);
|
|
||||||
//setBackground(new ColorDrawable(0x00000000));
|
|
||||||
|
|
||||||
// 2. 初始化主容器LinearLayout
|
// 2. 初始化主容器LinearLayout
|
||||||
initLinearLayout();
|
initLinearLayout();
|
||||||
@@ -106,17 +108,16 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
setDefaultTransparentBackground();
|
setDefaultTransparentBackground();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (bean.isUseBackgroundScaledCompressFile()) {
|
String targetPath = bean.isUseBackgroundScaledCompressFile()
|
||||||
loadImage(bean.getBackgroundScaledCompressFilePath());
|
? bean.getBackgroundScaledCompressFilePath()
|
||||||
} else {
|
: bean.getBackgroundFilePath();
|
||||||
|
// 调用带路径判断的loadImage方法
|
||||||
loadImage(bean.getBackgroundFilePath());
|
loadImage(targetPath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================== 对外方法 ======================================
|
// ====================================== 对外方法 ======================================
|
||||||
/**
|
/**
|
||||||
* 加载图片(保持原图比例,在LinearLayout中居中平铺)
|
* 改造后:添加路径判断,路径更新时同步更新缓存
|
||||||
* @param imagePath 图片绝对路径
|
* @param imagePath 图片绝对路径
|
||||||
*/
|
*/
|
||||||
public void loadImage(String imagePath) {
|
public void loadImage(String imagePath) {
|
||||||
@@ -135,22 +136,48 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
|
|
||||||
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)) {
|
if (!calculateImageAspectRatio(imageFile)) {
|
||||||
setDefaultTransparentBackground();
|
setDefaultTransparentBackground();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 压缩加载Bitmap
|
|
||||||
Bitmap bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920);
|
Bitmap bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920);
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
setDefaultTransparentBackground();
|
setDefaultTransparentBackground();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置图片
|
// 缓存新图片,并更新当前缓存路径记录
|
||||||
|
App._mBitmapCacheUtils.cacheBitmap(imagePath);
|
||||||
|
mCurrentCachedPath = imagePath;
|
||||||
|
LogUtils.d(TAG, "loadImage: 加载新图片并更新缓存 - " + imagePath);
|
||||||
|
|
||||||
mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap));
|
mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap));
|
||||||
adjustImageViewSize(); // 调整尺寸
|
adjustImageViewSize();
|
||||||
|
mIvBackground.setVisibility(View.VISIBLE);
|
||||||
LogUtils.d(TAG, "=== loadImage 完成 ===");
|
LogUtils.d(TAG, "=== loadImage 完成 ===");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,31 +225,15 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 调整ImageView尺寸(保持原图比例,在LinearLayout中居中平铺)
|
|
||||||
*/
|
|
||||||
private void adjustImageViewSize() {
|
private void adjustImageViewSize() {
|
||||||
//LogUtils.d(TAG, "=== adjustImageViewSize 启动 ===");
|
|
||||||
if (mLlContainer == null || mIvBackground == null) {
|
if (mLlContainer == null || mIvBackground == null) {
|
||||||
//LogUtils.e(TAG, "控件为空");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取LinearLayout尺寸
|
|
||||||
int llWidth = mLlContainer.getWidth();
|
int llWidth = mLlContainer.getWidth();
|
||||||
int llHeight = mLlContainer.getHeight();
|
int llHeight = mLlContainer.getHeight();
|
||||||
// if (llWidth == 0 || llHeight == 0) {
|
|
||||||
// postDelayed(new Runnable() {
|
|
||||||
// @Override
|
|
||||||
// public void run() {
|
|
||||||
// adjustImageViewSize();
|
|
||||||
// }
|
|
||||||
// }, 100);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (llWidth != 0 && llHeight != 0) {
|
if (llWidth != 0 && llHeight != 0) {
|
||||||
// 计算ImageView尺寸(保持比例,不超出LinearLayout)
|
|
||||||
int ivWidth, ivHeight;
|
int ivWidth, ivHeight;
|
||||||
if (mImageAspectRatio >= 1.0f) {
|
if (mImageAspectRatio >= 1.0f) {
|
||||||
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
|
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
|
||||||
@@ -232,16 +243,12 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
ivWidth = (int) (ivHeight * mImageAspectRatio);
|
ivWidth = (int) (ivHeight * mImageAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用尺寸
|
|
||||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
|
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
|
||||||
params.width = ivWidth;
|
params.width = ivWidth;
|
||||||
params.height = ivHeight;
|
params.height = ivHeight;
|
||||||
mIvBackground.setLayoutParams(params);
|
mIvBackground.setLayoutParams(params);
|
||||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 确保居中平铺
|
mIvBackground.setScaleType(ScaleType.FIT_CENTER);
|
||||||
mIvBackground.setVisibility(View.VISIBLE);
|
mIvBackground.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
//LogUtils.d(TAG, "ImageView尺寸:" + ivWidth + "x" + ivHeight);
|
|
||||||
//LogUtils.d(TAG, "=== adjustImageViewSize 完成 ===");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +256,8 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
mIvBackground.setImageBitmap(null);
|
mIvBackground.setImageBitmap(null);
|
||||||
mIvBackground.setBackgroundColor(0x00000000);
|
mIvBackground.setBackgroundColor(0x00000000);
|
||||||
mImageAspectRatio = 1.0f;
|
mImageAspectRatio = 1.0f;
|
||||||
|
// 清空缓存路径记录
|
||||||
|
mCurrentCachedPath = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================================== 重写方法 ======================================
|
// ====================================== 重写方法 ======================================
|
||||||
@@ -258,3 +267,4 @@ public class BackgroundView extends RelativeLayout {
|
|||||||
adjustImageViewSize(); // 尺寸变化时重新调整
|
adjustImageViewSize(); // 尺寸变化时重新调整
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user