彻底解决Android因加载多个大图引起的OutOfMemoryError,内存溢出的问题

http://blog.csdn.net/wulianghuan/article/details/11548373?reload

最近因为项目里需求是选择或者拍摄多张照片后,提供滑动预览和上传,很多照片是好几MB一张,因为目前的Android系统对运行的程序都有一定的内存限制,一般是16MB或24MB(视平台而定),不做处理直接加载的话必然会报OOM (Out Of Memmory)。网上有很多解决android加载bitmap内存溢出的方法,我总结了一个通用的方法,下面是我从的开发案例抽取出来的代码:

我在项目中建了个Util.java工具类,里面写了个方法,根据图片的路径返回一个字节流数组对象:

[java] view plaincopyprint?

  1. public static byte[] decodeBitmap(String path) {
  2. BitmapFactory.Options opts = new BitmapFactory.Options();
  3. opts.inJustDecodeBounds = true;// 设置成了true,不占用内存,只获取bitmap宽高
  4. BitmapFactory.decodeFile(path, opts);
  5. opts.inSampleSize = computeSampleSize(opts, -1, 1024 * 800);
  6. opts.inJustDecodeBounds = false;// 这里一定要将其设置回false,因为之前我们将其设置成了true
  7. opts.inPurgeable = true;
  8. opts.inInputShareable = true;
  9. opts.inDither = false;
  10. opts.inPurgeable = true;
  11. opts.inTempStorage = new byte[16 * 1024];
  12. FileInputStream is = null;
  13. Bitmap bmp = null;
  14. ByteArrayOutputStream baos = null;
  15. try {
  16. is = new FileInputStream(path);
  17. bmp = BitmapFactory.decodeFileDescriptor(is.getFD(), null, opts);
  18. double scale = getScaling(opts.outWidth * opts.outHeight,
  19. 1024 * 600);
  20. Bitmap bmp2 = Bitmap.createScaledBitmap(bmp,
  21. (int) (opts.outWidth * scale),
  22. (int) (opts.outHeight * scale), true);
  23. bmp.recycle();
  24. baos = new ByteArrayOutputStream();
  25. bmp2.compress(Bitmap.CompressFormat.JPEG, 100, baos);
  26. bmp2.recycle();
  27. return baos.toByteArray();
  28. } catch (FileNotFoundException e) {
  29. e.printStackTrace();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. } finally {
  33. try {
  34. is.close();
  35. baos.close();
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. }
  39. System.gc();
  40. }
  41. return baos.toByteArray();
  42. }
  43. private static double getScaling(int src, int des) {
  44. /**
  45. * 48 目标尺寸÷原尺寸 sqrt开方,得出宽高百分比 49
  46. */
  47. double scale = Math.sqrt((double) des / (double) src);
  48. return scale;
  49. }
  50. public static int computeSampleSize(BitmapFactory.Options options,
  51. int minSideLength, int maxNumOfPixels) {
  52. int initialSize = computeInitialSampleSize(options, minSideLength,
  53. maxNumOfPixels);
  54. int roundedSize;
  55. if (initialSize <= 8) {
  56. roundedSize = 1;
  57. while (roundedSize < initialSize) {
  58. roundedSize <<= 1;
  59. }
  60. } else {
  61. roundedSize = (initialSize + 7) / 8 * 8;
  62. }
  63. return roundedSize;
  64. }
  65. private static int computeInitialSampleSize(BitmapFactory.Options options,
  66. int minSideLength, int maxNumOfPixels) {
  67. double w = options.outWidth;
  68. double h = options.outHeight;
  69. int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math
  70. .sqrt(w * h / maxNumOfPixels));
  71. int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(
  72. Math.floor(w / minSideLength), Math.floor(h / minSideLength));
  73. if (upperBound < lowerBound) {
  74. return lowerBound;
  75. }
  76. if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
  77. return 1;
  78. } else if (minSideLength == -1) {
  79. return lowerBound;
  80. } else {
  81. return upperBound;
  82. }
  83. }
public static byte[] decodeBitmap(String path) {
		BitmapFactory.Options opts = new BitmapFactory.Options();
		opts.inJustDecodeBounds = true;// 设置成了true,不占用内存,只获取bitmap宽高
		BitmapFactory.decodeFile(path, opts);
		opts.inSampleSize = computeSampleSize(opts, -1, 1024 * 800);
		opts.inJustDecodeBounds = false;// 这里一定要将其设置回false,因为之前我们将其设置成了true
		opts.inPurgeable = true;
		opts.inInputShareable = true;
		opts.inDither = false;
		opts.inPurgeable = true;
		opts.inTempStorage = new byte[16 * 1024];
		FileInputStream is = null;
		Bitmap bmp = null;
		ByteArrayOutputStream baos = null;
		try {
			is = new FileInputStream(path);
			bmp = BitmapFactory.decodeFileDescriptor(is.getFD(), null, opts);
			double scale = getScaling(opts.outWidth * opts.outHeight,
					1024 * 600);
			Bitmap bmp2 = Bitmap.createScaledBitmap(bmp,
					(int) (opts.outWidth * scale),
					(int) (opts.outHeight * scale), true);
			bmp.recycle();
			baos = new ByteArrayOutputStream();
			bmp2.compress(Bitmap.CompressFormat.JPEG, 100, baos);
			bmp2.recycle();
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				is.close();
				baos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.gc();
		}
		return baos.toByteArray();
	}

	private static double getScaling(int src, int des) {
		/**
		 * 48 目标尺寸÷原尺寸 sqrt开方,得出宽高百分比 49
		 */
		double scale = Math.sqrt((double) des / (double) src);
		return scale;
	}

	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 lowerBound;
		}

		if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
			return 1;
		} else if (minSideLength == -1) {
			return lowerBound;
		} else {
			return upperBound;
		}
	}

然后在我需要加载图片BitMap的地方来调用Util.decodeBitmap():

[java] view plaincopyprint?

  1. Bitmap bitmap = BitmapFactory.decodeByteArray(Util.decodeBitmap(imagePath), 0, Util.decodeBitmap(imagePath).length);
  2. imageCache.put(imagePath, new SoftReference<Bitmap>(bitmap));
Bitmap bitmap = BitmapFactory.decodeByteArray(Util.decodeBitmap(imagePath), 0, Util.decodeBitmap(imagePath).length);
                imageCache.put(imagePath, new SoftReference<Bitmap>(bitmap));

上面这两行是我的AsyncImageLoaderByPath类中的代码,用来加载SD卡里面的图片,该类完整代码是:

[java] view plaincopyprint?

  1. package com.pioneer.travel.util;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.lang.ref.SoftReference;
  5. import java.net.MalformedURLException;
  6. import java.net.URL;
  7. import java.util.HashMap;
  8. import android.content.Context;
  9. import android.graphics.Bitmap;
  10. import android.graphics.BitmapFactory;
  11. import android.graphics.BitmapFactory.Options;
  12. import android.graphics.drawable.Drawable;
  13. import android.os.Handler;
  14. import android.os.Message;
  15. import android.provider.MediaStore;
  16. import android.util.Log;
  17. import android.widget.ImageView;
  18. public class AsyncImageLoaderByPath {
  19. //SoftReference是软引用,是为了更好的为了系统回收变量
  20. private HashMap<String, SoftReference<Bitmap>> imageCache;
  21. private Context context;
  22. public AsyncImageLoaderByPath(Context context) {
  23. this.imageCache = new HashMap<String, SoftReference<Bitmap>>();
  24. this.context = context;
  25. }
  26. public Bitmap loadBitmapByPath(final String imagePath, final ImageView imageView, final ImageCallback imageCallback){
  27. if (imageCache.containsKey(imagePath)) {
  28. //从缓存中获取
  29. SoftReference<Bitmap> softReference = imageCache.get(imagePath);
  30. Bitmap bitmap = softReference.get();
  31. if (bitmap != null) {
  32. return bitmap;
  33. }
  34. }
  35. final Handler handler = new Handler() {
  36. public void handleMessage(Message message) {
  37. imageCallback.imageLoaded((Bitmap) message.obj, imageView, imagePath);
  38. }
  39. };
  40. //建立新一个获取SD卡的图片
  41. new Thread() {
  42. @Override
  43. public void run() {
  44. Bitmap bitmap = BitmapFactory.decodeByteArray(Util.decodeBitmap(imagePath), 0, Util.decodeBitmap(imagePath).length);
  45. imageCache.put(imagePath, new SoftReference<Bitmap>(bitmap));
  46. Message message = handler.obtainMessage(0, bitmap);
  47. handler.sendMessage(message);
  48. }
  49. }.start();
  50. return null;
  51. }
  52. //回调接口
  53. public interface ImageCallback {
  54. public void imageLoaded(Bitmap imageBitmap,ImageView imageView, String imagePath);
  55. }
  56. }
package com.pioneer.travel.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.ImageView;

public class AsyncImageLoaderByPath {
    //SoftReference是软引用,是为了更好的为了系统回收变量
    private HashMap<String, SoftReference<Bitmap>> imageCache;
    private Context context;

    public AsyncImageLoaderByPath(Context context) {
        this.imageCache = new HashMap<String, SoftReference<Bitmap>>();
        this.context = context;
    }
    public Bitmap loadBitmapByPath(final String imagePath, final ImageView imageView, final ImageCallback imageCallback){
        if (imageCache.containsKey(imagePath)) {
            //从缓存中获取
            SoftReference<Bitmap> softReference = imageCache.get(imagePath);
            Bitmap bitmap = softReference.get();
            if (bitmap != null) {
                return bitmap;
            }
        }
        final Handler handler = new Handler() {
            public void handleMessage(Message message) {
                imageCallback.imageLoaded((Bitmap) message.obj, imageView, imagePath);
            }
        };
        //建立新一个获取SD卡的图片
        new Thread() {
            @Override
            public void run() {
            	Bitmap bitmap = BitmapFactory.decodeByteArray(Util.decodeBitmap(imagePath), 0, Util.decodeBitmap(imagePath).length);
                imageCache.put(imagePath, new SoftReference<Bitmap>(bitmap));
                Message message = handler.obtainMessage(0, bitmap);
                handler.sendMessage(message);
            }
        }.start();
        return null;
    }
    //回调接口
    public interface ImageCallback {
        public void imageLoaded(Bitmap imageBitmap,ImageView imageView, String imagePath);
    }
}

通过这个实例,我验证了一下,一次性获取20张5MB的照片,都可以加载的很流畅,完全没有再出现报OOM的错误了

以下是运行效果

SD卡中的图片:

进入应用,选择11张照片进行滑动预览:

希望以上所写对大家有帮助,谢谢!

时间: 2024-09-27 17:31:08

彻底解决Android因加载多个大图引起的OutOfMemoryError,内存溢出的问题的相关文章

【转】解决Android因加载多个大图引起的OutOfMemoryError,内存溢出的问题

本文来自:http://blog.csdn.net/wulianghuan/article/details/11548373,感谢原作者的分享. 目标是读取SD卡中的图片并且展示出来 主要思路是通过一个工具类来压缩来自sd卡中的图片,最后通过缓存机制来避免占用过多内存. Util.java package com.kale.socketactivity; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; i

浅谈android中加载高清大图及图片压缩方式(二)

这一讲就是本系列的第二篇,一起来聊下关于android中加载高清大图的问题,我们都知道如果我们直接加载原图的话,一个是非常慢,需要等待一定时间,如果没有在一定的时间内给用户响应的话,将会极大影响用户的体验.另一个是如果你的手机内存小的话,可能会直接崩溃.这也就是直接加载高清原图问题.遇到这些问题很容易想到的一点就是图片压缩,本篇文章也就是讲述图片压缩方式来实现加载高清大图的效果.但是现在问题就来了,通过上篇博客我们知道,手机的分辨率有很多,如何保证我同一张图片在不同分辨率的手机上能适当的压缩比例

解决Android Studio加载第三方jar包,出现包重复加载的问题:

通过Maven中央库添加第三方jar包的时候,出现了重复加载jar包的问题,解决办法很简单去掉一个不让它去加载就OK了 一.错误 Error:Execution failed for task ':app:dexDebug'. > com.android.ide.common.internal.LoggedErrorException: Failed to run command: F:\zsl\sdk\build-tools\21.1.2\dx.bat --dex --output F:\zs

Android异步加载全解析之使用多线程

异步加载之使用多线程 初次尝试 异步.异步,其实说白了就是多任务处理,也就是多线程执行,多线程那就会有各种问题,我们一步步来看,首先,我们创建一个class--ImageLoaderWithoutCaches,从命名上,大家也看出来,这个类,我们实现的是不带缓存的图像加载,不多说,我们再创建一个方法--showImageByThread,通过多线程来加载图像: /** * Using Thread * @param imageView * @param url */ public void sh

Android异步加载全解析之使用AsyncTask

Android异步加载全解析之使用AsyncTask 概述 既然前面提到了多线程,就不得不提到线程池,通过线程池,不仅可以对并发线程进行管理,更可以提高他们执行的效率,优化整个App.当然我们可以自己创建一个线程池,不过这样是很烦的,要创建一个高效的线程池还是挺费事的,不过,Android系统给我吗提供了AsyncTask这样一个类,来帮助我们快速实现多线程开发,它的底层实现,其实就是一个线程池. AsyncTask初探 AsyncTask,顾名思义就是用来做异步处理的.通过AsyncTask,

Android异步加载全解析之大图处理

Android异步加载全解析之大图处理 异步加载中非常重要的一部分就是对图像的处理,这也是我们前面用异步加载图像做演示例子的原因.一方面是因为图像处理不好的话会非常占内存,而且容易OOM,另一方面,图像也比文字要大,加载比较慢.所以,在讲解了如何进行多线程.AsyncTask进行多线程加载后,先暂停下后面的学习,来对图像的异步处理进行一些优化工作. 为什么要对图像处理 为什么要对图像进行处理,这是一个很直接的问题,一张图像,不管你拿手机.相机.单反还是什么玩意拍出来,它就有一定的大小,但是在不同

实现Android 动态加载APK(Fragment or Activity实现)

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38565345 最近由于项目太大了,导致编译通不过(Android对一个应用中的方法个数貌似有限制),所以一直琢磨着能否将某些模块的APK不用安装,动态加载,通过在网上查找资料和网友的帮助,终于实现了APK的动态加载,网络上介绍APK动态加载的文章非常多,但是我觉得写得非常好的就是这位大牛的,我基本上就是使用他的这种方案,然后加入了自己的元素.这位大牛是通过Activity实现的,我稍作修改

Android动态加载框架DL的架构与基本原理解析

转载请注明出处,本文来自[ Mr.Simple的博客 ]. 我正在参加博客之星,点击这里投我一票吧,谢谢~ 前言 最近这一两年,Android App使用插件化技术开发的数量越来越大,其实还是业务地快速膨胀导致,需求越来越多,App越来越臃肿.虽然手机的内存空间不断地的增大,但是太大的安装包给用户也造成了心理压力.于是大家都会想到插件化的开发方式,把App做成一个平台,而不是一个独立的app.平台上可以集成各种各样的功能,功能模块也插件的形式添加进来,这些插件不需要安装,只需要用户按需下载到某个

android客户端加载网络大图片如何避免内存溢出

在Android开发中加载sdcard上的大图片到内存时容易导致OOM异常,常见的解决办法是基于BitmapFactory.Options类提供的方法定义指定的解码方式,设置inJustDecodeBounds属性为true,避免分配内存,返回一个null的Bitmap对象(包含outWidth,outHeightandoutMimeType),然后读取图片的尺寸和类型.再根据屏幕的高和宽对图片进行缩放,最后将缩放的图片加载到内存,主要代码如下: 1 Options opts = new Opt