在Android的开发中,咱们常常回去处理一些图片相关的问题,好比当加载图片到内存中产生的OOM(OutOfMemory)异常、图片加载到内存中占多大内存的问题、jpg png两种常见的图片的原理及区别。 git
图片加载到内存所占内存大小的问题github
在讲OOM异常前须要对图片的加载有所了解,因此在这里就先介绍图片加载的问题。 缓存
图片加载到内存中的大小,不是直接由图片的存储大小来决定的。好比一个10k大小的png格式的图片加载到内存可能就不止10k了。那应该怎么计算呢?网络
图片加载到内存中的大小=图片的宽×图片的高×该图片一个像素所占的位数/8 框架
举个例子:一个1024*1024像素的图片,每一个像素是32位,那么他的大小就是1024×1024×32÷8=4M。一般图片保存成jpg、png格式是通过压缩处理的,它的存储大小可能就只有几k。这就是为何咱们在加载一个10多k的图片是会出现OOM异常的缘由。ide
加载较大的图片函数
在展现高分辨率图片的时候,最好先将图片进行压缩。压缩后的图片大小应该和用来展现它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用不少的内存,并且在性能上还可能会带来负面影响。下面咱们就来看一看,如何对一张大图片进行适当的压缩,让它可以以最佳大小显示的同时,还能防止OOM的出现。 性能
BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于建立Bitmap对象,咱们应该根据图片的来源选择合适的方法。好比SD卡中的图片可使用decodeFile方法,网络上的图片可使用decodeStream方法,资源文件中的图片可使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易致使OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可让解析方法禁止为bitmap分配内存,返回值也再也不是一个Bitmap对象,而是null。虽然Bitmap是null了,可是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让咱们能够在加载图片以前就获取到图片的长宽值和MIME类型,从而根据状况对图片进行压缩。以下代码所示:spa
1 BitmapFactory.Options options = new BitmapFactory.Options(); 2 options.inJustDecodeBounds = true; 3 BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 4 int imageHeight = options.outHeight; 5 int imageWidth = options.outWidth; 6 String imageType = options.outMimeType;
在加载图片时,最好每次都先检查一下图片的大小,除非你能确保这个图片不会致使OOM异常。 code
经过上面的代码咱们能获得图片的大小,下面咱们来对图片进行压缩处理。经过设置BitmapFactory.Options中inSampleSize的值就能够实现。好比咱们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就能够把这张图片压缩成512*384像素。本来加载这张图片须要占用13M的内存,压缩后就只须要占用0.75M了(假设图片是ARGB_8888类型,即每一个像素点占用4个字节)。下面的方法能够根据传入的宽和高,计算出合适的inSampleSize值:
1 public static int calculateInSampleSize(BitmapFactory.Options options, 2 int reqWidth, int reqHeight) { 3 // 源图片的高度和宽度 4 final int height = options.outHeight; 5 final int width = options.outWidth; 6 int inSampleSize = 1; 7 if (height > reqHeight || width > reqWidth) { 8 // 计算出实际宽高和目标宽高的比率 9 final int heightRatio = Math.round((float) height / (float) reqHeight); 10 final int widthRatio = Math.round((float) width / (float) reqWidth); 11 // 选择宽和高中最小的比率做为inSampleSize的值,这样能够保证最终图片的宽和高 12 // 必定都会大于等于目标的宽和高。 13 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 14 } 15 return inSampleSize; 16 }
使用这个方法,首先你要将BitmapFactory.Options的inJustDecodeBounds属性设置为true,解析一次图片。而后将BitmapFactory.Options连同指望的宽度和高度一块儿传递到到calculateInSampleSize方法中,就能够获得合适的inSampleSize值了。以后再解析一次图片,使用新获取到的inSampleSize值,并把inJustDecodeBounds设置为false,就能够获得压缩后的图片了。
大量图片的缓存处理
在你应用程序的UI界面加载一张图片是一件很简单的事情,可是当你须要在界面上加载一大堆图片的时候,状况就变得复杂起来。在不少状况下,(好比使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片能够经过滑动屏幕等事件不断地增长,最终致使OOM。
为了保证内存的使用始终维持在一个合理的范围,一般会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你再也不持有这些图片的引用,从而对这些图片进行GC操做。用这种思路来解决问题是很是好的,但是为了能让程序快速运行,在界面上迅速地加载图片,你又必需要考虑到某些图片被回收以后,用户又将它从新滑入屏幕这种状况。这时从新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你须要想办法去避免这个状况的发生。
这个时候,使用内存缓存技术能够很好的解决这个问题,它可让组件快速地从新加载和处理图片。下面咱们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载不少图片的时候能够提升响应速度和流畅性。
咱们可使用LruCache来处理这个问题,例子以下:
1 private LruCache<String, Bitmap> mMemoryCache; 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 // 获取到可用内存的最大值,使用内存超出这个值会引发OutOfMemory异常。 6 // LruCache经过构造函数传入缓存值,以KB为单位。 7 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 8 // 使用最大可用内存值的1/8做为缓存的大小。 9 int cacheSize = maxMemory / 8; 10 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 11 @Override 12 protected int sizeOf(String key, Bitmap bitmap) { 13 // 重写此方法来衡量每张图片的大小,默认返回图片数量。 14 return bitmap.getByteCount() / 1024; 15 } 16 }; 17 } 18 19 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 20 if (getBitmapFromMemCache(key) == null) { 21 mMemoryCache.put(key, bitmap); 22 } 23 } 24 25 public Bitmap getBitmapFromMemCache(String key) { 26 return mMemoryCache.get(key); 27 }
另外,在github上有个很好用的解决大量图片缓存的框架—xUtils,其中BitmapUtils模块就是用来解决这个问题的。