Bitmap内存计算:
简单地说是分辨率像素点大小 (长宽*像素点占用的字节)
- ARGB_8888 ARGB各占8位所以4个字节
- ARGB_4444 2个字节(不推荐使用,看起来质量太差)
- RGB_565 R 5位,G 6位,B 5位,即16位,2个字节
更详细的讲解:
https://www.cnblogs.com/dasusu/p/9789389.html
getByteCount
返回位图像素点的最小字节数,即内存。
Bitmap.Options
inDensity: 表示这个bitmap的像素密度,根据drawable/mipmap目录。
inTargetDensity: 表示屏幕的像素密度。
getResource().getDisplayMetrics().densityDpi。
Bitmap内存压缩
Bitmap.Options
inJustDecodeBounds: 设置true 读取图片outxxx参数如:outWidth、outHeight。
inPreferedConfig:设置图片解码后的像素格式,如ARGB_888/RGB565
inSampleSize:设置图片解码缩放比,值为2的倍数,1则不进行缩放,如设置4,则加载图片的宽高是原图的1/4,内存大小则是1/16。
对于内存的降低,无论是选择jpg还是png更或者是webp。其实都是毫无意义的。Jpg是属于有损压缩,我们看见的jpg比png文件小,那是因为压缩率高。这都是属于文件存储范畴。对于内存来说,我们加载一张不带alpha通道使用RGB_565格式的png与一张jpg占用的内存大小都是一样的。
对于内存的压缩我们能做的就是缩小图片尺寸与改变像素格式。
网上大多计算压缩比的方式如下:
private Bitmap getimage(String srcPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = 800f;
float ww = 480f;
//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;//be=1表示不缩放
if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//设置缩放比例
//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
return compressImage(bitmap);//压缩好比例大小后再进行质量压缩
}
看见一个有一点点不一样的计算缩放比的方式,旨在记录一下:
/**
*
* @param context
* @param id 图片id
* @param maxWidth 限制最大的宽
* @param maxHeight 限制最大的高
* @param hasAlpha 是否有透明度
* @return
*/
public static Bitmap resizeBitmap(Context context, int id, int maxWidth, int maxHeight, Boolean hasAlpha) {
Resources resources = context.getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, id, options);
int w = options.outWidth;
int h = options.outHeight;
//设置缩放系数
options.inSampleSize = calculateInSampleSize(w, h, maxWidth, maxHeight);
if (!hasAlpha) {
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(resources, id, options);
}
private static int calculateInSampleSize(int w, int h, int maxWidth, int maxHeight) {
int inSampleSize = 1;
if (w > maxWidth && h > maxHeight) {
inSampleSize = 2;
//循环使宽高小于最大的宽高
while (w / inSampleSize > maxWidth && h / inSampleSize > maxHeight) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Bitmap缓存
Bitmap内存管理的官方文档
https://developer.android.google.cn/topic/performance/graphics/manage-memory.html#java
Google的图片浏览demo
https://developer.android.google.cn/samples/DisplayingBitmaps/index.html
内存缓存
首先是创建图片,不管是网络加载还是本地加载的,在这个创建的过程中,系统会为每一个图片申请一块内存,我们使用LruCache缓存这些bitmap,每张图片都会申请内存,会导致内存占用过多,所以就需要使用到bitmap的内存复用,就是当缓存的bitmap被回收的时候,我们使用一个复用池来存放这些bitmap,当有新的图片需要创建并申请内存的时候,我们把复用池中的bitmap内存给他,这样有利的减少内存的申请。这里使用HashSet作为复用池
//线程安全
resuablePool = Collections.synchronizedSet(new HashSet>());
//maxSize 能够缓存的内存最大数
mMemoryCache = new LruCache(memory / 8 * 1024 * 1024) {
/**
*getAllocationByteCount
一般情况下getByteCount()与getAllocationByteCount()是相等的;通过复用Bitmap来解码图片,
如果被复用的Bitmap的内存比待分配内存的Bitmap大,
那么getByteCount()表示新解码图片占用内存的大小(并非实际内存大小,实际大小是复用的那个Bitmap的大小),
getAllocationByteCount()表示被复用Bitmap占用的内存大小。所以可能allocation比bytecount大。
* @param key
* @param value
* @return value占用的内存
*/
@Override
protected int sizeOf(Integer key, Bitmap value) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return value.getAllocationByteCount();
}
return value.getByteCount();
}
/**
* 当bitmap从lruCache中移除时回调
* @param evicted
* @param key
* @param oldValue
* @param newValue
*/
@Override
protected void entryRemoved(boolean evicted, Integer key, Bitmap oldValue, Bitmap newValue) {
if (oldValue.isMutable()) {
resuablePool.add(new WeakReference(oldValue));
} else {
oldValue.recycle();
}
}
};
但是这个内存复用是有条件的:
- 可被复用的Bitmap必须设置inMutable为true;
- Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用;
- Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig;
- Android4.4(API 19)之后被复用的Bitmap的内存必须大于等于需要申请内存的Bitmap的内存;
磁盘缓存
磁盘缓存使用DiskLruCache
https://github.com/JakeWharton/DiskLruCache
public void putBitmap2Disk(String key, Bitmap bitmap) {
DiskLruCache.Snapshot snapshot = null;
OutputStream os = null;
try {
snapshot = diskLruCache.get(key);
if (snapshot == null) {
DiskLruCache.Editor edit = diskLruCache.edit(key);
if (edit != null) {
os = edit.newOutputStream(0);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);
edit.commit();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (snapshot != null) {
snapshot.close();
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
当内存没有图片,就要从磁盘获取,这个时候获取到的新图片就应该使用内存复用了
/**
*
* @param key
* @param reusable 复用池里的图片
* @return
*/
public Bitmap getBitmapFromDisk(String key,Bitmap reusable) {
DiskLruCache.Snapshot snapshot = null;
Bitmap bitmap = null;
try {
snapshot = diskLruCache.get(key);
if (snapshot == null) {
return null;
}
//获得文件输入流 读取bitmap
//is的close snapshot.close() 已经帮助close了
InputStream is = snapshot.getInputStream(0);
//内存复用
options.inMutable = true;
options.inBitmap = reusable;
bitmap = BitmapFactory.decodeStream(is,null,options);
if (bitmap != null){
mMemoryCache.put(key,bitmap);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (snapshot != null) {
snapshot.close();
}
}
return bitmap;
}
/**
* 从复用池里获取可用于内存复用的bitmap
* @param w
* @param h
* @param inSampleSize
* @return
*/
public Bitmap getReusable(int w, int h, int inSampleSize) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
return null;
}
Iterator> iterator = resuablePool.iterator();
Bitmap reusable = null;
//迭代查找符合复用条件的bitmap
while (iterator.hasNext()) {
Bitmap bitmap = iterator.next().get();
if (bitmap != null) {
if (checkInBitmap(bitmap, w, h, inSampleSize)) {
reusable = bitmap;
//移除复用池
iterator.remove();
break;
}
} else {
iterator.remove();
}
}
return reusable;
}
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;
}
//如果缩放系数大于1 获得缩放后的宽高
if (inSampleSize > 1) {
w /= inSampleSize;
h /= inSampleSize;
}
int byteCount = w * h * getPixeCount(bitmap.getConfig());
return byteCount <= bitmap.getAllocationByteCount();
}
int getPixeCount(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
}
return 2;
}