Compare commits

...

6 Commits

4 changed files with 375 additions and 279 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Mon Dec 22 10:04:49 HKT 2025 #Mon Dec 22 11:02:54 HKT 2025
stageCount=19 stageCount=22
libraryProject= libraryProject=
baseVersion=15.14 baseVersion=15.14
publishVersion=15.14.18 publishVersion=15.14.21
buildCount=0 buildCount=0
baseBetaVersion=15.14.19 baseBetaVersion=15.14.22

View File

@@ -1,15 +1,11 @@
package cc.winboll.studio.powerbell; package cc.winboll.studio.powerbell;
import android.content.ComponentCallbacks2;
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.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.models.NotificationMessage;
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;
@@ -17,10 +13,10 @@ import cc.winboll.studio.powerbell.utils.BitmapCacheUtils;
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils; import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView; import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView;
import java.io.File; import java.io.File;
import java.util.concurrent.TimeUnit;
/** /**
* 应用全局入口类适配Android API 30基于Java 7编写 * 应用全局入口类适配Android API 30基于Java 7编写
* 核心策略:无论内存是否紧张,强制保持位图缓存与视图控件缓存
*/ */
public class App extends GlobalApplication { public class App extends GlobalApplication {
// ===================== 常量定义区 ===================== // ===================== 常量定义区 =====================
@@ -36,29 +32,17 @@ public class App extends GlobalApplication {
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1"; 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 ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2";
// 内存紧张通知文案常量
private static final String TRIM_MEMORY_NOTIFY_TITLE = "应用使用时内存紧张提醒";
private static final String TRIM_MEMORY_NOTIFY_CONTENT = "由于本应用使用时,系统通知内存紧张程度级别较高,图片缓存功能暂时不启用。";
// 定时任务间隔常量(分钟)
//private static final long TIMER_INTERVAL_MINUTES = 1;
// ===================== 静态属性区 ===================== // ===================== 静态属性区 =====================
// 数据配置工具 // 数据配置工具
private static AppConfigUtils sAppConfigUtils; private static AppConfigUtils sAppConfigUtils;
private static AppCacheUtils sAppCacheUtils; private static AppCacheUtils sAppCacheUtils;
// 全局Bitmap缓存工具 // 全局Bitmap缓存工具(强制保持,不随内存紧张清理)
public static BitmapCacheUtils sBitmapCacheUtils; public static BitmapCacheUtils sBitmapCacheUtils;
// 全局视图控件缓存工具 // 全局视图控件缓存工具(强制保持,不随内存紧张清理)
public static MemoryCachedBackgroundView sMemoryCachedBackgroundView; public static MemoryCachedBackgroundView sMemoryCachedBackgroundView;
// 临时文件夹路径 // 临时文件夹路径
private static String sTempDirPath = ""; private static String sTempDirPath = "";
// 定时任务静态属性(全局唯一)
// private static Handler sTimerHandler;
// private static Runnable sTimerRunnable;
// private static boolean sIsTimerRunning = false;
//
// ===================== 成员属性区 ===================== // ===================== 成员属性区 =====================
// 全局广播接收器 // 全局广播接收器
private GlobalApplicationReceiver mGlobalReceiver; private GlobalApplicationReceiver mGlobalReceiver;
@@ -119,14 +103,12 @@ public class App extends GlobalApplication {
initBaseTools(); initBaseTools();
// 初始化临时文件夹 // 初始化临时文件夹
initTempDir(); initTempDir();
// 初始化工具类实例 // 初始化工具类实例(含强制缓存工具)
initUtils(); initUtils();
// 初始化广播接收器 // 初始化广播接收器
initReceiver(); initReceiver();
// 启动定时任务
//initTimerTask();
LogUtils.d(TAG, "onCreate() 应用初始化完成"); LogUtils.d(TAG, "onCreate() 应用初始化完成,强制缓存策略已启用");
} }
@Override @Override
@@ -138,8 +120,8 @@ public class App extends GlobalApplication {
ToastUtils.release(); ToastUtils.release();
// 释放通知工具 // 释放通知工具
releaseNotificationManager(); releaseNotificationManager();
// 停止定时任务 // 释放广播接收器
//stopTimerTask(); releaseReceiver();
LogUtils.d(TAG, "onTerminate() 应用资源释放完成"); LogUtils.d(TAG, "onTerminate() 应用资源释放完成");
} }
@@ -147,23 +129,19 @@ public class App extends GlobalApplication {
@Override @Override
public void onTrimMemory(int level) { public void onTrimMemory(int level) {
super.onTrimMemory(level); super.onTrimMemory(level);
LogUtils.d(TAG, "onTrimMemory() 调用内存等级level" + level); // 核心修改:移除所有缓存清理逻辑,强制保持位图和视图控件缓存
sMemoryCachedBackgroundView.clearAllCache(); LogUtils.w(TAG, "onTrimMemory() 调用内存等级level" + level + ",强制保持所有缓存(不清理)");
sMemoryCachedBackgroundView.getLastInstance(this); // 仅记录缓存状态,不执行任何清理操作
// logCacheStatus();
// // 初始化通知工具(若未初始化) }
// if (mNotificationManager == null) {
// mNotificationManager = new NotificationManagerUtils(this); @Override
// LogUtils.d(TAG, "onTrimMemory()NotificationManagerUtils实例已初始化"); public void onLowMemory() {
// } super.onLowMemory();
// // 核心修改:低内存时也不清理缓存,仅记录日志
// // 内存紧张等级判断 LogUtils.w(TAG, "onLowMemory() 调用,强制保持所有缓存(不清理)");
// if (level > ComponentCallbacks2.TRIM_MEMORY_MODERATE) { // 仅记录缓存状态,不执行任何清理操作
// sendTrimMemoryNotification(level); logCacheStatus();
// } else {
// sBitmapCacheUtils = BitmapCacheUtils.getInstance();
// LogUtils.d(TAG, "onTrimMemory()Bitmap缓存已启用");
// }
} }
// ===================== 私有初始化方法区 ===================== // ===================== 私有初始化方法区 =====================
@@ -193,15 +171,27 @@ public class App extends GlobalApplication {
} }
/** /**
* 初始化工具类实例 * 初始化工具类实例(核心:强制初始化缓存工具,不随内存紧张重建)
*/ */
private void initUtils() { private void initUtils() {
LogUtils.d(TAG, "initUtils() 开始初始化工具类"); LogUtils.d(TAG, "initUtils() 开始初始化工具类,启用强制缓存策略");
sAppConfigUtils = getAppConfigUtils(this); sAppConfigUtils = getAppConfigUtils(this);
sAppCacheUtils = getAppCacheUtils(this); sAppCacheUtils = getAppCacheUtils(this);
sBitmapCacheUtils = BitmapCacheUtils.getInstance();
// 强制初始化Bitmap缓存工具单例唯一不重复创建
if (sBitmapCacheUtils == null) {
sBitmapCacheUtils = BitmapCacheUtils.getInstance();
LogUtils.d(TAG, "initUtils() Bitmap缓存工具已初始化强制保持");
}
// 强制初始化视图控件缓存工具(单例唯一,不重复创建)
if (sMemoryCachedBackgroundView == null) {
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getLastInstance(this);
LogUtils.d(TAG, "initUtils() 视图控件缓存工具已初始化(强制保持)");
}
mNotificationManager = new NotificationManagerUtils(this); mNotificationManager = new NotificationManagerUtils(this);
LogUtils.d(TAG, "initUtils() 工具类初始化完成"); LogUtils.d(TAG, "initUtils() 工具类初始化完成,强制缓存策略已生效");
} }
/** /**
@@ -214,68 +204,21 @@ public class App extends GlobalApplication {
LogUtils.d(TAG, "initReceiver() 广播接收器注册完成"); LogUtils.d(TAG, "initReceiver() 广播接收器注册完成");
} }
/**
* 初始化定时任务(全局唯一实例)
*/
// private void initTimerTask() {
// LogUtils.d(TAG, "initTimerTask() 开始初始化定时任务,当前运行状态:" + sIsTimerRunning);
//
// // 已运行则直接返回
// if (sIsTimerRunning) {
// LogUtils.d(TAG, "initTimerTask() 定时任务已在运行,无需重复启动");
// return;
// }
//
// // 初始化Handler
// if (sTimerHandler == null) {
// sTimerHandler = new Handler(Looper.getMainLooper());
// LogUtils.d(TAG, "initTimerTask() 定时任务Handler已初始化");
// }
//
// // 初始化Runnable
// if (sTimerRunnable == null) {
// sTimerRunnable = new Runnable() {
// @Override
// public void run() {
// try {
// LogUtils.d(TAG, "定时任务执行,间隔:" + TIMER_INTERVAL_MINUTES + "分钟");
// sBitmapCacheUtils = BitmapCacheUtils.getInstance();
// LogUtils.d(TAG, "定时任务Bitmap缓存已重新初始化");
// } catch (Exception e) {
// LogUtils.e(TAG, "定时任务执行异常:" + e.getMessage());
// } finally {
// if (sIsTimerRunning) {
// long delayMillis = TimeUnit.MINUTES.toMillis(TIMER_INTERVAL_MINUTES);
// sTimerHandler.postDelayed(this, delayMillis);
// LogUtils.d(TAG, "定时任务已预约下次执行,延迟:" + delayMillis + "ms");
// }
// }
// }
// };
// LogUtils.d(TAG, "initTimerTask() 定时任务Runnable已初始化");
// }
//
// // 启动任务
// sTimerHandler.post(sTimerRunnable);
// sIsTimerRunning = true;
// LogUtils.d(TAG, "initTimerTask() 定时任务已启动,间隔:" + TIMER_INTERVAL_MINUTES + "分钟");
// }
// ===================== 私有工具方法区 ===================== // ===================== 私有工具方法区 =====================
/** /**
* 停止定时任务 * 释放广播接收器资源
*/ */
// private void stopTimerTask() { private void releaseReceiver() {
// LogUtils.d(TAG, "stopTimerTask() 开始停止定时任务"); LogUtils.d(TAG, "releaseReceiver() 开始释放广播接收器");
// if (sTimerHandler != null && sTimerRunnable != null) { if (mGlobalReceiver != null) {
// sTimerHandler.removeCallbacks(sTimerRunnable); mGlobalReceiver.unregisterAction();
// sIsTimerRunning = false; mGlobalReceiver = null;
// LogUtils.d(TAG, "stopTimerTask() 定时任务已停止运行状态重置为false"); LogUtils.d(TAG, "releaseReceiver() 广播接收器资源已释放");
// } else { } else {
// LogUtils.d(TAG, "stopTimerTask() 定时任务未初始化,无需停止"); LogUtils.d(TAG, "releaseReceiver() 广播接收器未初始化,无需释放");
// } }
// } }
//
/** /**
* 释放通知管理工具资源 * 释放通知管理工具资源
*/ */
@@ -291,53 +234,17 @@ public class App extends GlobalApplication {
} }
/** /**
* 发送内存紧张通知 * 记录缓存状态(用于调试,不影响缓存数据)
*/ */
private void sendTrimMemoryNotification(int level) { private void logCacheStatus() {
LogUtils.d(TAG, "sendTrimMemoryNotification() 调用内存等级level" + level); LogUtils.d(TAG, "logCacheStatus() 开始记录缓存状态");
NotificationMessage message = new NotificationMessage(); if (sBitmapCacheUtils != null) {
message.setTitle(TRIM_MEMORY_NOTIFY_TITLE); LogUtils.d(TAG, "logCacheStatus() Bitmap缓存工具实例有效强制保持");
String content = String.format("%s [ 缓存紧张级别描述: Level %d | %s ]",
TRIM_MEMORY_NOTIFY_CONTENT, level, getTrimMemoryLevelDesc(level));
message.setContent(content);
mNotificationManager.showConfigNotification(this, message);
LogUtils.d(TAG, "sendTrimMemoryNotification() 内存紧张通知已发送,内容:" + content);
}
/**
* 转换内存等级为可读描述
*/
private String getTrimMemoryLevelDesc(int level) {
LogUtils.d(TAG, "getTrimMemoryLevelDesc() 调用传入level" + level);
String desc;
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
desc = "TRIM_MEMORY_COMPLETE应用内存完全紧张";
break;
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
desc = "MODERATE中等内存紧张";
break;
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
desc = "BACKGROUND应用进入后台";
break;
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
desc = "BACKGROUND应用UI隐藏";
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
desc = "RUNNING_CRITICAL应用运行关键级紧张";
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
desc = "RUNNING_LOW应用运行低内存";
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
desc = "RUNNING_MODERATE应用运行中等内存紧张";
break;
default:
desc = "UNKNOWN(" + level + ")";
break;
} }
LogUtils.d(TAG, "getTrimMemoryLevelDesc() 内存等级描述结果:" + desc); if (sMemoryCachedBackgroundView != null) {
return desc; LogUtils.d(TAG, "logCacheStatus() 视图控件缓存工具实例有效(强制保持)");
}
LogUtils.d(TAG, "logCacheStatus() 缓存状态记录完成,所有缓存均强制保持");
} }
} }

View File

@@ -4,25 +4,33 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.text.TextUtils; import android.text.TextUtils;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.App;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/11 01:57 * @Date 2025/12/11 01:57
* @Describe 单例 Bitmap 缓存工具类Java 7 兼容) * @Describe 单例 Bitmap 缓存工具类Java 7 兼容)- 强制缓存版
* 功能:内存缓存 Bitmap支持路径关联缓存、全局获取、缓存清空、SP 持久化最后缓存路径、构造时预加载 * 功能:内存缓存 Bitmap支持路径关联缓存、全局获取、缓存清空、SP 持久化最后缓存路径、构造时预加载
* 特点1. 单例模式 2. 压缩加载避免OOM 3. 路径-Bitmap 映射 4. 线程安全 5. SP 持久化最后缓存路径 6. 构造时预加载 * 特点1. 单例模式 2. 硬引用唯一缓存(强制保持,内存紧张不回收) 3. 路径-Bitmap 映射 4. 线程安全
* 5. SP 持久化最后缓存路径 6. 构造时预加载 7. 引用计数防误回收 8. 高压缩比减少OOM风险
* 核心策略无论内存如何紧张强制保持已缓存的Bitmap通过高压缩比降低单张Bitmap内存占用
*/ */
public class BitmapCacheUtils { public class BitmapCacheUtils {
public static final String TAG = "BitmapCacheUtils"; public static final String TAG = "BitmapCacheUtils";
// 最大图片尺寸(适配1080P屏幕可根据需求调整 // 最大图片尺寸(降低至720P进一步减少内存占用强制缓存核心策略
private static final int MAX_WIDTH = 1080; private static final int MAX_WIDTH = 720;
private static final int MAX_HEIGHT = 1920; private static final int MAX_HEIGHT = 1280;
// SP 相关常量 // SP 相关常量
private static final String SP_NAME = "BitmapCacheSP"; private static final String SP_NAME = "BitmapCacheSP";
@@ -30,18 +38,24 @@ public class BitmapCacheUtils {
// 单例实例volatile 保证多线程可见性) // 单例实例volatile 保证多线程可见性)
private static volatile BitmapCacheUtils sInstance; private static volatile BitmapCacheUtils sInstance;
// 路径-Bitmap 缓存容器(内存缓存 // 路径-Bitmap 硬引用缓存(唯一缓存,强制保持,内存紧张不回收
private final Map<String, Bitmap> mBitmapCacheMap; private final Map<String, Bitmap> mHardCacheMap;
// 路径-引用计数 映射(解决多实例共享问题)
private final Map<String, Integer> mRefCountMap;
// SP 实例(用于持久化最后缓存路径) // SP 实例(用于持久化最后缓存路径)
private final SharedPreferences mSp; private final SharedPreferences mSp;
// 私有构造器(单例模式) // 私有构造器(单例模式)
private BitmapCacheUtils() { private BitmapCacheUtils() {
mBitmapCacheMap = new HashMap<>(); // 使用ConcurrentHashMap保证线程安全避免手动同步
mHardCacheMap = new ConcurrentHashMap<>();
mRefCountMap = new ConcurrentHashMap<>();
// 初始化 SP使用 App 全局上下文,避免内存泄漏) // 初始化 SP使用 App 全局上下文,避免内存泄漏)
mSp = App.getInstance().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); mSp = App.getInstance().getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
// 构造时自动预加载 SP 中保存的最后一次缓存路径的图片 // 构造时自动预加载 SP 中保存的最后一次缓存路径的图片
preloadLastCachedBitmap(); preloadLastCachedBitmap();
// 注册内存状态监听(仅记录日志,不清理缓存)
registerMemoryStatusListener();
} }
/** /**
@@ -58,6 +72,28 @@ public class BitmapCacheUtils {
return sInstance; return sInstance;
} }
/**
* 补充接口直接缓存已解码的Bitmap适配BackgroundView改进需求
* @param imagePath 图片绝对路径
* @param bitmap 已解码的有效Bitmap
* @return 缓存后的Bitmap / null参数无效
*/
public Bitmap cacheBitmap(String imagePath, Bitmap bitmap) {
if (TextUtils.isEmpty(imagePath) || !isBitmapValid(bitmap)) {
LogUtils.e(TAG, "cacheBitmap: 路径或Bitmap无效");
return null;
}
// 强制存入硬引用缓存,不转软引用
mHardCacheMap.put(imagePath, bitmap);
// 初始化引用计数为1
mRefCountMap.put(imagePath, 1);
// 持久化当前路径到 SP
saveLastCachePathToSp(imagePath);
LogUtils.d(TAG, "cacheBitmap: 直接缓存已解码Bitmap成功强制保持 - " + imagePath);
return bitmap;
}
/** /**
* 核心接口:根据图片路径缓存 Bitmap 到内存,并持久化路径到 SP * 核心接口:根据图片路径缓存 Bitmap 到内存,并持久化路径到 SP
* @param imagePath 图片绝对路径 * @param imagePath 图片绝对路径
@@ -76,29 +112,26 @@ public class BitmapCacheUtils {
} }
// 已缓存则直接返回,避免重复加载 // 已缓存则直接返回,避免重复加载
if (mBitmapCacheMap.containsKey(imagePath)) { Bitmap hardCacheBitmap = mHardCacheMap.get(imagePath);
Bitmap cachedBitmap = mBitmapCacheMap.get(imagePath); if (isBitmapValid(hardCacheBitmap)) {
// 额外校验缓存的Bitmap是否有效 LogUtils.d(TAG, "cacheBitmap: 硬引用缓存命中,引用计数+1 - " + imagePath);
if (cachedBitmap != null && !cachedBitmap.isRecycled()) { // 引用计数+1
LogUtils.d(TAG, "cacheBitmap: 图片已缓存,直接返回 - " + imagePath); increaseRefCount(imagePath);
// 持久化当前路径到 SP(更新最后缓存路径) // 持久化当前路径到 SP
saveLastCachePathToSp(imagePath); saveLastCachePathToSp(imagePath);
return cachedBitmap; return hardCacheBitmap;
} else {
// 缓存的Bitmap已失效移除后重新加载
mBitmapCacheMap.remove(imagePath);
LogUtils.w(TAG, "cacheBitmap: 缓存Bitmap已失效移除后重新加载 - " + imagePath);
}
} }
// 压缩加载 Bitmap避免OOM // 压缩加载 Bitmap强制缓存核心通过降低分辨率减少单张Bitmap内存占用
Bitmap bitmap = decodeCompressedBitmap(imagePath); Bitmap bitmap = decodeCompressedBitmap(imagePath);
if (bitmap != null) { if (bitmap != null) {
// 存入缓存容器 // 强制存入硬引用缓存,不转软引用
mBitmapCacheMap.put(imagePath, bitmap); mHardCacheMap.put(imagePath, bitmap);
// 持久化当前路径到 SP更新最后缓存路径 // 初始化引用计数为1
mRefCountMap.put(imagePath, 1);
// 持久化当前路径到 SP
saveLastCachePathToSp(imagePath); saveLastCachePathToSp(imagePath);
LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径 - " + imagePath); LogUtils.d(TAG, "cacheBitmap: 图片缓存成功并持久化路径(强制保持) - " + imagePath);
} else { } else {
LogUtils.e(TAG, "cacheBitmap: 图片解码失败 - " + imagePath); LogUtils.e(TAG, "cacheBitmap: 图片解码失败 - " + imagePath);
} }
@@ -114,46 +147,103 @@ public class BitmapCacheUtils {
if (TextUtils.isEmpty(imagePath)) { if (TextUtils.isEmpty(imagePath)) {
return null; return null;
} }
Bitmap bitmap = mBitmapCacheMap.get(imagePath);
// 校验Bitmap是否有效 // 仅从硬引用缓存获取,无软引用 fallback
if (bitmap != null && bitmap.isRecycled()) { Bitmap hardCacheBitmap = mHardCacheMap.get(imagePath);
mBitmapCacheMap.remove(imagePath); if (isBitmapValid(hardCacheBitmap)) {
return null; return hardCacheBitmap;
} }
return bitmap;
// 缓存未命中或Bitmap已失效
return null;
} }
/** /**
* 清空所有 Bitmap 缓存(释放内存),并清空 SP 中保存的最后缓存路径 * 新增接口增加指定路径Bitmap的引用计数
* @param imagePath 图片绝对路径
*/
public void increaseRefCount(String imagePath) {
if (TextUtils.isEmpty(imagePath)) {
return;
}
synchronized (mRefCountMap) {
Integer count = mRefCountMap.get(imagePath);
if (count == null) {
mRefCountMap.put(imagePath, 1);
} else {
mRefCountMap.put(imagePath, count + 1);
}
LogUtils.d(TAG, "increaseRefCount: " + imagePath + " 引用计数变为 " + mRefCountMap.get(imagePath));
}
}
/**
* 新增接口减少指定路径Bitmap的引用计数计数为0时仅标记不回收强制缓存策略
* @param imagePath 图片绝对路径
*/
public void decreaseRefCount(String imagePath) {
if (TextUtils.isEmpty(imagePath)) {
return;
}
synchronized (mRefCountMap) {
Integer count = mRefCountMap.get(imagePath);
if (count == null || count <= 0) {
return;
}
int newCount = count - 1;
if (newCount <= 0) {
// 强制缓存策略引用计数为0时仅移除计数不回收Bitmap
mRefCountMap.remove(imagePath);
LogUtils.d(TAG, "decreaseRefCount: " + imagePath + " 引用计数为0保留Bitmap强制缓存");
} else {
mRefCountMap.put(imagePath, newCount);
LogUtils.d(TAG, "decreaseRefCount: " + imagePath + " 引用计数变为 " + newCount);
}
}
}
/**
* 清空所有 Bitmap 缓存(仅手动调用时执行,内存紧张时不自动执行)
*/ */
public void clearAllCache() { public void clearAllCache() {
synchronized (mBitmapCacheMap) { LogUtils.d(TAG, "clearAllCache: 手动清空所有缓存(强制缓存策略:仅手动触发)");
for (Bitmap bitmap : mBitmapCacheMap.values()) {
if (bitmap != null && !bitmap.isRecycled()) { // 清空硬引用缓存并回收Bitmap
bitmap.recycle(); // 主动回收 Bitmap for (Bitmap bitmap : mHardCacheMap.values()) {
} if (isBitmapValid(bitmap)) {
bitmap.recycle();
} }
mBitmapCacheMap.clear();
} }
mHardCacheMap.clear();
// 清空引用计数
mRefCountMap.clear();
// 清空 SP 中保存的最后缓存路径 // 清空 SP 中保存的最后缓存路径
clearLastCachePathInSp(); clearLastCachePathInSp();
LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空SP 路径已清除");
LogUtils.d(TAG, "clearAllCache: 所有 Bitmap 缓存已清空");
} }
/** /**
* 移除指定路径的 Bitmap 缓存 * 移除指定路径的 Bitmap 缓存(仅手动调用时执行,内存紧张时不自动执行)
* @param imagePath 图片绝对路径 * @param imagePath 图片绝对路径
*/ */
public void removeCachedBitmap(String imagePath) { public void removeCachedBitmap(String imagePath) {
if (TextUtils.isEmpty(imagePath)) { if (TextUtils.isEmpty(imagePath)) {
return; return;
} }
synchronized (mBitmapCacheMap) {
Bitmap bitmap = mBitmapCacheMap.remove(imagePath); synchronized (mRefCountMap) {
if (bitmap != null && !bitmap.isRecycled()) { // 手动移除时才回收Bitmap
bitmap.recycle(); Bitmap hardBitmap = mHardCacheMap.remove(imagePath);
LogUtils.d(TAG, "removeCachedBitmap: 移除并回收缓存 - " + imagePath); if (isBitmapValid(hardBitmap)) {
hardBitmap.recycle();
LogUtils.d(TAG, "removeCachedBitmap: 手动回收硬引用缓存 - " + imagePath);
} }
mRefCountMap.remove(imagePath);
// 若移除的是最后缓存的路径,清空 SP // 若移除的是最后缓存的路径,清空 SP
String lastPath = getLastCachePathFromSp(); String lastPath = getLastCachePathFromSp();
if (imagePath.equals(lastPath)) { if (imagePath.equals(lastPath)) {
@@ -164,7 +254,7 @@ public class BitmapCacheUtils {
} }
/** /**
* 压缩解码 Bitmap按最大尺寸缩放避免OOM * 压缩解码 Bitmap强制缓存核心:通过降低分辨率+RGB_565减少单张Bitmap内存占用
* @param imagePath 图片绝对路径 * @param imagePath 图片绝对路径
* @return 解码后的 Bitmap / null文件无效/解码失败) * @return 解码后的 Bitmap / null文件无效/解码失败)
*/ */
@@ -187,20 +277,21 @@ public class BitmapCacheUtils {
return null; return null;
} }
// 计算缩放比例 // 计算高压缩比缩放比例(强制缓存核心:尽可能降低分辨率)
int sampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT); int sampleSize = calculateHighCompressSampleSize(options, MAX_WIDTH, MAX_HEIGHT);
// 第二步:加载压缩的 Bitmap // 第二步:加载压缩的 Bitmap
options.inJustDecodeBounds = false; options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize; options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存(比ARGB_8888少一半内存 options.inPreferredConfig = Bitmap.Config.RGB_565; // 强制使用RGB_565比ARGB_8888少一半内存
options.inPurgeable = true; options.inPurgeable = false; // 关闭可清除标志,强制保持内存
options.inInputShareable = true; options.inInputShareable = false;
try { try {
return BitmapFactory.decodeFile(imagePath, options); return BitmapFactory.decodeFile(imagePath, options);
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
LogUtils.e(TAG, "decodeCompressedBitmap: OOM异常 - " + imagePath); LogUtils.e(TAG, "decodeCompressedBitmap: OOM异常(已启用高压缩比) - " + imagePath);
// 强制缓存策略OOM时仅记录日志不清理已缓存的Bitmap
return null; return null;
} catch (Exception e) { } catch (Exception e) {
LogUtils.e(TAG, "decodeCompressedBitmap: 解码异常 - " + imagePath, e); LogUtils.e(TAG, "decodeCompressedBitmap: 解码异常 - " + imagePath, e);
@@ -209,23 +300,29 @@ public class BitmapCacheUtils {
} }
/** /**
* 计算 Bitmap 缩放比例 * 计算高压缩比缩放比例(强制缓存核心:优先保证不超过最大尺寸,尽可能压缩)
*/ */
private int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) { private int calculateHighCompressSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
int rawWidth = options.outWidth; int rawWidth = options.outWidth;
int rawHeight = options.outHeight; int rawHeight = options.outHeight;
int inSampleSize = 1; int inSampleSize = 1;
if (rawWidth > maxWidth || rawHeight > maxHeight) { // 高压缩比逻辑:只要超过最大尺寸,就持续放大采样率
int halfWidth = rawWidth / 2; while (rawWidth / inSampleSize > maxWidth || rawHeight / inSampleSize > maxHeight) {
int halfHeight = rawHeight / 2; inSampleSize *= 2;
while ((halfWidth / inSampleSize) >= maxWidth && (halfHeight / inSampleSize) >= maxHeight) {
inSampleSize *= 2;
}
} }
LogUtils.d(TAG, "calculateHighCompressSampleSize: 高压缩比缩放比例为 " + inSampleSize);
return inSampleSize; return inSampleSize;
} }
/**
* 工具方法判断Bitmap是否有效非空且未被回收
*/
private boolean isBitmapValid(Bitmap bitmap) {
return bitmap != null && !bitmap.isRecycled();
}
/** /**
* 从 SP 中获取最后一次缓存的图片路径 * 从 SP 中获取最后一次缓存的图片路径
* @return 最后缓存的路径 / null未保存 * @return 最后缓存的路径 / null未保存
@@ -266,12 +363,44 @@ public class BitmapCacheUtils {
// 调用 cacheBitmap 预加载(内部已做文件校验和缓存判断) // 调用 cacheBitmap 预加载(内部已做文件校验和缓存判断)
Bitmap bitmap = cacheBitmap(lastPath); Bitmap bitmap = cacheBitmap(lastPath);
if (bitmap != null) { if (bitmap != null) {
LogUtils.d(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径成功 - " + lastPath); LogUtils.d(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径成功(强制保持) - " + lastPath);
} else { } else {
LogUtils.w(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径失败,清空无效路径 - " + lastPath); LogUtils.w(TAG, "preloadLastCachedBitmap: 预加载 SP 中最后缓存路径失败,清空无效路径 - " + lastPath);
// 预加载失败,清空 SP 中无效路径 // 预加载失败,清空 SP 中无效路径
clearLastCachePathInSp(); clearLastCachePathInSp();
} }
} }
/**
* 注册内存状态监听(仅记录日志,不清理缓存,强制缓存策略)
*/
private void registerMemoryStatusListener() {
if (Build.VERSION.SDK_INT >= 14) {
App.getInstance().registerComponentCallbacks(new MemoryStatusCallback());
LogUtils.d(TAG, "registerMemoryStatusListener: 内存状态监听已注册(仅记录日志,不清理缓存)");
}
}
/**
* 内存状态回调(仅记录日志,不清理缓存,强制缓存策略)
*/
private class MemoryStatusCallback implements android.content.ComponentCallbacks2 {
@Override
public void onTrimMemory(int level) {
// 强制缓存策略:内存紧张时仅记录日志,不清理任何缓存
LogUtils.w(TAG, "onTrimMemory: 内存紧张级别 - " + level + "强制保持所有Bitmap缓存");
}
@Override
public void onLowMemory() {
// 强制缓存策略:低内存时仅记录日志,不清理任何缓存
LogUtils.w(TAG, "onLowMemory: 系统低内存强制保持所有Bitmap缓存已启用高压缩比");
}
@Override
public void onConfigurationChanged(android.content.res.Configuration newConfig) {
// 配置变化时无需处理
}
}
} }

View File

@@ -3,7 +3,9 @@ package cc.winboll.studio.powerbell.views;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
@@ -15,17 +17,17 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.App;
import cc.winboll.studio.powerbell.models.BackgroundBean; import cc.winboll.studio.powerbell.models.BackgroundBean;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import java.io.File; import java.io.File;
/** /**
* 基于Java7的BackgroundViewLinearLayout+ImageView保持原图比例居中平铺 * 基于Java7的BackgroundViewLinearLayout+ImageView保持原图比例居中平铺
* 核心ImageView保持原图比例在LinearLayout中居中平铺无拉伸、无裁剪 * 核心ImageView保持原图比例在LinearLayout中居中平铺无拉伸、无裁剪
* 改进强化Bitmap生命周期管理防止已回收Bitmap绘制崩溃
*/ */
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 String mCurrentCachedPath = "";
private Context mContext; private Context mContext;
@@ -108,7 +110,7 @@ public class BackgroundView extends RelativeLayout {
loadByBackgroundBean(bean, false); loadByBackgroundBean(bean, false);
} }
public void loadByBackgroundBean(BackgroundBean bean, boolean isRefresh) { public void loadByBackgroundBean(BackgroundBean bean, boolean isRefresh) {
if (!bean.isUseBackgroundFile()) { if (!bean.isUseBackgroundFile()) {
setDefaultTransparentBackground(); setDefaultTransparentBackground();
return; return;
@@ -117,22 +119,26 @@ public class BackgroundView extends RelativeLayout {
? bean.getBackgroundScaledCompressFilePath() ? bean.getBackgroundScaledCompressFilePath()
: bean.getBackgroundFilePath(); : bean.getBackgroundFilePath();
if (!(new File(targetPath).exists())) { if (!(new File(targetPath).exists())) {
LogUtils.d(TAG, String.format("视图控件图片不存在:%s", targetPath)); LogUtils.d(TAG, String.format("视图控件图片不存在:%s", targetPath));
return; return;
} }
// 调用带路径判断的loadImage方法 // 调用带路径判断的loadImage方法
if (isRefresh) { if (isRefresh) {
App.sBitmapCacheUtils.removeCachedBitmap(targetPath); App.sBitmapCacheUtils.removeCachedBitmap(targetPath);
App.sBitmapCacheUtils.cacheBitmap(targetPath); // 刷新时直接解码,避免缓存旧数据
} Bitmap newBitmap = decodeBitmapWithCompress(new File(targetPath), 1080, 1920);
loadImage(targetPath); if (newBitmap != null) {
App.sBitmapCacheUtils.cacheBitmap(targetPath, newBitmap);
}
}
loadImage(targetPath);
} }
// ====================================== 对外方法 ====================================== // ====================================== 对外方法 ======================================
/** /**
* 改造后添加路径判断路径更新时同步更新缓存缓存Bitmap为null时提示并加载透明背景 * 改进版强化Bitmap有效性校验增加缓存重加载机制防止已回收Bitmap崩溃
* @param imagePath 图片绝对路径 * @param imagePath 图片绝对路径
*/ */
public void loadImage(String imagePath) { public void loadImage(String imagePath) {
@@ -149,36 +155,33 @@ public class BackgroundView extends RelativeLayout {
return; return;
} }
//mIvBackground.setVisibility(View.GONE); mIvBackground.setVisibility(View.GONE);
// ======================== 新增:路径判断逻辑 ======================== // ======================== 路径判断逻辑(改进版) ========================
// 1. 路径未变化:直接使用缓存 // 1. 路径未变化:校验缓存有效性,无效则重加载
if (imagePath.equals(mCurrentCachedPath)) { if (imagePath.equals(mCurrentCachedPath)) {
Bitmap cachedBitmap = App.sBitmapCacheUtils.getCachedBitmap(imagePath); Bitmap cachedBitmap = App.sBitmapCacheUtils.getCachedBitmap(imagePath);
// 核心修改判断缓存Bitmap是否为null if (isBitmapValid(cachedBitmap)) {
if (cachedBitmap != null && !cachedBitmap.isRecycled()) { LogUtils.d(TAG, "loadImage: 路径未变,使用有效缓存 Bitmap");
LogUtils.d(TAG, "loadImage: 路径未变,使用缓存 Bitmap"); mImageAspectRatio = (float) cachedBitmap.getWidth() / cachedBitmap.getHeight();
mImageAspectRatio = (float) cachedBitmap.getWidth() / cachedBitmap.getHeight(); mIvBackground.setImageBitmap(cachedBitmap);
mIvBackground.setImageBitmap(cachedBitmap); adjustImageViewSize();
adjustImageViewSize();
return;
} else {
// 缓存Bitmap为空或已回收提示并加载透明背景
LogUtils.e(TAG, "loadImage: 全局位图缓存为空或已回收 - " + imagePath);
ToastUtils.show("全局位图缓存为空,无法加载图片");
setDefaultTransparentBackground();
return; return;
} else {
LogUtils.e(TAG, "loadImage: 缓存Bitmap无效尝试重加载 - " + imagePath);
// 缓存无效,移除旧缓存并强制重加载
App.sBitmapCacheUtils.removeCachedBitmap(imagePath);
} }
} }
// 2. 路径已更新:移除旧缓存,加载新图片并更新缓存 // 2. 路径已更新:移除旧缓存
if (!TextUtils.isEmpty(mCurrentCachedPath)) { if (!TextUtils.isEmpty(mCurrentCachedPath)) {
App.sBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath); App.sBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath);
LogUtils.d(TAG, "loadImage: 路径已更新,移除旧缓存 - " + mCurrentCachedPath); LogUtils.d(TAG, "loadImage: 路径已更新,移除旧缓存 - " + mCurrentCachedPath);
} }
// ======================== 路径判断逻辑结束 ======================== // ======================== 路径判断逻辑结束 ========================
// 无缓存/路径更新:走原有逻辑加载图片 // 无缓存/缓存无效/路径更新:重新加载图片
if (!calculateImageAspectRatio(imageFile)) { if (!calculateImageAspectRatio(imageFile)) {
setDefaultTransparentBackground(); setDefaultTransparentBackground();
return; return;
@@ -193,16 +196,24 @@ public class BackgroundView extends RelativeLayout {
} }
// 缓存新图片,并更新当前缓存路径记录 // 缓存新图片,并更新当前缓存路径记录
App.sBitmapCacheUtils.cacheBitmap(imagePath); App.sBitmapCacheUtils.cacheBitmap(imagePath, bitmap);
mCurrentCachedPath = imagePath; mCurrentCachedPath = imagePath;
LogUtils.d(TAG, "loadImage: 加载新图片并更新缓存 - " + imagePath); LogUtils.d(TAG, "loadImage: 加载新图片并更新缓存 - " + imagePath);
mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap)); // 改进直接使用setImageBitmap避免BitmapDrawable包装的引用风险
mIvBackground.setImageBitmap(bitmap);
adjustImageViewSize(); adjustImageViewSize();
LogUtils.d(TAG, "=== loadImage 完成 ==="); LogUtils.d(TAG, "=== loadImage 完成 ===");
} }
// ====================================== 内部工具方法 ====================================== // ====================================== 内部工具方法 ======================================
/**
* 工具方法判断Bitmap是否有效非空且未被回收
*/
private boolean isBitmapValid(Bitmap bitmap) {
return bitmap != null && !bitmap.isRecycled();
}
private boolean calculateImageAspectRatio(File file) { private boolean calculateImageAspectRatio(File file) {
try { try {
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
@@ -231,14 +242,17 @@ public class BackgroundView extends RelativeLayout {
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options); BitmapFactory.decodeFile(file.getAbsolutePath(), options);
int scaleX = options.outWidth / maxWidth; // 改进:更精准的采样率计算(避免过度压缩)
int scaleY = options.outHeight / maxHeight; int scaleX = (int) Math.ceil((float) options.outWidth / maxWidth);
int scaleY = (int) Math.ceil((float) options.outHeight / maxHeight);
int inSampleSize = Math.max(scaleX, scaleY); int inSampleSize = Math.max(scaleX, scaleY);
if (inSampleSize <= 0) inSampleSize = 1; inSampleSize = Math.max(1, inSampleSize); // 确保采样率≥1
options.inJustDecodeBounds = false; options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize; options.inSampleSize = inSampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存
options.inPurgeable = true; // 允许系统在内存紧张时回收
options.inInputShareable = true;
return BitmapFactory.decodeFile(file.getAbsolutePath(), options); return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
} catch (Exception e) { } catch (Exception e) {
LogUtils.e(TAG, "压缩解码失败:" + e.getMessage()); LogUtils.e(TAG, "压缩解码失败:" + e.getMessage());
@@ -254,40 +268,86 @@ public class BackgroundView extends RelativeLayout {
int llWidth = mLlContainer.getWidth(); int llWidth = mLlContainer.getWidth();
int llHeight = mLlContainer.getHeight(); int llHeight = mLlContainer.getHeight();
if (llWidth != 0 && llHeight != 0) { if (llWidth == 0 || llHeight == 0) {
int ivWidth, ivHeight; LogUtils.w(TAG, "adjustImageViewSize: 容器尺寸未初始化,延迟调整");
if (mImageAspectRatio >= 1.0f) { // 延迟调整(容器尺寸未就绪时)
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth); post(new Runnable() {
ivHeight = (int) (ivWidth / mImageAspectRatio); @Override
} else { public void run() {
ivHeight = Math.min((int) (llWidth / mImageAspectRatio), llHeight); adjustImageViewSize();
ivWidth = (int) (ivHeight * mImageAspectRatio); }
} });
return;
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);
} }
int ivWidth, ivHeight;
if (mImageAspectRatio >= 1.0f) {
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
ivHeight = (int) (ivWidth / mImageAspectRatio);
} else {
ivHeight = Math.min((int) (llWidth / mImageAspectRatio), llHeight);
ivWidth = (int) (ivHeight * mImageAspectRatio);
}
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
params.width = ivWidth;
params.height = ivHeight;
mIvBackground.setLayoutParams(params);
mIvBackground.setScaleType(ScaleType.FIT_CENTER);
mIvBackground.setVisibility(View.VISIBLE);
} }
private void setDefaultTransparentBackground() { private void setDefaultTransparentBackground() {
mIvBackground.setImageBitmap(null); // 改进先清空Drawable避免残留已回收Bitmap
mIvBackground.setImageDrawable(null);
mIvBackground.setBackgroundColor(0x00000000); mIvBackground.setBackgroundColor(0x00000000);
mImageAspectRatio = 1.0f; mImageAspectRatio = 1.0f;
// 清空缓存路径记录
mCurrentCachedPath = ""; mCurrentCachedPath = "";
//mIvBackground.setVisibility(View.GONE);
} }
// ====================================== 重写方法 ====================================== // ====================================== 重写方法(核心改进) ======================================
/**
* 重写绘制前强制校验Bitmap有效性防止已回收Bitmap崩溃
*/
@Override
protected void onDraw(Canvas canvas) {
Drawable drawable = mIvBackground.getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (!isBitmapValid(bitmap)) {
LogUtils.e(TAG, "onDraw: 检测到已回收Bitmap清空绘制");
mIvBackground.setImageDrawable(null);
return;
}
}
super.onDraw(canvas);
}
/**
* 重写View从窗口移除时主动释放资源避免内存泄漏和无效引用
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
LogUtils.d(TAG, "onDetachedFromWindow: 释放Bitmap资源");
// 清空ImageView的Drawable释放Bitmap引用
mIvBackground.setImageDrawable(null);
// 清空当前缓存路径,避免后续错误引用
mCurrentCachedPath = "";
// 可选如果当前View是唯一使用者移除全局缓存
// if (!TextUtils.isEmpty(mCurrentCachedPath)) {
// App.sBitmapCacheUtils.removeCachedBitmap(mCurrentCachedPath);
// }
}
/**
* 重写恢复尺寸调整逻辑确保View尺寸变化时正确显示
*/
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
//adjustImageViewSize(); // 尺寸变化时重新调整 adjustImageViewSize(); // 恢复尺寸调整
} }
} }