第7颗蛀牙编号Android

我们知道产生OOM的原因是内存的使用量持续增长,直到超过了内存使用上限。每部手机对应用程序的内存上限在出厂时就已经是固定的了,所以我们需要尽量控制内存的使用量不要超过这个内存阀值,才能避免OOM。具体的做法就是去减少对象对内存的消耗。而今天要讨论的主要是减少图片对内存的消耗,因为图片消耗内存比较大,大多数应用程序都会有大量图片的加载,产生OOM往往也会发生在加载大量图片的时候。所以对图片占用内存的管理优化是很有必要的。但要记住的一点是,在内存的持续增长中,图片确实是其中一个很重量级的角色,但不是唯一的诱因,比如不好的代码写法导致产生大量对象或者内存泄露。这里说的只是降低图片对内存的消耗,谈不上解决OOM,因为图片一多,还是会产生OOM,只是推迟了问题产生的时间而已。

一、从网上获取到的图片的大小和图片占用的内存是不一样的

看到大小是23.44KB,,尺寸即宽高是240px x 240px。那当程序解码这张图片时申请的内存大小也是23.44KB吗?这里要注意的是,我们所要加载的网络图片的大小并不是Bitmap对象占用的内存大小!图片在内存中的大小的计算方式是:图片长(px) x 图片宽(px) x 单位像素占用的字节数。这张图片网页上显示的大小是24007字节,23.44KB,但是读到内存中(使用ARGB_8888颜色类型),他占用的内存大小是240x240x4=230400字节,225KB。

二、怎样查看或获取图片占用的内存大小?

1.在一、中用到的通过内存大小的计算方式直接计算得出:

图片的长 x图片的宽 x单位像素占用的字节数

Android中不同的图片格式,他的单位像素占用的字节数不同。图片格式总共有四种:

Bitmap.Config = ALPHA_8 一个像素占1个字节

Bitmap.Config = ARGB_4444 一个像素占2个字节

Bitmap.Config = ARGB_8888 一个像素占4个字节(默认)

Bitmap.Config = ARGB_565 一个像素占2个字节

所以一个尺寸为400px x 800px的图片,如果采用ARGB_8888的颜色类型,那么计算得到的内存占用量即为 400x800x4 = 640000字节,即625kb。两张这样的图片的内存开销就已经超过1M了。

2、通过以下代码获取:

@TargetApi(Build.VERSION_CODES.KITKAT)public int getBitmapSize(Bitmap bitmap){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){// API 19 Android 4.4return bitmap.getAllocationByteCount();}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){// API 12 Android 3.1return bitmap.getByteCount();}return bitmap.getRowBytes() * bitmap.getHeight();}Android已经为我们提供了返回图片占用内存大小的方法了,调用该方法和我们通过计算得到的结果是一致的。

三、压缩图片如果原图的尺寸比手机上要显示的图片尺寸要大,那么可以通过压缩图片来减少内存浪费。

我们要加载一张240×240的图片,如果设置了屏幕上显示120px*120px像素大小的ImageView,那么就没有必要将240px*240px的图片加载到内存中,造成不必要的内存浪费。把BitmapFactory.Options的inJustDecodeBounds设置为true,再去解析图片,就可以在decode的时候避免内存分配,他会返回一个null的Bitmap,虽然返回的Bitmap是null,但是他返回了原图的宽和高和类型。所以在构建Bitmap之前可以优先读取到图片的尺寸和类型,然后通过原图宽高和目标图的宽高计算出最终的缩放比例赋值给option.inSampleSize,再把option.inJustDecodeBounds的值设置为false,最后重新解析一次Bitmap,就可以达到压缩图片,节省内存的目的。

以解析网络流的图片为例:

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,需要重新打开一次inputStreamconnection.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.4return bitmap.getAllocationByteCount();}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1){// API 12 Android 3.1return bitmap.getByteCount();}return bitmap.getRowBytes() * bitmap.getHeight();}

图片压缩前加载图片后打印的内存占用量:

人生的成功不过是在紧要处多一份坚持,

第7颗蛀牙编号Android

相关文章:

你感兴趣的文章:

标签云: