在Android开发过程中,经常会用到图片加载的地方,现在的图片基本都是比较清晰的了,动不动就是10M以上,直接加载到系统中去很容易会造成程序OOM。
所以,在Android 的内存优化这一块对图片的优化就显得特别重要。
首先介绍一下图片的几种常见的图片压缩格式
- ALPHA_8 : 表示8位Alpha位图,即透明度占8个位,一个像素点占用1个字节,它没有颜色,只有透明度
- ARGB_4444 : 表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节
- ARGB_8888 : 表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节。
- RGB_565 : 表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
其实,图片压缩可以简单的认为是:降低图片的高度和宽度、或者降低每个像素点所占用的内存大小
Bitmap到底占用多大内存?内存计算公式为 = 图片长度 * 图片宽度 * 每个像素占用的内存。在Android中,Bitmap有四种像素类型:ARGB_8888、ARGB_4444、ARGB_565、ALPHA_8,他们每个像素占用的字节数分别为4、2、2、1。因此,一个20001000的ARGB_8888类型的Bitmap占用的内存为20001000*4 = 8000000B = 8MB
下面总结一下常用的图片优化方法:
- 采用三级图片缓存技术
1,网络缓存
2,内存缓存
3,磁盘缓存
其实Android系统以为我们提供了一个LruCache一个基于最近最少使用算法的工具类,内部是基于LinkedHashMap
int maxMemory = (int) (Runtime.getRuntime().totalMemory()/1024);
// LruCache缓存的大小,一般为当前进程可用容量的1/8
int cacheSize = maxMemory/8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
// 重写sizeOf方法,计算出要缓存的每张图片的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
// 添加到缓存
mMemoryCache.put(key,bitmap);
// 获取缓存对象
bitmap = mMemoryCache.get(key);
// 移除缓存对象
mMemoryCache.remove(key);
// 清理内存缓存
mMemoryCache.evictAll();
磁盘缓存一般是基于DiskLruCache来实现
//指定的是数据的缓存地址
File cacheDir = context.getCacheDir();
//最多可以缓存多少字节的数据
long diskCacheSize = 1024 * 1024 * 30;
//指定当前应用程序的版本号
int appVersion = DiskLruUtils.getAppVersion(context);
//指定同一个key可以对应多少个缓存文件
int valueCount = 1;
try {
mDiskCache = DiskLruCache.open(cacheDir, appVersion
, valueCount, diskCacheSize);
} catch (Exception ex) {
}
// 添加图片到磁盘缓存
private void addBitmapToDiskCache(String key, byte[] value) {
OutputStream out = null;
try {
DiskLruCache.Editor editor = mDiskCache.edit(key);
if (editor != null) {
out = editor.newOutputStream(0);
if (value != null && value.length > 0) {
out.write(value);
out.flush();
editor.commit();
} else {
editor.abort();
}
}
mDiskCache.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
DiskLruUtils.closeQuietly(out);
}
}
- 图片压缩
- 等比例缩放
// 比如调节为2,宽高会为原来的1/2,内存变回原来的1/4
private static int calculateInSampleSize(BitmapFactory.Options options,
, int reqWidth, int reqHeight)
{
final int height = options.outHeight;
final int width = options.outWidth;
// inSampleSize 只能是整数,且其值只能为2的幂次方
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
BitmapFactory.Options options = new Options();
// 方法就不会生成Bitmap对象,而仅仅是读取该图片的尺寸和类型信息
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(rs, R.drawable.a2,options);
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
options.inSampleSize = calculateInSampleSize(options, 200, 200);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(rs, R.drawable.a2,options);
iv.setImageBitmap(bitmap);
- 指定图片长宽
public Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
// 获取这个图片的宽和高
float width = bgimage.getWidth();
float height = bgimage.getHeight();
// 创建操作图片用的matrix对象
Matrix matrix = new Matrix();
// 计算宽高缩放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 缩放图片动作
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0
, (int) width, (int) height, matrix, true);
return bitmap;
}
- 质量压缩
/* 质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的
** 图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
** 我们可以看到有个参数:quality,可以调节你压缩的比例,
** 但是还要注意一点就是,质量压缩堆png格式这种图片没有作用,因为png是无损压缩。
*/
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mSrcSize = bm.getByteCount() + "byte";
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
byte[] bytes = bos.toByteArray();
mSrcBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
当然,还有一些开源的图片加载框架,都是很不多的选择,使用框架最大的好处就是框架本身就已经为性能作出了很好的优化,开发人员直接使用就可以了。后面的一些博客,会重点分享一些框架原理之类的干货。