众所周知,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB。
如果是开发图片浏览器应用,例如像Android系统自带的Gallery那样的应用,这个问题将变得尤为突出。
如果开发的是购物客户端,有时候处理不当也会碰到这种问题,刚好我这两天开发都碰上了,=. =!。
内存限制是Android对应用的一个系统级限制,作为应用层开发人员,没有办法彻底去消灭这个限制,但是可以通过一些手段去合理使用内存,从而规避这个问题。
以下是个人总结各位大神的一些常用方法:
(1)缓存图像到内存,采用软引用缓存到内存,而不是在每次使用的时候都从新加载到内存;
(2)调整图像大小,手机屏幕尺寸有限,分配给图像的显示区域本身就更小,有时图像大小可以做适当调整;
(3)采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存;
(4)及时回收图像,如果引用了大量Bitmap对象,而应用又不需要同时显示所有图片,可以将暂时用不到的Bitmap对象及时回收掉;
(5)自定义堆内存分配大小,优化Dalvik虚拟机的堆内存分配;
一、在listview,gridview等需要加载大量图片的时候,为了进一步避免OOM,除了缓存,还可以对图片进行压缩,进一步节省内存,多数情况下调整图片大小并不会影响应用的表现力。
首先我们把这个图片转成Bitmap,尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图
因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存
传说decodeStream直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。
Bitmap=BitmapFactory.decodeStream(context.getResources().openRawResource(resId));
一般来说利用Bitmap的getWidth()和getHeight()方法就可以取到图片的宽高了。
在通过BitmapFactory.decodeFile(Stringpath)方法将突破转成Bitmap时,遇到大一些的图片,我们经常会遇到OOM的问题。
这就用到了我们上面提到的BitmapFactory.Options这个类这就用到了BitmapFactory.Options这个类。
BitmapFactory.Options这个类,有一个字段叫做 inJustDecodeBounds 。SDK中对这个成员的说明是这样的:
If set to true, the decoder will returnnull (no bitmap), but the out…
也就是说,如果我们把它设为true,那么BitmapFactory.decodeFile(String path, Options opt)并不会真的返回一个Bitmap给你,
它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM了。
示例代码如下:
BitmapFactory.Options options = newBitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bmp = BitmapFactory.decodeFile(path,options);
这里返回的bmp是null
这段代码之后,options.outWidth 和 options.outHeight就是我们想要的宽和高了。
有了宽,高的信息,我们怎样在图片不变形的情况下获取到图片指定大小的缩略图呢?
比如我们需要在图片不变形的前提下得到宽度为200px的缩略图。
那么我们需要先计算一下缩放之后,图片的高度是多少
这样虽然我们可以得到我们期望大小的ImageView
但是在执行BitmapFactory.decodeFile(path,options);时,并没有节约内存。
要想节约内存,还需要用到BitmapFactory.Options这个类里的 inSampleSize 这个成员变量。
我们可以根据图片实际的宽高和我们期望的宽高来计算得到这个值。
/* 计算得到图片的高度 */
/* 这里需要主意,如果你需要更高的精度来保证图片不变形的话,需要自己进行一下数学运算 */
/* 计算得到图片的高度以下插入一个比较好的工具类,可以返回一个适合的 options.inSampleSize 值 */
public class Util {
* Compute the sample size as a function of minSideLength
* and maxNumOfPixels.
* minSideLength is used to specify that minimal width or height of a
* bitmap.
* maxNumOfPixels is used to specify the maximal size in pixels that is
* tolerable in terms of memory usage.
*
* The function returns a sample size based on the constraints.
* Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
* which indicates no care of the corresponding constraint.
* The functions prefers returning a sample size that
* generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
*
* Also, the function rounds up the sample size to a power of 2 or multiple
* of 8 because BitmapFactory only honors sample size this way.
* For example, BitmapFactory downsamples an image by 2 even though the
* request is 3. So we round up the sample size to avoid OOM.
*/
public static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength,
maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
public static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == -1) &&
(minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
}
/* 这样才能真正的返回一个Bitmap给你 */options.inJustDecodeBounds = false;
options.inSampleSize ="一个合适的值";
Bitmap bmp = BitmapFactory.decodeFile(path,options);image.setImageBitmap(bmp);
另外,为了节约内存我们还可以使用下面的几个字段:
options.inPreferredConfig =Bitmap.Config.ARGB_4444; // 默认是Bitmap.Config.ARGB_8888
//四种构造Bitmap的
Bitmap.createBitmap(width, height,Bitmap.Config.ALPHA_8); 8bit
Bitmap.createBitmap(width, height,Bitmap.Config.ARGB_4444); 12bit
Bitmap.createBitmap(width, height,Bitmap.Config.ARGB_8888); 32bit
Bitmap.createBitmap(width, height,Bitmap.Config.RGB_565); 16bit //前三个各占8位,ARGB_8888的config就是等于4
如果对缩略图的质量没什么特别要求的话,尽量选低一点config,可以大量节省内存对于400*800的图,占用内存是400*800*config,接近1M....= . =!
嫌麻烦的话,至少也要压缩个两倍,官方API说明文档说推荐是2的倍数
/* 下面两个字段需要组合使用 */
options.inPurgeable = true;
*BitmapFactory.Options.inPurgeable;
* 如果 inPurgeable 设为True的话表示使用BitmapFactory创建的Bitmap
* 用于存储Pixel的内存空间在系统内存不足时可以被回收,
* 在应用需要再次访问Bitmap的Pixel时(如绘制Bitmap或是调用getPixel),
* 系统会再次调用BitmapFactory decoder重新生成Bitmap的Pixel数组。
* 为了能够重新解码图像,bitmap要能够访问存储Bitmap的原始数据。
* 在inPurgeable为false时表示创建的Bitmap的Pixel内存空间不能被回收,
* 这样BitmapFactory在不停decodeByteArray创建新的Bitmap对象,
* 不同设备的内存不同,因此能够同时创建的Bitmap个数可能有所不同,
* 200个bitmap足以使大部分的设备重新OutOfMemory错误。
* 当isPurgable设为true时,系统中内存不足时,
* 可以回收部分Bitmap占据的内存空间,这时一般不会出现OutOfMemory 错误。
options.inInputShareable = true;
* inInputShareable 是否深拷贝
* This field works in conjuction with inPurgeable.
* If inPurgeable is false, then this field is ignored. If inPurgeable is
* true, then this field determines whether the bitmap can share areference
* to the input data (inputstream, array, etc.) or if it must make a deepcopy.
二、为图片加载的添加一个软引用缓存,每次图片从缓存中获取图片对象,若缓存中不存在,才会从Sdcard加载图片,并将该对象加入缓存。同时软引用的对象也有助于GC在内存不足的时候回收它们。
首先先简单介绍下SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
看下面代码:
Object aRef = new Object();
SoftReference SoftRef=newSoftReference(aRef);
此时,对于这个Object对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aRef的强引用,所以这个Object对象是强可及对象。
随即,我们可以结束aRef对这个Object实例的强引用:
aRef = null;
此后,这个Object对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,并不会因为有一个SoftReference对该对象的引用而始终保留该对象。
Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。
也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。
在回收这些对象之前,我们可以通过:Object anotherRef=(Object)aSoftRef.get();
重新获得对该实例的强引用。而回收之后,调用get()方法就只能得到null了。
记得使用ReferenceQueue清除失去了软引用对象的SoftReference作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。
所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量
SoftReference对象带来的内存泄漏。
在java.lang.ref包里还提供了ReferenceQueue。
如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:
ReferenceQueue queue = newReferenceQueue();
SoftReference ref=new SoftReference(aMyObject, queue);
那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。
也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。
另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。
如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。
于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。常用的方式为:
SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) !=null) {
//清除ref
}
//详细使用例子
public class ImageLoaderWithRecyle implements Loadable {
private HashMap<String, SoftReference<Bitmap>> mImageCache;
public ImageLoaderWithRecyle() {
mImageCache = new HashMap<String, SoftReference<Bitmap>>();
}
@Override
public Bitmap loadBitmapImage(String path) {
if(mImageCache.containsKey(path)) {
SoftReference<Bitmap> softReference = mImageCache.get(path);
Bitmap bitmap = softReference.get();
if(null != bitmap)
return bitmap;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
Log.d("OomDemo", "alert!!!" + String.valueOf(options.mCancel) + " " + options.outWidth + options.outHeight);
return null;
}
options.inSampleSize = Util.computeSampleSize(options, 600, (int) (1 * 1024 * 1024));
Log.d("OomDemo", "inSampleSize: " + options.inSampleSize);
options.inJustDecodeBounds = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
mImageCache.put(path, new SoftReference<Bitmap>(bitmap));
return bitmap;
}
@Override
public Drawable loadDrawableImage(String path) {
return new BitmapDrawable(loadBitmapImage(path));
}
@Override
public void releaseImage(String path) {
if(mImageCache.containsKey(path)) {
SoftReference<Bitmap> reference = mImageCache.get(path);
Bitmap bitmap = reference.get();
if(null != bitmap) {
Log.d("OomDemo", "recyling " + path);
bitmap.recycle();
}
mImageCache.remove(path);
}
}
}
三、在有些情况下,严重缩小图片还是会影响应用的显示效果的,所以有必要在尽可能少地缩小图片的前提下展示图片,此时手动去回收图片就变得尤为重要。也可以用try catch 块包围住容易内存溢出的地方,譬如说getview()里面setimage的地方,捕抓到OOM异常就及时手动进行释放不必要的资源,释放完将刚才发生异常没有执行的代码再重新执行一次。
//在Activity中,每一次gallery选中之后都释放前后几个不需要展示item的bitmap
@Override
publicvoid onItemSelected(AdapterView<?> parent, View view, int position,longid) {
//TODO Auto-generated method stub
((GalleryAdapter)parent.getAdapter()).releaseDrawable(mGallery);
}
//在Adapter里的一个释放内存的类,保留前后各两张的bitmap资源
public void releaseDrawable(AdapterView view) {
MyGallery gallery = (MyGallery) view;
int start = gallery.getFirstVisiblePosition() - 2;
int end = gallery.getLastVisiblePosition() + 2;
for(int i = 0; i < start; i++) {
mImageLoader.releaseImage(mResPath[i]);
}
for(int i = end + 1; i < mResPath.length; i++) {
mImageLoader.releaseImage(mResPath[i]);
}
}
}
四、这个我不是太懂,优化Dalvik虚拟机的堆内存分配,听着略为强大
对于Android平台来说,其托管层使用的Dalvik JavaVM从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉
GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。当然具体原理我们可以参考开源工程,这里我们仅说下使
用方法: 代码如下:
private final staticfloatTARGET_HEAP_UTILIZATION = 0.75f;
//在程序onCreate时就可以调用
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
五、自定义我们的应用需要多大的内存,强行设置最小内存大小,代码如下:
private final static int CWJ_HEAP_SIZE = 6*1024* 1024 ;
//设置最小heap内存为6MB大小
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
小结
我觉得JAVA的垃圾回收系统是好,但是回收效率和效果远远不够用,我们也要学C程序员一样学会自己回收资源。本文介绍了软引用缓存、调整大小、回收等手段来避免
OOM,综合使用来说效果还是明显的。
由于本人能力所限以及时间关系,本文还有很多说不好。比如对Android内存分配的理解不深,没能透彻地解释Bitmap的内存占用情况;通过自定义堆内存分配大小,优化