- 一、Bitmap 复用池
- 二、弱引用 Bitmap 内存释放
- 三、从 Bitmap 复用池中获取对应可以被复用的 Bitmap 对象
- 1、Android 2.3.3(API 级别 10)及以下的版本
- 2、Android 4.4(API 级别 19)以下的版本
- 2、在 Android 4.4(API 级别 19)及以上的版本
- 四、LruCache 内存缓存、内存复用工具类
- 1、工具类
- 2、工具类测试
- 3、执行结果
- 五、源码及资源下载
在上一篇博客 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 缓存策略 | LruCache 内存缓存 | LruCache 常用操作 | 工具类代码 ) 中 , 使用 LruCache 缓存 Bitmap 数据到内存中 , 设置其最大缓存为应用可用内存的 1/8 , 将解码后的 Bitmap 对象缓存到 LruCache 中 , 避免重复使用该 Bitmap 对象时重复解码加载图片 ;
一、Bitmap 复用池1 . Bitmap 复用池 : 加载图片时 , 使用 inBitmap 复用选项 , 需要获取图片时 , 优先从 Bitmap 复用池中查找复用已存在的 Bitmap 对象 ; 假如 Bitmap 对象长时间不使用 , 就会从 LruCache 内存缓存中移除 , 此时放入到 Bitmap 复用池中 ;
2 . 弱引用 : 这里使用弱引用保存该 Bitmap , 每次 GC 时都会回收没有被引用的 Bitmap , 需要创建一个线程安全的 HashSet , 其中的元素是 Bitmap 弱引用 ;
Set bitmapReusePool;
二、弱引用 Bitmap 内存释放
有一点特别注意 , Java 中的弱引用 , 在 GC 时会回收没有使用到的内存 ; Bitmap 内存如果在 Java 层 , 可以将该内存回收 , 但是如果 Bitmap 内存在 Native 层 , 必须调用 Bitmap 对象的 recycle 方法 , 才能将内存释放 ;
1 . Bitmap 内存放置策略 :
- 3.0 以下系统中 , Bitmap 内存在 Native 层
- 3.0 以上系统中 , Bitmap 内存在 Java 层
- 8.0 及以上的系统中 , Bitmap 内存在 Native 层
为了适配所有手机 , 所有版本 , 不管 GC 是否自动释放 Bitmap 内存 , 在弱引用对象被回收时 , 必须手动调用一下 Bitmap 对象的 recycle 方法 ;
2 . 兼容弱引用释放方法 : 使用引用队列 ReferenceQueue 监控该弱引用 Bitmap 的 Set 集合元素 , 当有 Bitmap 被回收后 , 就会将其放入 ReferenceQueue 中 , 此时开启一个线程 , 不断从 ReferenceQueue 调用 remove 方法获取被释放的内存对象 , 如果获取到了非空内容 , 说明有一个 Bitmap 弱引用对象被释放了 , 拿到该对象引用 Reference 后 , 获取其对应的 Bitmap 对象 , 手动调用 Bitmap 对象的 recycle 方法 , 即可完成对应操作 ;
代码示例 :
/**
* Bitmap 复用池
* 使用 inBitmap 复用选项
* 需要获取图片时 , 优先从 Bitmap 复用池中查找
* 这里使用弱引用保存该 Bitmap , 每次 GC 时都会回收该 Bitmap
* 创建一个线程安全的 HashSet , 其中的元素是 Bitmap 弱引用
*
* 该 Bitmap 复用池的作用是 , 假如 Bitmap 对象长时间不使用 , 就会从内存缓存中移除
*
* 因此这里需要处理 Bitmap 内存在 Native 层的情况 , 监控到 Java 层的弱引用被释放了
* 需要调用 Bitmap 对象的 recycle 方法 , 释放 Native 层的内存
*
* 需要使用引用队列监控弱引用的释放情况
*/
Set bitmapReusePool;
/**
* 引用队列 , 用于监控 Set bitmapReusePool 的内存是否被回收
* 需要维护一个线程 , 不断尝试从该引用队列中获取引用
*
*/
private ReferenceQueue referenceQueue;
/**
* 监控 Set bitmapReusePool 的内存是否被回收 ,
* 调用 ReferenceQueue referenceQueue 的 remove 方法 ,
* 查看是否存在被回收的弱引用 , 如果存在 , 直接回收该弱引用对应的 Bitmap 对象
*/
private Thread referenceQueueMonitorThread;
/**
* 是否持续监控引用队列 ReferenceQueue
*/
private boolean isMonitorReferenceQueue = true;
/**
* 初始化引用队列
*/
private void initBitmapReusePool(){
// 创建一个线程安全的 HashSet , 其中的元素是 Bitmap 弱引用
bitmapReusePool = Collections.synchronizedSet(new HashSet());
// 引用队列 , 当弱引用被 GC 扫描后 , 需要回收 , 会将该弱引用放入队列
// 一直不断的尝试从该引用队列中获取数据 , 如果获取到数据 , 就要回收该对象
referenceQueue = new ReferenceQueue();
// 定义监控线程
referenceQueueMonitorThread = new Thread(){
@Override
public void run() {
while (isMonitorReferenceQueue){
try {
Reference reference = (Reference) referenceQueue.remove();
Bitmap bitmap = reference.get();
// 不为空 , 且没有被回收 , 回收 Bitmap 内存
if(bitmap != null && !bitmap.isRecycled()){
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 启动引用队列监控线程
referenceQueueMonitorThread.start();
}
三、从 Bitmap 复用池中获取对应可以被复用的 Bitmap 对象
根据不同系统版本进行不同处理 :
1、Android 2.3.3(API 级别 10)及以下的版本Android 2.3.3(API 级别 10)及以下的版本 : 使用 Bitmap 对象的 recycle 方法回收内存 ;
// Android 2.3.3(API 级别 10)及以下的版本中 , 使用 Bitmap 对象的 recycle 方法回收内存
if (Build.VERSION.SDK_INT 1){
width = width / inSampleSize ;
height = height / inSampleSize;
}
// 计算内存占用 , 默认 ARGB_8888 格式
int byteInMemory = width * height * 4;;
if(bitmap.getConfig() == Bitmap.Config.ARGB_8888){
// 此时每个像素占 4 字节
byteInMemory = width * height * 4;
}else if(bitmap.getConfig() == Bitmap.Config.RGB_565){
// 此时每个像素占 2 字节
byteInMemory = width * height * 2;
}
// 如果解码后的图片内存小于等于被复用的内存大小 , 可以复用
if(byteInMemory = Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
/**
* 从 LruCache 缓存移除 Bitmap 时会回调该方法
* @param evicted
* @param key
* @param oldValue
* @param newValue
*/
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue,
Bitmap newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
/*
如果从 LruCache 内存缓存中移除的 Bitmap 是可变的
才能被复用 , 否则只能回收该 Bitmap 对象
Bitmap 回收策略 :
3.0 以下系统中 , Bitmap 内存在 Native 层
3.0 以上系统中 , Bitmap 内存在 Java 层
8.0 及以上的系统中 , Bitmap 内存在 Native 层
因此这里需要处理 Bitmap 内存在 Native 层的情况 , 监控到 Java 层的弱引用被释放了
需要调用 Bitmap 对象的 recycle 方法 , 释放 Native 层的内存
*/
if(oldValue.isMutable()){ // 可以被复用
// 将其放入弱引用中 , 每次 GC 启动后 , 如果该弱引用没有被使用 , 都会被回收
bitmapReusePool.add(new WeakReference(oldValue, referenceQueue));
}else{ // 不可被复用 , 直接回收
oldValue.recycle();
}
}
};
}
private void initBitmapReusePool(){
// 创建一个线程安全的 HashSet , 其中的元素是 Bitmap 弱引用
bitmapReusePool = Collections.synchronizedSet(new HashSet());
// 引用队列 , 当弱引用被 GC 扫描后 , 需要回收 , 会将该弱引用放入队列
// 一直不断的尝试从该引用队列中获取数据 , 如果获取到数据 , 就要回收该对象
referenceQueue = new ReferenceQueue();
// 定义监控线程
referenceQueueMonitorThread = new Thread(){
@Override
public void run() {
while (isMonitorReferenceQueue){
try {
Reference reference = (Reference) referenceQueue.remove();
Bitmap bitmap = reference.get();
// 不为空 , 且没有被回收 , 回收 Bitmap 内存
if(bitmap != null && !bitmap.isRecycled()){
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 启动引用队列监控线程
referenceQueueMonitorThread.start();
}
/**
* 获取一个可以被复用的 Bitmap 对象
*
* 与 BitmapFactory 配合使用 :
*
* Android 4.4 以后的 Bitmap 复用情况 :
* 在 KITKAT ( Android 4.4 , 19 平台 ) 以后的代码中 ,
* 只要被解码生成的 Bitmap 对象的字节大小 ( 缩放后的 )
* 小于等于 inBitmap 的字节大小 , 就可以复用成功 ;
*
* Android 4.4 之前的 Bitmap 复用情况 : ( 比较苛刻 )
* 在 KITKAT 之前的代码中 , 被解码的图像必须是
* - JPEG 或 PNG 格式 ,
* - 并且 图像大小必须是相等的 ,
* - inssampleSize 设置为 1 ,
* 才能复用成功 ;
* 另外被复用的图像的 像素格式 Config ( 如 RGB_565 ) 会覆盖设置的 inPreferredConfig 参数
*
* @param width
* @param height
* @param inSampleSize
* @return
*/
public Bitmap getReuseBitmap(int width,int height,int inSampleSize){
// Android 2.3.3(API 级别 10)及以下的版本中 , 使用 Bitmap 对象的 recycle 方法回收内存
if (Build.VERSION.SDK_INT 1){
width = width / inSampleSize ;
height = height / inSampleSize;
}
// 计算内存占用 , 默认 ARGB_8888 格式
int byteInMemory = width * height * 4;;
if(bitmap.getConfig() == Bitmap.Config.ARGB_8888){
// 此时每个像素占 4 字节
byteInMemory = width * height * 4;
}else if(bitmap.getConfig() == Bitmap.Config.RGB_565){
// 此时每个像素占 2 字节
byteInMemory = width * height * 2;
}
// 如果解码后的图片内存小于等于被复用的内存大小 , 可以复用
if(byteInMemory
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?