From 0f736c20070af2632936ea696bc6a75b43d9ac63 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 22 Dec 2025 12:59:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E4=BD=8D=E5=9B=BE=E5=8E=8B?= =?UTF-8?q?=E7=BC=A9=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=B0=BD=E9=87=8F=E4=BF=9D?= =?UTF-8?q?=E6=8C=81=E4=BD=8D=E5=9B=BE=E5=93=81=E8=B4=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../powerbell/utils/BitmapCacheUtils.java | 78 +++++++------------ .../powerbell/views/BackgroundView.java | 66 +++++++--------- 3 files changed, 59 insertions(+), 89 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index 30cb53e..13c2b81 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Dec 22 11:21:24 HKT 2025 +#Mon Dec 22 04:57:52 GMT 2025 stageCount=23 libraryProject= baseVersion=15.14 publishVersion=15.14.22 -buildCount=0 +buildCount=2 baseBetaVersion=15.14.23 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 index fbb4341..3d2b334 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BitmapCacheUtils.java @@ -16,17 +16,16 @@ import java.util.concurrent.ConcurrentHashMap; /** * @Author ZhanGSKen&豆包大模型 * @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(); } 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 794aed8..2678004 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 @@ -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) {