本文使用LruCache、DiskLruCache进行Bitmap的内存管理。LruCache是Android自带的,DiskLruCache是Jake Wharton大神的作品:https://github.com/JakeWharton/DiskLruCache
做Bitmap内存管理,首先要用到BitmapFactory.Options,它是BitmapFactory类的静态内部类,主要使用Options相关参数对Bitmap进行设置。如Options.inBitmap用于设置加载图片时进行内存复用,如下图1、2所示:
Bitmap内存管理的项目使用见以下代码:
activity_main.xml :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FrameLayout>
item.xml :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="80dp" />
FrameLayout>
因为涉及到DiskLruCache的使用,因此需要去DiskLruCache代码库将源码拷入项目中。
ImageResize.java :
package com.example.administrator.lsn7_demo.utils;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
/**
* Bitmap加载优化
*/
public class ImageResize {
/**
* 缩放Bitmap
*
* @param context 上下文
* @param imgId 图片的资源id
* @param maxW 允许的最大宽度
* @param maxH 允许的最大高度
* @param hasAlpha 图片是否有Alpha通道
* @param reusable 复用的Bitmap
* @return 返回缩放后的Bitmap
*/
public static Bitmap resizeBitmap(Context context, int imgId, int maxW, int maxH, boolean hasAlpha, Bitmap reusable) {
Resources resources = context.getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
//不加载到内存,方便计算图片宽高,从而得到inSampleSize
options.inJustDecodeBounds = true;
//预加载
BitmapFactory.decodeResource(resources, imgId, options);
//拿到图片宽高
int w = options.outWidth;
int h = options.outHeight;
//设置采样率(根据宽、高进行缩放,设置缩放系数)
options.inSampleSize = calcuteInSampleSize(w, h, maxW, maxH);
if (!hasAlpha) {
/*
图片质量压缩,即解码率压缩。
RGB_565占2个字节,ARGB_8888占4个字节
*/
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
//加载进内存
options.inJustDecodeBounds = false;
//可变的bitmap
options.inMutable = true;
//设置成能复用,可实现第二张图片复用第一张图片的内存
options.inBitmap = reusable;
return BitmapFactory.decodeResource(resources, imgId, options);
}
/**
* 计算缩放系数
*
* @param w 图片宽
* @param h 图片高
* @param maxW 允许的最大宽度
* @param maxH 允许的最大高度
* @return 缩放的系数
*/
private static int calcuteInSampleSize(int w, int h, int maxW, int maxH) {
//采样率
int inSampleSize = 1;
if (w > maxW && h > maxH) {
inSampleSize = 2;
//循环 使宽、高小于 最大的宽、高
while (w / inSampleSize > maxW && h / inSampleSize > maxH) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
ImageCache.java :
package com.example.administrator.lsn7_demo.utils;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.LruCache;
import com.example.administrator.lsn7_demo.BuildConfig;
import com.example.administrator.lsn7_demo.disk.DiskLruCache;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Bitmap内存管理(内存、复用池、磁盘、网络)
*/
public class ImageCache {
private static volatile ImageCache instance;
private Context context;
private BitmapFactory.Options options = new BitmapFactory.Options();
/**
* 内存
*/
private LruCache<String, Bitmap> memoryLruCache;
/**
* 磁盘
*/
private DiskLruCache diskLruCache;
/**
* 复用沲
*/
private static Set<WeakReference<Bitmap>> reuseablePool;
/**
* 引用队列
*/
private ReferenceQueue<Bitmap> referenceQueue;
private Thread clearRefQueueThread;
private boolean shutDown;
public static ImageCache getInstance() {
if (null == instance) {
synchronized (ImageCache.class) {
if (null == instance) {
instance = new ImageCache();
}
}
}
return instance;
}
/**
* 初始化方法,dir是用来存放图片文件的路径
*/
public void init(Context context, String dir) {
this.context = context.getApplicationContext();
//复用池
reuseablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取程序最大可用内存 单位是M
int memoryClass = am.getMemoryClass();
//参数表示能够缓存的内存最大值 单位是byte
memoryLruCache = new LruCache<String, Bitmap>(memoryClass / 8 * 1024 * 1024) {
/**
* @return value占用的内存大小
*/
@Override
protected int sizeOf(String key, Bitmap value) {
//19之前 必需同等大小,才能复用 inSampleSize=1
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
/**
* 当lru满了,bitmap从lru中移除对象时,会回调
*/
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//如果是设置成能复用的内存块,拉到java层来管理
if (oldValue.isMutable()) {
//3.0以下 Bitmap native
//3.0以后---8.0之前 java
//8。0开始 native
//把这些图片放到一个复用沲中
reuseablePool.add(new WeakReference<Bitmap>(oldValue, referenceQueue));
} else {
//oldValue就是移出来的对象
oldValue.recycle();
}
}
};
try {
//第三个参数valueCount:表示一个key对应valueCount个文件
diskLruCache = DiskLruCache.open(new File(dir), BuildConfig.VERSION_CODE, 1, 10 * 1024 * 1024);
} catch (Exception e) {
e.printStackTrace();
}
getReferenceQueue();
}
/**
* 获得引用队列
*/
private ReferenceQueue<Bitmap> getReferenceQueue() {
if (null == referenceQueue) {
//当弱引用需要被回收的时候,会进到这个队列中
referenceQueue = new ReferenceQueue<Bitmap>();
//单开一个线程,去扫描引用队列中GC扫到的内容,交到native层去释放
clearRefQueueThread = new Thread(new Runnable() {
@Override
public void run() {
while (!shutDown) {
try {
/*
remove是阻塞式的,
Removes the next reference object in this queue, blocking until one becomes available
*/
Reference<Bitmap> reference = (Reference<Bitmap>) referenceQueue.remove();
Bitmap bitmap = reference.get();
if (null != bitmap && !bitmap.isRecycled()) {
bitmap.recycle();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
clearRefQueueThread.start();
}
return referenceQueue;
}
public void putBitmapToMemeory(String key, Bitmap bitmap) {
memoryLruCache.put(key, bitmap);
}
public Bitmap getBitmapFromMemory(String key) {
return memoryLruCache.get(key);
}
public void clearmemoryLruCache() {
memoryLruCache.evictAll();
}
/**
* 获取复用池中的内容
* @param w 图片宽
* @param h 图片高
* @param inSampleSize 采样率
* @return 复用图片
*/
public Bitmap getReuseable(int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return null;
}
Bitmap reuseable = null;
Iterator<WeakReference<Bitmap>> iterator = reuseablePool.iterator();
while (iterator.hasNext()) {
Bitmap bitmap = iterator.next().get();
if (null != bitmap) {
//可以复用
if (checkInBitmap(bitmap, w, h, inSampleSize)) {
reuseable = bitmap;
iterator.remove();
break;
} else {
iterator.remove();
}
}
}
return reuseable;
}
/**
* @param bitmap 图片
* @param w 图片宽
* @param h 图片高
* @param inSampleSize 采样率
* @return 图片是否可复用
*/
private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
}
if (inSampleSize >= 1) {
w /= inSampleSize;
h /= inSampleSize;
}
int byteCount = w * h * getPixelsCount(bitmap.getConfig());
return byteCount <= bitmap.getAllocationByteCount();
}
/**
* 图片质量压缩,即解码率压缩。
* RGB_565占2个字节,ARGB_8888占4个字节
*
* @param config 解码率
* @return 占用字节大小
*/
private int getPixelsCount(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
}
return 2;
}
/**
* 加入磁盘缓存
*
* @param key 要存入磁盘中的索引
* @param bitmap 要存入的图片
*/
public void putBitMapToDisk(String key, Bitmap bitmap) {
DiskLruCache.Snapshot snapshot = null;
OutputStream os = null;
try {
snapshot = diskLruCache.get(key);
//如果缓存中已经有这个文件 不理他
if (null == snapshot) {
//如果没有这个文件,就生成这个文件
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (null != editor) {
os = editor.newOutputStream(0);
//图片压缩格式为jpeg,50表示中度压缩(0-100之间)
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
editor.commit();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != snapshot) {
snapshot.close();
}
if (null != os) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 从磁盘缓存中取
*
* @param key 磁盘存储索引
* @param reuseable 复用图片
* @return 磁盘中取出的图片
*/
public Bitmap getBitmapFromDisk(String key, Bitmap reuseable) {
DiskLruCache.Snapshot snapshot = null;
Bitmap bitmap = null;
try {
snapshot = diskLruCache.get(key);
if (null == snapshot) {
return null;
}
//获取文件输入流,读取bitmap
InputStream is = snapshot.getInputStream(0);
//解码图片,写入,复用内存
options.inMutable = true;
options.inBitmap = reuseable;
bitmap = BitmapFactory.decodeStream(is, null, options);
if (null != bitmap) {
//加入内存缓存
memoryLruCache.put(key, bitmap);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != snapshot) {
snapshot.close();
}
}
return bitmap;
}
}
MainActivity.java :
package com.example.administrator.lsn7_demo;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ListView;
import com.example.administrator.lsn7_demo.utils.ImageCache;
/**
* Bitmap内存管理(内存、复用池、磁盘、网络)
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageCache.getInstance().init(this, Environment.getExternalStorageDirectory() + "/dn");
ListView listView = findViewById(R.id.listView);
listView.setAdapter(new MyAdapter(this));
//假设是从网络上来的
/*BitmapFactory.Options options = new BitmapFactory.Options();
//如果要复用,需要设计成可变的
options.inMutable = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.wyz_p, options);
for (int i = 0; i < 100; i++) {
options.inBitmap = bitmap;
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.wyz_p, options);
}*/
}
void i(Bitmap bitmap) {
Log.i("jett", "图片" + bitmap.getWidth() + "x" + bitmap.getHeight() + " 内存大小是:" + bitmap.getByteCount());
}
}
MyAdapter.java :
package com.example.administrator.lsn7_demo;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.example.administrator.lsn7_demo.utils.ImageCache;
import com.example.administrator.lsn7_demo.utils.ImageResize;
public class MyAdapter extends BaseAdapter {
private Context context;
public MyAdapter(Context context) {
this.context = context;
}
@Override
public int getCount() {
return 50;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (null == convertView) {
convertView = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//图片优化方式一
//Bitmap bitmap = ImageResize.resizeBitmap(context, R.mipmap.wyz_p, 80, 80, false);
//图片优化方式二
Bitmap bitmap = ImageCache.getInstance().getBitmapFromMemory(String.valueOf(position));
if (null == bitmap) {
//如果内存没数据,就去复用池找。reuseable是能复用的内存
Bitmap reuseable = ImageCache.getInstance().getReuseable(60, 60, 1);
//从磁盘找
bitmap = ImageCache.getInstance().getBitmapFromDisk(String.valueOf(position), reuseable);
//如果磁盘中也没缓存,就从网络下载
if (null == bitmap) {
bitmap = ImageResize.resizeBitmap(context, R.mipmap.wyz_p, 80, 80, false, reuseable);
//然后将下载的Bitmap分别放入内存、磁盘
ImageCache.getInstance().putBitmapToMemeory(String.valueOf(position), bitmap);
ImageCache.getInstance().putBitMapToDisk(String.valueOf(position), bitmap);
Log.i("jett", "从网络加载了数据");
} else {
Log.i("jett", "从磁盘中加载了数据");
}
} else {
Log.i("jett", "从内存中加载了数据");
}
holder.iv.setImageBitmap(bitmap);
return convertView;
}
static class ViewHolder {
ImageView iv;
ViewHolder(View view) {
iv = view.findViewById(R.id.iv);
}
}
}
最后,别忘了添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
项目地址:
https://github.com/keyyoo/Android_Advanced_Series/tree/master/%E4%B8%93%E9%A2%983-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/Lsn7_Bitmap%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86