在Android中当我们经常会遇到一些OOM(Out Of Memory)的情况。今天探究了一些Android中经常可能会遇到的一些oom情况。
首先我们想要解决问题得先分析造成oom的原因。一般情况如下:
一、加载对象过大。
二、相应资源过多,来不及释放。
总的说来就是由于手机设备限制每个手机应用分配的内存是固定有限,当DVM请求的内存大于了剩下的内存量就会造成OOM。
知道了原因,我们可以知道只要剩下的内存大于需要的内存时就不会发生oom。因此我们要做的就是适时释放不用的内存。
我按照常见的情况分为两类来讨论:
一、加载对象过大。
一般我们都是加载过大的图片,所以这里我们也仅仅讨论加载过大图片的情况。比如一个应用要加载20M的图片,如果不经过处理的话,肯定会发生程序崩溃,造成oom的。
而一般情况下我们会将图片压缩再显示,不然肯定会出现oom。BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们一般可以通过这个类提供的方法来压缩图片。
同时我们也要考虑到显示图片要的内存大小,要缩放的比例等情况。要缩放就要获取到具体的图片属性等情况,通过设置BitmapFactory.Options这个参数,将inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。我们可以通过这样来获取具体的属性。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.test, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
在缩放时,我们再将inJustDecodeBounds设为false,然后再计算缩放的比例:
//源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
一般第一种情况便是采用这种解决方案。
二、相应资源过多,来不及释放。
在这种情况下一般是我们加载了过多的资源,内存来不及释放就会造成oom,比如常见的ListView,GridView等视图加载情况产生的。
我们要做的就是及时释放内存。所以这就可以通过以下方法来实现:
1、及时释放Bitmap的内存。在没有使用时我们令bitmap对象为null,让GC回收资源释放内存。通过及时的调用Bitmap的recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。
if(bitmap != null && !bitmap.isRecycled()){
// 回收并且置为null
bitmap.recycle();
bitmap = null;
}
System.gc();
同时我们可以优化java的对象引用来进行内存优化。那么首先我们就来了解一下java中的引用:
java中的引用主要分为四类:
1、强引用:在平时代码里普遍存在的引用,无论何时对象都不会被JVM回收。
2、软引用:描述一些有用但并不是必需的对象,对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。
3、弱引用:弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
4、虚引用:它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
Java4种引用的级别由高到低依次为:
强引用 > 软引用 > 弱引用 > 虚引用
因此我们可以采用软引用,当内存不勾时可以及时释放内存。如下我们可以先将bitmap存储在软引用当中:
/**
* 将软引用通过HasMap保存起来
*/
public Map<String, SoftReference<Bitmap>> mImageCacheMap = new HashMap<String,SoftReference<Bitmap>>();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null,options);
SoftReference<Bitmap> d = new SoftReference<Bitmap>(bitmap);
mImageCacheMap.put(imagePath, d);
SoftReference<Bitmap> softReference = mImageCacheMap.get(imageUrl);
if (softReference.get() != null) {
return softReference.get();
}
我们使用取出来的bitmap对象时,如果内存不够就会及时释放。我们现在不推荐这种方式,而是下面的方案,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
2、缓存技术
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。为我们解决oom提供的好的方法。主要方法:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount();
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
同时我们也可以采用DiskLruCache(Google提供了一套硬盘缓存的解决方案,DiskLruCache非Google官方编写,但获得官方认证)技术,当缓存中没有图片时,从存储卡上获取资源。源码地址:DiskLruCache。主要方法:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)return softReference.get();
}
open()方法接收四个参数,第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1,第四个参数指定最多可以缓存多少字节的数据。其中缓存地址会存放在 /sdcard/Android/data//cache这个路径下。
demo截图: