我们知道产生OOM的原因是内存的使用量持续增长,直到超过了内存使用上限。每部手机对应用程序的内存上限在出厂时就已经是固定的了,所以我们需要尽量控制内存的使用量不要超过这个内存阀值,才能避免OOM。具体的做法就是去减少对象对内存的消耗。而今天要讨论的主要是减少图片对内存的消耗,因为图片消耗内存比较大,大多数应用程序都会有大量图片的加载,产生OOM往往也会发生在加载大量图片的时候。所以对图片占用内存的管理优化是很有必要的。但要记住的一点是,在内存的持续增长中,图片确实是其中一个很重量级的角色,但不是唯一的诱因,比如不好的代码写法导致产生大量对象或者内存泄露。这里说的只是降低图片对内存的消耗,谈不上解决OOM,因为图片一多,还是会产生OOM,只是推迟了问题产生的时间而已。
一、从网上获取到的图片的大小和图片占用的内存是不一样的
比如在我们图片列表里要加载这样一张图片:https://img-my.csdn.net/uploads/201407/26/1406383299_1976.jpg 打开链接查看图片属性
看到大小是23.44KB,尺寸即宽高是240px x 240px。那当程序解码这张图片时申请的内存大小也是23.44KB吗?这里要注意的是,我们所要加载的网络图片的大小并不是Bitmap对象占用的内存大小!图片在内存中的大小的计算方式是:图片长(px) x 图片宽(px) x 单位像素占用的字节数。这张图片网页上显示的大小是24007字节,23.44KB,但是读到内存中(使用ARGB_8888颜色类型),他占用的内存大小是240x240x4=230400字节,225KB。
二、怎样查看或获取图片占用的内存大小?
图片的长 x图片的宽 x 单位像素占用的字节数
Android中不同的图片格式,他的单位像素占用的字节数不同。图片格式总共有四种:
Bitmap.Config = ALPHA_8 一个像素占1个字节
Bitmap.Config = ARGB_4444 一个像素占2个字节
Bitmap.Config = ARGB_8888 一个像素占4个字节(默认)
Bitmap.Config = ARGB_565 一个像素占2个字节
@TargetApi(Build.VERSION_CODES.KITKAT)
public int getBitmapSize(Bitmap bitmap){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){// API 19 Android 4.4
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){// API 12 Android 3.1
return bitmap.getByteCount();
}
return bitmap.getRowBytes() * bitmap.getHeight();
}
Android已经为我们提供了返回图片占用内存大小的方法了,调用该方法和我们通过计算得到的结果是一致的。
如果原图的尺寸比手机上要显示的图片尺寸要大,那么可以通过压缩图片来减少内存浪费。
private Bitmap loadBitmap(String imageUrl) {
HttpURLConnection connection = null;
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
connection = (HttpURLConnection) url.openConnection();
BitmapFactory.Options options = new BitmapFactory.Options();
//防止第一次解析时对图片的内存分配
options.inJustDecodeBounds = true;
//第一次解析
BitmapFactory.decodeStream(connection.getInputStream(), null, options);
//decodeStream不能重复解析同一个网络流的inputStream,需要重新打开一次inputStream
connection.disconnect();
connection = (HttpURLConnection) url.openConnection();
//获取图片的缩放比例
options.inSampleSize = calculateInSampleSize(options,100, 100);
//将options.inJustDecodeBounds的值设回false,为了第二次解析能够正常分配内存
options.inJustDecodeBounds = false;
//第二次解析,此时图片的内存会按照压缩后的图片宽高去分配
bitmap = BitmapFactory.decodeStream(connection.getInputStream(), null, options);
L.d("loadBitmap url = "+imageUrl);
L.d("该图占用内存 = "+getBitmapSize(bitmap)+"bytes, "+(getBitmapSize(bitmap)/1024)+"KB"+", width = "+bitmap.getWidth()+", height = "+bitmap.getHeight()+", config = "+bitmap.getConfig().name());
return bitmap;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (connection != null){
connection.disconnect();
}
}
return bitmap;
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int width = options.outWidth;//原图的宽
int height = options.outHeight;//原图的高
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth){
//计算出原图宽高和目标宽高的比例——缩小几倍
int heightRadio = Math.round((float)height / (float)reqHeight);
int widthRadio = Math.round((float)width / (float)reqWidth);
//选择宽和高中最小的比例作为inSampleSize的值,可以保证最终图片的宽和高一定都会大于等于目标的宽和高
inSampleSize = heightRadio < widthRadio ? heightRadio : widthRadio;
}
return inSampleSize;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public int getBitmapSize(Bitmap bitmap){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){// API 19 Android 4.4
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){// API 12 Android 3.1
return bitmap.getByteCount();
}
return bitmap.getRowBytes() * bitmap.getHeight();
}
参考: