一、BitmapFactory.decodeStream()方法
尽量不要使用setImageBitmap()、setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过Java层的createBitmap()来完成的,需要消耗更多内存资源。
因此,可以使用先通过BitmapFactory.decodeStream()方法创建出一个Bitmap,再将其设为ImageView的source的方法。
BitmapFactory.decodeStream()方法最大的秘密在于,其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用Java层的createBitmap,从而节省了Java层的空间。
二、BitmapFactory.Options
当图片过大或图片数量较多时,使用BitmapFactory解码图片会出java.lang.OutOfMemoryError: bitmap size exceeds VM budget,要想正常使用则需分配更少的内存,具体的解决办法是修改采样值BitmapFactory.Options.inSampleSize,例如:
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inSampleSize = 4;
- Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
查看官方的文档说明:
简单翻译一下:
如果设置的值大于1,则要求解码器从原始图片中采样一张小图,以此来节省内存消耗。例如,inSampleSize的值为4时,则解码器返回图片的宽高是原始图片的1/4,也就是说,返回图片的像素点数量是原始图片的1/16。任何小于1的值都会被当成1。
注意:解码器会尽可能满足采样请求,但有时候返回结果会和原来的精确请求有所差异;把值设置为2对解码器来说是最快最容易满足的。
因此,如何正确设置恰当的inSampleSize是解决该问题的关键之一。
BitmapFactory.Options提供了另一个成员变量inJustDecodeBounds。例如:
- BitmapFactory.Options opts = new BitmapFactory.Options();
- opts.inJustDecodeBounds = true;
- Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
查看官方的文档说明:
简单翻译一下:
如果设置为true,解码器会返回null,但图片的外围边界值仍然可以得到,这就允许调用者在无需分配内存加载图片的情况下获取图片的相关信息。
设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize。
查看Android源码,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;
- }
- }
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) {
- }
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) {
}
另外,可以通过Bitmap.recycle()方法来释放位图所占的空间,当然前提是位图没有被使用。
三、优化Dalvik虚拟机的堆内存分配
对Android平台来说,其托管层使用的Dalvik Java VM从目前的表现来看还有很多地方可以优化处理,比如我们在开发一些大型游戏或耗资源的应用中可能考虑手动干涉GC处理,使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。
当然具体的做法可以参考开源工程,这里仅说下使用方法,在程序onCreate()时调用即可 :
- private final static float TARGET_HEAP_UTILIZATION = 0.75f;
- VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
private final static float TARGET_HEAP_UTILIZATION = 0.75f;
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
四、自定义Android堆内存大小
对一些Android项目来说,影响性能的主要瓶颈是Android自身的内存管理机制问题,目前手机厂商对RAM都比较吝啬,对软件的流畅性来说,RAM对性能的影响十分敏感,除了优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义自己软件对内存的要求大小,我们使用Dalvik提供的dalvik.system.VMRuntime类来设置最小堆内存:
- // 设置最小Heap内存为6MB
- private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
- VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
// 设置最小Heap内存为6MB
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
当然对于内存吃紧,还可以通过手动干涉GC去处理。
参考资料:
http://www.maxhis.info/archives/491