为了防止图片加载的时候出现OOM,笔者在这里总结了几种方式,均来自与wuli互联网并加上了自己的见解。
方法1:
读取图片时注意方法的调用,适当压缩 。尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decode
Resource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗
更多内存。因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的
source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的
createBitmap,从而节省了java层的空间。
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;//给其分配内存
options.inSampleSize = 10; // width,hight设为原来的十分一
Bitmap btp = BitmapFactory.decodeStream(is, null, options);
如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常。
下面是我从网络上找到的一段神奇的代码,据说是以最少的内存去读取本地资源图片
public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片的Config参数
opt.inPurgeable = true;
opt.inInputShareable = true;
// 获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
有些人可能对以上的参数不理解,我来解释一番:
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.inPreferredConfig值来降低内存消耗:
默认为ARGB_8888: 每个像素4字节. 共32位。
Alpha_8: 只保存透明度,共8位,1字节。
ARGB_4444: 共16位,2字节。
RGB_565:共16位,2字节。
如果不需要透明度,可把默认值ARGB_8888改为RGB_565,节约一半内存。
inInputShareable:
设置是否深拷贝,与inPurgeable结合使用
那为什么decodeStream方法可以代替decodeResource,setImageResource,setImageBitmap等方法来加载图片呢?
decodeStream直接读取图片字节码,调用nativeDecodeAsset/nativeDecodeStream来完成decode。无需使用Java空间的一些额外处理过程,节省dalvik内存。但是由于直接读取字节码,没有处理过程,因此不会根据机器的各种分辨率来自动适应,需要在hdpi,mdpi和ldpi中分别配置相应的图片资源,否则在不同分辨率机器上都是同样的大小(像素点数量),显示的实际大小不对。
decodeResource会在读取完图片数据后,根据机器的分辨率,进行图片的适配处理,导致增大了很多dalvik内存消耗。
在适当的时候及时回收图片占用的内存 通常Activity或者Fragment在onStop/onDestroy时候就可以释放图片资源:
if(imageView != null && imageView.getDrawable() != null){
Bitmap oldBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
imageView.setImageDrawable(null);
if(oldBitmap != null){
oldBitmap.recycle();
oldBitmap = null;
}
}
System.gc();
在释放资源时,需要注意释放的Bitmap或者相关的Drawable是否有被其它类引用。如果正常的调用,可以通过Bitmap.isRecycled()方法来判断是否有被标记回收;而如果是被UI线程的界面相关代码使用,就需要特别小心避免回收有可能被使用的资源,不然有可能抛出系统异常: E/AndroidRuntime: java.lang.IllegalArgumentException:
Cannot draw recycled bitmaps 并且该异常无法有效捕捉并处理。
方法3:
不必要的时候避免图片的完整加载,只需要知道图片大小的情形下,可以不完整加载图片到内存。 在使用
BitmapFactory压缩图片的时候,BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方
法,可以在不分配空间状态下计算出图片的大小。示例如下:
//加载图片
//图片压缩
//1.获得图片需要显示的大小
ImageSize imageSize = getImageViewSize(imageView);
//2.压缩图片
Bitmap bm = decodeSampleBitmapFromPath(path, imageSize.width, imageSize.height);
//根据图片需要显示的宽和高进行压缩
private Bitmap decodeSampleBitmapFromPath(String path, int width, int height) {
//获得图片的宽和高,并不把图片加载到内存中
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//不分配内存
BitmapFactory.decodeFile(path, options);
options.inSampleSize = caculateInSampleSize(options, width, height);
//使用获得的InSampleSize再次解析图片
options.inJustDecodeBounds = false;//可以分配内存了
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
/**
* 根据需求的宽和高以及图片的宽和高计算SampleSize
*/
private int caculateInSampleSize(BitmapFactory.Options options, int reqwidth, int reqheight) {
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width > reqwidth || height > reqheight) {
int wdithRadio = Math.round(width * 1.0f / reqwidth);
int heightRadio = Math.round(height * 1.0f / reqheight);
inSampleSize = Math.max(heightRadio, wdithRadio);
}
return inSampleSize;
}
/**
* 根据ImageView获取宽和高
*/
private ImageSize getImageViewSize(ImageView imageView) {
ImageSize imageSize = new ImageSize();
displayMetrics = imageView.getContext().getResources().getDisplayMetrics();
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
//获取ImageView的实际宽度
int width = imageView.getWidth();
if (width <= 0) {
width = lp.width;//获取imageview在layout中声明的宽度
}
if (width <= 0) {
width = getImageViewFieldValue(imageView, "mMaxWidth");//检查最大值
}
if (width <= 0) {
width = displayMetrics.widthPixels;
}
int height = imageView.getHeight();
//获取imageview的实际高度
if (height <= 0) {
height = lp.height;//获取imageview在layout中声明的高度
}
if (height <= 0) {
height = getImageViewFieldValue(imageView, "mMaxHeight");//检查最大值
}
if (height <= 0) {
height = displayMetrics.heightPixels;
}
imageSize.width = width;
imageSize.height = height;
return imageSize;
}
//通过反射获取Imageview的某个属性值
private static int getImageViewFieldValue(Object object, String fieldName) {
int value = 0;
try {
Field field = ImageView.class.getDeclaredField(fieldName);
field.setAccessible(true);
int fieldValue = field.getInt(object);
if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {
value = fieldValue;
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return value;
}
private class ImageSize {
int width;
int height;
}
方法4
优化Dalvik虚拟机的堆内存分配,堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不
是一成不变的,通常有一个分配机制来控制它的大小。比如初始的HEAP是4M大,当4M的空间被占用超过75%的
时候,重新分配堆为8M大,当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。
Heap Utilization是堆的利用率。当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让
实际占用率向个百分比靠拢。使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆
内存的处理效率。
private final static float TARGET_HEAP_UTILIZATION = 0.75f;
// 在程序onCreate时就可以调用
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
自定义堆(Heap)内存大小,我们可以强制定义自己软件的对内存大小,我们使用Dalvik提供的
dalvik.system.VMRuntime类来设置最小堆内存为例:
private final static int CWJ_HEAP_SIZE = 6 * 1024 * 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); // 设置最小heap内存为6MB大小。
但是上面方法还是存在问题,函数setMinimumHeapSize其实只是改变了堆的下限值,它可以防止过于频繁的堆内存分配,当设置最小堆内存大小超过上限值(Max Heap Size)时仍然采用堆的上限值,对于内存不足没什么作用。
方法6:
在Manifest.xml文件里面的<application 里面添加Android:largeHeap="true"
简单粗暴。这种方法允许应用需要耗费手机很多的内存空间,但却是最快捷的解决办法
最后介绍一下图片占用进程的内存算法。android中处理图片的基础类是Bitmap,顾名思义,就是位图。占用内存的算法如:图片的width*height*Config.如果Config设置为ARGB_8888,那么上面的Config就是4。一张480*320的图片占用的内存就是480*320*4byte。 在默认情况下android进程的内存占用量为16M,因为Bitmap他除了java中持有数据外,底层C++的 skia图形库还会持有一个SKBitmap对象,因此一般图片占用内存推荐大小应该不超过8M。这个可以调整,编译源代码时可以设置参数。
总结完毕:不喜勿喷,谢谢!!!