Compare commits
2 Commits
powerbell-
...
powerbell-
| Author | SHA1 | Date | |
|---|---|---|---|
| c38b392e39 | |||
| 0f736c2007 |
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Dec 22 11:21:24 HKT 2025
|
||||
stageCount=23
|
||||
#Mon Dec 22 13:00:54 HKT 2025
|
||||
stageCount=24
|
||||
libraryProject=
|
||||
baseVersion=15.14
|
||||
publishVersion=15.14.22
|
||||
publishVersion=15.14.23
|
||||
buildCount=0
|
||||
baseBetaVersion=15.14.23
|
||||
baseBetaVersion=15.14.24
|
||||
|
||||
@@ -16,17 +16,16 @@ 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. 硬引用唯一缓存(极致强制保持,任何情况不自动回收) 3. 路径-Bitmap 映射 4. 线程安全
|
||||
* 5. SP 持久化最后缓存路径 6. 构造时预加载 7. 引用计数防误回收 8. 高压缩比减少OOM风险
|
||||
* 核心策略:无论内存如何紧张,强制保持已缓存的Bitmap,通过高压缩比降低单张Bitmap内存占用,永不自动清理
|
||||
* 5. SP 持久化最后缓存路径 6. 构造时预加载 7. 引用计数防误回收 8. 无图片压缩,保留原始品质
|
||||
* 核心策略:无论内存如何紧张,强制保持已缓存的Bitmap,保留图片原始品质,永不自动清理
|
||||
*/
|
||||
public class BitmapCacheUtils {
|
||||
public static final String TAG = "BitmapCacheUtils";
|
||||
// 最大图片尺寸(降低至720P,进一步减少内存占用,强制缓存核心策略)
|
||||
private static final int MAX_WIDTH = 720;
|
||||
private static final int MAX_HEIGHT = 1280;
|
||||
// 移除最大图片尺寸限制(不压缩图片)
|
||||
// 若需限制尺寸而非压缩品质,可保留此常量用于校验,不参与采样率计算
|
||||
|
||||
// SP 相关常量
|
||||
private static final String SP_NAME = "BitmapCacheSP";
|
||||
@@ -68,7 +67,7 @@ public class BitmapCacheUtils {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
// ====================================== 新增核心方法:解决App类调用问题 ======================================
|
||||
// ====================================== 保留核心监控方法:保证与App类兼容 ======================================
|
||||
/**
|
||||
* 获取当前缓存的Bitmap数量(App类调用专用)
|
||||
* @return 缓存的Bitmap数量
|
||||
@@ -106,7 +105,7 @@ public class BitmapCacheUtils {
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// ====================================== 核心接口:缓存操作 ======================================
|
||||
// ====================================== 核心接口:缓存操作(无压缩) ======================================
|
||||
/**
|
||||
* 补充接口:直接缓存已解码的Bitmap(适配BackgroundView改进需求)
|
||||
* @param imagePath 图片绝对路径
|
||||
@@ -125,7 +124,7 @@ public class BitmapCacheUtils {
|
||||
mRefCountMap.putIfAbsent(imagePath, 1);
|
||||
// 持久化当前路径到 SP
|
||||
saveLastCachePathToSp(imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 直接缓存已解码Bitmap成功(极致强制保持) - " + imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 直接缓存已解码Bitmap成功(极致强制保持,无压缩) - " + imagePath);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@@ -157,8 +156,8 @@ public class BitmapCacheUtils {
|
||||
return hardCacheBitmap;
|
||||
}
|
||||
|
||||
// 高压缩比加载 Bitmap(强制缓存核心:通过降低分辨率减少单张Bitmap内存占用)
|
||||
Bitmap bitmap = decodeCompressedBitmap(imagePath);
|
||||
// 无压缩解码 Bitmap(保留原始品质)
|
||||
Bitmap bitmap = decodeOriginalBitmap(imagePath);
|
||||
if (bitmap != null) {
|
||||
// 极致强制:存入硬引用缓存,永不自动回收
|
||||
mHardCacheMap.put(imagePath, bitmap);
|
||||
@@ -166,7 +165,7 @@ public class BitmapCacheUtils {
|
||||
mRefCountMap.put(imagePath, 1);
|
||||
// 持久化当前路径到 SP
|
||||
saveLastCachePathToSp(imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径(极致强制保持) - " + imagePath);
|
||||
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径(极致强制保持,无压缩) - " + imagePath);
|
||||
} else {
|
||||
LogUtils.e(TAG, "cacheBitmap: 图片解码失败 - " + imagePath);
|
||||
}
|
||||
@@ -291,75 +290,54 @@ public class BitmapCacheUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================== 内部工具方法 ======================================
|
||||
// ====================================== 内部工具方法(无压缩解码) ======================================
|
||||
/**
|
||||
* 高压缩比解码 Bitmap(极致强制缓存核心:通过降低分辨率+RGB_565减少单张Bitmap内存占用)
|
||||
* 无压缩解码 Bitmap(保留原始品质)
|
||||
* @param imagePath 图片绝对路径
|
||||
* @return 解码后的 Bitmap / null(文件无效/解码失败)
|
||||
*/
|
||||
private Bitmap decodeCompressedBitmap(String imagePath) {
|
||||
private Bitmap decodeOriginalBitmap(String imagePath) {
|
||||
// 前置校验:确保文件有效
|
||||
File imageFile = new File(imagePath);
|
||||
if (!imageFile.exists() || !imageFile.isFile() || imageFile.length() <= 0) {
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: 文件无效,跳过解码 - " + imagePath);
|
||||
LogUtils.e(TAG, "decodeOriginalBitmap: 文件无效,跳过解码 - " + imagePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
// 第一步:只获取图片尺寸,不加载像素
|
||||
// 仅获取尺寸用于日志记录,不参与解码逻辑
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(imagePath, options);
|
||||
|
||||
// 校验尺寸是否有效
|
||||
if (options.outWidth <= 0 || options.outHeight <= 0) {
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: 图片尺寸无效 - " + imagePath);
|
||||
LogUtils.e(TAG, "decodeOriginalBitmap: 图片尺寸无效 - " + imagePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 计算高压缩比缩放比例(极致强制缓存核心:尽可能降低分辨率)
|
||||
int sampleSize = calculateHighCompressSampleSize(options, MAX_WIDTH, MAX_HEIGHT);
|
||||
LogUtils.d(TAG, "decodeOriginalBitmap: 图片原始尺寸 - " + options.outWidth + "x" + options.outHeight);
|
||||
|
||||
// 第二步:加载高压缩比的 Bitmap
|
||||
// 无压缩解码配置
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = sampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565; // 强制使用RGB_565,比ARGB_8888少一半内存
|
||||
options.inSampleSize = 1; // 不缩放,采样率为1
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888; // 保留全彩品质(如需节省内存可改为RGB_565,不影响品质)
|
||||
options.inPurgeable = false; // 关闭可清除标志,极致强制保持内存
|
||||
options.inInputShareable = false;
|
||||
options.inDither = false; // 关闭抖动,减少内存占用
|
||||
options.inScaled = false; // 关闭自动缩放,避免额外内存消耗
|
||||
options.inDither = true; // 开启抖动,保证色彩还原
|
||||
options.inScaled = false; // 关闭自动缩放,保留原始尺寸
|
||||
|
||||
try {
|
||||
return BitmapFactory.decodeFile(imagePath, options);
|
||||
} catch (OutOfMemoryError e) {
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: OOM异常(已启用高压缩比) - " + imagePath);
|
||||
LogUtils.e(TAG, "decodeOriginalBitmap: OOM异常(无压缩,图片尺寸过大) - " + imagePath);
|
||||
// 极致强制缓存策略:OOM时仅放弃当前解码,绝对不清理已缓存的Bitmap
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "decodeCompressedBitmap: 解码异常 - " + imagePath, e);
|
||||
LogUtils.e(TAG, "decodeOriginalBitmap: 解码异常 - " + imagePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算高压缩比缩放比例(极致强制缓存核心:优先保证不超过最大尺寸,尽可能压缩)
|
||||
*/
|
||||
private int calculateHighCompressSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
|
||||
int rawWidth = options.outWidth;
|
||||
int rawHeight = options.outHeight;
|
||||
int inSampleSize = 1;
|
||||
|
||||
// 高压缩比逻辑:只要超过最大尺寸,就持续放大采样率
|
||||
while (rawWidth / inSampleSize > maxWidth || rawHeight / inSampleSize > maxHeight) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
|
||||
// 额外压缩:即使未超过最大尺寸,也强制至少压缩1倍(进一步减少内存占用)
|
||||
inSampleSize = Math.max(inSampleSize, 2);
|
||||
|
||||
LogUtils.d(TAG, "calculateHighCompressSampleSize: 高压缩比缩放比例为 " + inSampleSize);
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具方法:判断Bitmap是否有效(非空且未被回收)
|
||||
*/
|
||||
@@ -409,7 +387,7 @@ 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 中无效路径
|
||||
@@ -435,7 +413,7 @@ public class BitmapCacheUtils {
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
// 极致强制缓存策略:内存紧张时仅记录日志,不清理任何缓存
|
||||
LogUtils.w(TAG, "onTrimMemory: 内存紧张级别 - " + level + ",极致强制保持所有Bitmap缓存");
|
||||
LogUtils.w(TAG, "onTrimMemory: 内存紧张级别 - " + level + ",极致强制保持所有Bitmap缓存(无压缩)");
|
||||
// 记录当前缓存状态
|
||||
logCurrentCacheStatus();
|
||||
}
|
||||
@@ -443,7 +421,7 @@ public class BitmapCacheUtils {
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
// 极致强制缓存策略:低内存时仅记录日志,不清理任何缓存
|
||||
LogUtils.w(TAG, "onLowMemory: 系统低内存,极致强制保持所有Bitmap缓存(已启用高压缩比)");
|
||||
LogUtils.w(TAG, "onLowMemory: 系统低内存,极致强制保持所有Bitmap缓存(无压缩)");
|
||||
// 记录当前缓存状态
|
||||
logCurrentCacheStatus();
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.io.File;
|
||||
|
||||
/**
|
||||
* 基于Java7的BackgroundView(LinearLayout+ImageView,保持原图比例居中平铺)
|
||||
* 核心:ImageView保持原图比例,在LinearLayout中居中平铺,无拉伸、无裁剪
|
||||
* 改进:强制保持缓存策略,无论内存是否紧张,不自动清理任何缓存
|
||||
* 核心:ImageView保持原图比例,在LinearLayout中居中平铺,无拉伸、无裁剪、无压缩
|
||||
* 改进:强制保持缓存策略,无论内存是否紧张,不自动清理任何缓存,保留图片原始品质
|
||||
*/
|
||||
public class BackgroundView extends RelativeLayout {
|
||||
|
||||
@@ -100,7 +100,7 @@ public class BackgroundView extends RelativeLayout {
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
mIvBackground.setLayoutParams(ivParams);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 保持比例+居中平铺
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 保持比例+居中平铺(无拉伸裁剪)
|
||||
mIvBackground.setBackgroundColor(0x00000000);
|
||||
mLlContainer.addView(mIvBackground);
|
||||
LogUtils.d(TAG, "=== initImageView 完成 ===");
|
||||
@@ -124,11 +124,11 @@ public class BackgroundView extends RelativeLayout {
|
||||
return;
|
||||
}
|
||||
|
||||
// 核心修改:刷新时不删除旧缓存,仅重新解码并更新缓存(强制保持策略)
|
||||
// 核心修改:刷新时不删除旧缓存,仅重新解码原始品质图片并更新缓存(强制保持策略)
|
||||
if (isRefresh) {
|
||||
LogUtils.d(TAG, "loadByBackgroundBean: 刷新图片,重新解码并更新缓存 - " + targetPath);
|
||||
// 刷新时直接解码,更新缓存(不删除旧缓存)
|
||||
Bitmap newBitmap = decodeBitmapWithCompress(new File(targetPath), 1080, 1920);
|
||||
LogUtils.d(TAG, "loadByBackgroundBean: 刷新图片,重新解码原始品质图片并更新缓存 - " + targetPath);
|
||||
// 刷新时直接解码原始品质图片,更新缓存(不删除旧缓存)
|
||||
Bitmap newBitmap = decodeOriginalBitmap(new File(targetPath));
|
||||
if (newBitmap != null) {
|
||||
App.sBitmapCacheUtils.cacheBitmap(targetPath, newBitmap);
|
||||
// 增加引用计数
|
||||
@@ -140,7 +140,7 @@ public class BackgroundView extends RelativeLayout {
|
||||
|
||||
// ====================================== 对外方法 ======================================
|
||||
/**
|
||||
* 改进版:强制保持缓存策略,不自动清理任何缓存,强化引用计数管理
|
||||
* 改进版:强制保持缓存策略,不自动清理任何缓存,强化引用计数管理,保留图片原始品质
|
||||
* @param imagePath 图片绝对路径
|
||||
*/
|
||||
public void loadImage(String imagePath) {
|
||||
@@ -164,14 +164,14 @@ public class BackgroundView extends RelativeLayout {
|
||||
if (imagePath.equals(mCurrentCachedPath)) {
|
||||
Bitmap cachedBitmap = App.sBitmapCacheUtils.getCachedBitmap(imagePath);
|
||||
if (isBitmapValid(cachedBitmap)) {
|
||||
LogUtils.d(TAG, "loadImage: 路径未变,使用有效缓存 Bitmap");
|
||||
LogUtils.d(TAG, "loadImage: 路径未变,使用有效缓存 Bitmap(原始品质)");
|
||||
mImageAspectRatio = (float) cachedBitmap.getWidth() / cachedBitmap.getHeight();
|
||||
mIvBackground.setImageBitmap(cachedBitmap);
|
||||
adjustImageViewSize();
|
||||
return;
|
||||
} else {
|
||||
LogUtils.e(TAG, "loadImage: 缓存Bitmap无效,尝试重加载 - " + imagePath);
|
||||
// 缓存无效,直接重加载(不删除旧缓存,强制保持策略)
|
||||
LogUtils.e(TAG, "loadImage: 缓存Bitmap无效,尝试重加载原始品质图片 - " + imagePath);
|
||||
// 缓存无效,直接重加载原始品质图片(不删除旧缓存,强制保持策略)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ public class BackgroundView extends RelativeLayout {
|
||||
}
|
||||
// ======================== 路径判断逻辑结束 ========================
|
||||
|
||||
// 无缓存/缓存无效/路径更新:重新加载图片
|
||||
// 无缓存/缓存无效/路径更新:重新加载原始品质图片
|
||||
if (!calculateImageAspectRatio(imageFile)) {
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
@@ -191,19 +191,19 @@ public class BackgroundView extends RelativeLayout {
|
||||
// 先尝试从缓存获取
|
||||
Bitmap bitmap = App.sBitmapCacheUtils.getCachedBitmap(imagePath);
|
||||
if (isBitmapValid(bitmap)) {
|
||||
LogUtils.d(TAG, "loadImage: 从缓存获取有效Bitmap - " + imagePath);
|
||||
LogUtils.d(TAG, "loadImage: 从缓存获取有效Bitmap(原始品质) - " + imagePath);
|
||||
} else {
|
||||
// 缓存无效应解码
|
||||
bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920);
|
||||
// 缓存无效应解码原始品质图片
|
||||
bitmap = decodeOriginalBitmap(imageFile);
|
||||
if (bitmap == null) {
|
||||
LogUtils.e(TAG, "loadImage: 图片解码失败");
|
||||
LogUtils.e(TAG, "loadImage: 图片解码失败(原始品质)");
|
||||
ToastUtils.show("图片解码失败,无法加载");
|
||||
setDefaultTransparentBackground();
|
||||
return;
|
||||
}
|
||||
// 缓存新图片(强制保持)
|
||||
// 缓存新图片(强制保持,原始品质)
|
||||
App.sBitmapCacheUtils.cacheBitmap(imagePath, bitmap);
|
||||
LogUtils.d(TAG, "loadImage: 加载新图片并缓存 - " + imagePath);
|
||||
LogUtils.d(TAG, "loadImage: 加载新图片并缓存(原始品质) - " + imagePath);
|
||||
}
|
||||
|
||||
// 增加引用计数(配合全局缓存工具的引用计数机制)
|
||||
@@ -248,29 +248,21 @@ public class BackgroundView extends RelativeLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* 改进版解码方法:强制缓存策略,关闭可回收标志,使用高压缩比减少OOM风险
|
||||
* 移除压缩逻辑:解码原始品质图片(无缩放、无色彩损失)
|
||||
*/
|
||||
private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) {
|
||||
private Bitmap decodeOriginalBitmap(File file) {
|
||||
try {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
|
||||
// 更精准的采样率计算(避免过度压缩)
|
||||
int scaleX = (int) Math.ceil((float) options.outWidth / maxWidth);
|
||||
int scaleY = (int) Math.ceil((float) options.outHeight / maxHeight);
|
||||
int inSampleSize = Math.max(scaleX, scaleY);
|
||||
inSampleSize = Math.max(1, inSampleSize); // 确保采样率≥1
|
||||
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = inSampleSize;
|
||||
options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存
|
||||
// 核心修改:关闭可回收标志,防止系统主动回收(强制缓存策略)
|
||||
options.inPurgeable = false;
|
||||
// 核心配置:无缩放、全彩品质、关闭可回收标志(强制保持)
|
||||
options.inSampleSize = 1; // 不缩放,采样率为1
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888; // 全彩品质,无色彩损失
|
||||
options.inPurgeable = false; // 关闭可回收标志,防止系统主动回收
|
||||
options.inInputShareable = false;
|
||||
options.inDither = true; // 开启抖动,保证色彩还原精度
|
||||
options.inScaled = false; // 关闭自动缩放,保留原始尺寸
|
||||
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "压缩解码失败:" + e.getMessage());
|
||||
LogUtils.e(TAG, "原始品质解码失败:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -308,7 +300,7 @@ public class BackgroundView extends RelativeLayout {
|
||||
params.width = ivWidth;
|
||||
params.height = ivHeight;
|
||||
mIvBackground.setLayoutParams(params);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER);
|
||||
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 仅居中平铺,无缩放
|
||||
mIvBackground.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@@ -360,7 +352,7 @@ public class BackgroundView extends RelativeLayout {
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写:恢复尺寸调整逻辑,确保View尺寸变化时正确显示
|
||||
* 重写:恢复尺寸调整逻辑,确保View尺寸变化时正确显示(无压缩)
|
||||
*/
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
|
||||
Reference in New Issue
Block a user