下面这些都是我在做各个app时总结出来的,希望对各位有用:
1.Android内存溢出原因
当图片过大,或图片数量较多时使用BitmapFactory解码图片会出java.lang.OutOfMemoryError: bitmap size exceeds VM budget,要想正常使用则需分配更少的内存
具体的解决办法是修改采样值BitmapFactory.Options.inSampleSize;设置此数值后会将图片的长宽缩放此int值后读入内存
2.恰当的inSampleSize
BitmapFactory.Options提供了另一个成员inJustDecodeBounds;设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize。
Android提供了一种动态计算的方法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;
}
private 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;
}
}
使用该算法,就可动态计算出图片的inSampleSize
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile, opts);
opts.inSampleSize = computeSampleSize(opts, -1, 128*128);
opts.inJustDecodeBounds = false;
try {
Bitmap bmp = BitmapFactory.decodeFile(imageFile, opts);
imageView.setImageBitmap(bmp);
} catch (OutOfMemoryError err) {
}
3.使用ListView展示图片
大部分的app都是使用ListView来展示图片的,其实ListView的adapter是可以重用每个list的view的:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.e("ok", "position:"+position);
if (convertView == null) { // 这句就是重用的关键
convertView = LayoutInflater.from(context).inflate(R.layout.xxx, null);
}
}
重用convertView的方式也是google推荐的,这样系统是可以自动回收已经滑出屏幕的资源的。
但是重用convertView会有一个问题,就是如果图片是异步加载的,快速滑动listview时每个list的上的图片会不停的闪(以前做省电宝的时候遇到过这个bug),这个bug是由于我没有判断ImageView控件是否是重用的而引起的。 要解决这个bug,只需在异步加载图片的时候根据每个ImageView的tag判断一下ImageView是否重用:
boolean imageViewReused(ImageView iv, URL url) {
String tag=imageViewsHashMap.get(iv); // imageViewsHashMap是用来存储ImageView和tag的对应关系的(我是用图片的url来标识的tag,这个tag可以随意取值,只要能唯一的标识这个ImageView就好了)
if(tag==null || !tag.equals(url)) {
return true;
}
return false;
}
如果ImageView是重用的,就先设置一张默认的图片,等异步加载完成后再设置成最终要展示的图片。 如果ImageView不是重用的,忽略。
4.View隐藏或Activity退出时释放所有的Bitmap资源
之前在做壁纸的时候一直出现OOM的问题,找了好久都没有发现是怎么回事,之前做市场的时候也有遇到过,而且我确定我在使用了所有的Bitmap以后都手动释放了那些资源。这是一个很奇怪的事情,虽然我现在解了这个bug了,但是有些android的机制我还不是很理解,也希望有大拿指导一下。
下面是解决方案:
ImageView是存储在JVM里面的,但是Bitmap是存储在native heap里面的(3.0之前的系统是这样的),壁纸有一个大图浏览界面(一个Activity),每次进入这个界面的时候都会加载一张大图(大概在70K-200K),我加载大图的代码:
imageView.setImageBitmap(bitmap); //
我在每次Activity退出时都会主动的回收掉这个bitmap,代码:
if(bitmap != null && !bitmap.isrecycle) {
bitmap.recycle(); // 主动释放以后系统再GC也不管用,不知道为什么,我也尝试过对bitmap使用软引用/弱应用等,也都是不起作用
}
我在DDMS里面看到系统也会执行GC,但是就是不会回收我释放的这些资源,多次(一般3-5次)进入大图浏览界面以后一般就会报OOM的错误了。
后来我把退出时的代码改成了下面的形式:
imageView.setImageBitmap(null); // 不是主动的回收bitmap,而是主动的释放对bitmap的引用
imageView = null;
我从DDMS里面看到资源基本都被回收了,壁纸也基本不会再报OOM的错误了。