android图片处理

由于比较多的朋友加我qq询问我问题,我把代码就放在github上吧,谢谢大家的支持

github地址:https://github.com/washpan/SimpleImageLoader

安卓在加载大图的时候经常会出现oom的错误,给大家分享我的一些处理经验。
大家可以参考android官网的例子bitmapfun,里面有很多地方可以借鉴。
http://developer.android.com/training/displaying-bitmaps/index.html
1. 为了快速加载图片,我们需要将其缓存一部分到内存中,这里我们选用LruCache,我重写sizeOf方法和entryRemoved

继承LruCache实现我们自己的cache类

package com.washpan.bitmaptool;

import android.support.v4.util.LruCache;

/**
 * @author washpan [email protected] 用于预读Bitmap对象的LruCache
 * */
public class BitmapLruCache extends LruCache<String, SafeBitmap> {

	/**
	 * 删除时是否释放
	 * */
	public boolean isRecycleWhenRemove = false;
	public BitmapLruCache(int maxSize) {
		super(maxSize);
	}

	/**
	 * 如果是释放操作判断是否需要释放,标记释放位
	 * */
	@Override
	protected void entryRemoved(boolean evicted, String key, SafeBitmap oldValue,
			SafeBitmap newValue) {
		super.entryRemoved(evicted, key, oldValue, newValue);
		if (evicted) {
			if (null != oldValue && isRecycleWhenRemove) {
				if (null != oldValue.bitmap && oldValue.bitmap.isRecycled()) {
					oldValue.bitmap.recycle();
				}
			}
		}
	}

	/**
	 * 这里计算Bitmap的大小
	 * */
	@Override
	protected int sizeOf(String key, SafeBitmap value) {
		return BitmapDecodeTool.sizeOfBitmap(value.bitmap, value.config);
	}

}

2. 为了控制释放,编写一个含有Bitmap对象的数据结构,该结构包含了图片类型,方便计算占用空间大小。


SafeBitmap类代码

package com.washpan.bitmaptool;

import android.graphics.Bitmap;
/**
 * @author washpan [email protected]
 * 用于记录Bitmap的数据结构
 * */
public class SafeBitmap {
	/**
	 * Bitmap对象
	 * */
	public Bitmap bitmap;
	/**
	 * Bitmap的格式
	 * */
	public Bitmap.Config config;
}

BitmapDecodeTool代码


package com.washpan.bitmaptool;

import java.io.File;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Build;
import android.os.Debug;
/**
 * @author washpan [email protected] 用于解码bitmap的工具类
 * */
public class BitmapDecodeTool {
	
	public static synchronized Bitmap decodeBitmap(String filename, int width,
			int height, int maxMultiple,Bitmap.Config config,boolean isScale) {
		//只加载基础信息,并不真正解码图片
		final BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		// 这里进行假解码,获取宽高等信息,此时返回的bitmap是null的,因为并不真正进行解码
		BitmapFactory.decodeFile(filename, options);
		if (options.outWidth < 1 || options.outHeight < 1) {
			String fn = filename;
			File ft = new File(fn);
			if (ft.exists()) {
				ft.delete();
				return null;
			}
		}
		options.inPreferredConfig = config;
		//计算缩放率
		options.inSampleSize = calculateOriginal(options, width, height, maxMultiple);
		// 设置为false开始真的解码图片
		options.inJustDecodeBounds = false;
		Bitmap bm1 = null;
		try {
			bm1 = BitmapFactory.decodeFile(filename, options);
		} catch (Error e) {
			//发生错误进行再次压缩
			Runtime.getRuntime().runFinalization();
			try {
				Thread.sleep(600);

				options.inSampleSize += 1;
				options.inJustDecodeBounds = false;
				options.inDither = true;
				options.inPreferredConfig = null;
				try {
					bm1 = BitmapFactory.decodeFile(filename, options);
				} catch (Error e2) {
					//还错,继续压缩并且在sdcard中建立缓存区
					Runtime.getRuntime().runFinalization();
					try {
						Thread.sleep(600);
						options.inSampleSize += 1;
						//内存不足的情况下尝试在sdcard开辟空间存储内存
						options.inTempStorage = new byte[12 * 1024];
						options.inJustDecodeBounds = false;
						options.inDither = true;
						options.inPreferredConfig = null;

						try {
							bm1 = BitmapFactory.decodeFile(filename, options);
						} catch (Error e4) {
							//实在不行了返回null,解码失败
							Runtime.getRuntime().runFinalization();
							bm1 = null;
						}

					} catch (InterruptedException e3) {

					}
				}

			} catch (InterruptedException e1) {
			}
		}
		if (bm1 == null) {
			return null;
		}
		if (!isScale) {
			return bm1;
		}
		//等比缩放
		int queryWidth = width;
		int queryHeight = height;
		int resWidth = bm1.getWidth();
		int resHeight = bm1.getHeight();
		float scaleWidth = ((float) queryWidth) / resWidth;
		float scaleHeight = ((float) queryHeight) / resHeight;
		Bitmap bm;
		try {
			if (scaleWidth >= 1 && scaleHeight >= 1) {
				bm = bm1;
			} else if (scaleHeight >= 1 && scaleWidth < 1) {
				int cutH = resHeight;
				int cutW = queryWidth * cutH / queryHeight;
				int cutX = resWidth / 2 - cutW / 2;
				bm = Bitmap.createBitmap(bm1, cutX, 0, cutW, cutH);
			} else if (scaleWidth >= 1 && scaleHeight < 1) {
				int cutW = resWidth;
				int cutH = queryHeight * cutW / queryWidth;
				bm = Bitmap.createBitmap(bm1, 0, 0, cutW, cutH);
			} else {
				float scale = scaleHeight < scaleWidth ? scaleWidth
						: scaleHeight;
				Matrix matrix = new Matrix();
				matrix.postScale(scale, scale);
				bm = Bitmap.createBitmap(bm1, 0, 0, resWidth, resHeight,
						matrix, true);
			}
			if(null !=bm1 && !bm1.isRecycled()){
				bm1.recycle();
			}
		} catch (Exception e) {
			bm = bm1;
		}
		return bm;
	}

	/**
	 * 计算缩放比例
	 * */
	private static int calculateOriginal(BitmapFactory.Options options,
			int reqWidth, int reqHeight, int maxMultiple) {
		int inSampleSize = 1;
		final int height = options.outHeight;
		final int width = options.outWidth;

		if (height > reqHeight || width > reqWidth) {
			if (width > height) {
				inSampleSize = Math.round((float) height / (float) reqHeight);
			} else {
				inSampleSize = Math.round((float) width / (float) reqWidth);
			}
			final float totalPixels = width * height;
			final float totalReqPixelsCap = (reqWidth * reqHeight * maxMultiple);

			//按允许的倍数计算缩放倍数
			while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
				inSampleSize++;
			}
			//检测是否有足够的内存对缩放倍数进行缩放,不行则继续缩放
			while(!CheckBitmapFitsInMemory(reqWidth*maxMultiple/inSampleSize, reqHeight*maxMultiple/inSampleSize, options.inPreferredConfig)){
				inSampleSize++;
			}
		}
		return inSampleSize;
	}

	/**
	 * 按照宽高计算bitmap所占内存大小
	 * */
	public static long GetBitmapSize(long bmpwidth, long bmpheight,
			Bitmap.Config config) {
		return bmpwidth * bmpheight *  getBytesxPixel(config);
	}

	/**
	 * 计算bitmap所占空间,单位bytes
	 * */
	@SuppressLint("NewApi")
	public static int sizeOfBitmap(Bitmap bitmap, Bitmap.Config config) {
		int size = 1;
		// 3.1或者以上
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
			size = bitmap.getByteCount() * getBytesxPixel(config)>>10;
		}
		// 3.1以下
		else {
			size = bitmap.getRowBytes() * bitmap.getHeight()
					* getBytesxPixel(config)>>10;
		}
		return size;

	}

	/**
	 * 按照不同格式计算所占字节数
	 * */
	public static int getBytesxPixel(Bitmap.Config config) {
		int bytesxPixel = 1;
		// 3.1或者以上
		switch (config) {
		case RGB_565:
		case ARGB_4444:
			bytesxPixel = 2;
			break;
		case ALPHA_8:
			bytesxPixel = 1;
			break;
		case ARGB_8888:
			bytesxPixel = 4;
			break;
		}
		return bytesxPixel;
	}

	/**
	 * 当前空闲的堆内存
	 * */
	public static long FreeMemory() {
		return Runtime.getRuntime().maxMemory()
				- Debug.getNativeHeapAllocatedSize();
	}

	/**
	 * 检测当前是否有足够的内存进行读取bitmap
	 * */
	public static boolean CheckBitmapFitsInMemory(long bmpwidth,
			long bmpheight, Bitmap.Config config) {
		return (GetBitmapSize(bmpwidth, bmpheight, config) < FreeMemory());
	}
}

3.编写加载器,最好不要用线程池进行图片加载,当2个或者以上的线程(如果是多核cpu)同时加载大图的时候,oom异常的几率会加大,并且很难控制,例子中我是起一个线程在锁队列中一个一个任务进行执行。

BitmapLoader 代码


package com.washpan.bitmaptool;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingDeque;

import android.graphics.Bitmap;
import android.widget.ImageView;
/**
 * @author washpan [email protected] 用于加载bitmap的装载器
 * */
public class BitmapLoader {
	/**
	 * 当快速滑动时不加载
	 * */
	public static final Object LOCKWHENSCROLL = new Object();
	private BitmapLruCache cache = null;
	private boolean isPause = false;
	private String path = "";
	private int defaultWidth = 100;
	private int defaultHeight = 100;
	private boolean isStop = false;
	private Thread loadingThread = null;
	private ArrayList<LoadBitmapListener> listeners = new ArrayList<BitmapLoader.LoadBitmapListener>();
	/**
	 * 通过view找到正在执行的task,像listView重用item时快速滑动时旧的view所对应的task如果未启动就没有必要让其再启动了,因为会很耗资源
	 * */
	private HashMap<ImageView, LoadingTask> views = new HashMap<ImageView, LoadingTask>();

	/**
	 * 加载锁队列
	 * */
	private LinkedBlockingDeque<LoadingTask> tasks = new LinkedBlockingDeque<BitmapLoader.LoadingTask>();

	class LoadingTask implements Runnable {
		private String url = "";
		private ImageView view = null;
		public boolean isWorking = false;

		public LoadingTask(String url, ImageView view) {
			this.url = url;
			this.view = view;
		}

		public boolean isStop = false;

		@Override
		public void run() {

			// 用于快速滑动时锁住加载操作
			synchronized (LOCKWHENSCROLL) {
				while (isPause) {
					try {
						LOCKWHENSCROLL.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

			}
			isWorking = true;
			// 先从本地加载
			SafeBitmap safeBitmap = loadLocal(url);
			if (null != safeBitmap && null != safeBitmap.bitmap) {
				// 放入缓存中
				synchronized (cache) {
					cache.put(NameUtils.generateKey(url), safeBitmap);
				}

				synchronized (listeners) {
					for (LoadBitmapListener l : listeners) {
						l.onFinish(view, safeBitmap.bitmap);
					}
				}
			}
			// 从网络加载
			else {
				HttpDownloadPic dowload = new HttpDownloadPic();
				File cacheFile = new File(path, NameUtils.generateKey(url));
				try {
					dowload.downloadPic(cacheFile, url);

					if (null != cacheFile && cacheFile.exists()) {
						safeBitmap = loadLocal(url);
						if (null != safeBitmap && null != safeBitmap.bitmap) {
							// 放入缓存中
							synchronized (cache) {
								cache.put(NameUtils.generateKey(url),
										safeBitmap);
							}
							synchronized (listeners) {
								for (LoadBitmapListener l : listeners) {
									l.onFinish(view, safeBitmap.bitmap);
								}
							}
						}
					} else {
						synchronized (listeners) {
							for (LoadBitmapListener l : listeners) {
								l.onError(view);
							}
						}
					}
				} catch (IOException e) {
					synchronized (listeners) {
						for (LoadBitmapListener l : listeners) {
							l.onError(view);
						}
					}
				}finally{
					synchronized (views) {
						views.remove(view);
					}
				}
			}

		}

	}

	public void setPause(boolean isPause) {
		this.isPause = isPause;
		if (!isPause) {
			synchronized (LOCKWHENSCROLL) {
				LOCKWHENSCROLL.notifyAll();
			}
		}
	}

	public BitmapLoader(BitmapLruCache cache, String path, int defaultWidth,
			int defaultHeight) {
		this.cache = cache;
		this.path = path;
		this.defaultWidth = defaultWidth;
		this.defaultHeight = defaultHeight;
		loadingThread = new Thread(new Worker());
		loadingThread.start();

	}

	public class Worker implements Runnable {
		public void run() {
			while (!isStop) {
				LoadingTask task = null;
				task = tasks.poll();
				if (null != task && !task.isStop) {
					task.run();
				}
			}
		}
	}

	public interface LoadBitmapListener {
		void onFinish(final ImageView view, final Bitmap bitmap);

		void onError(final ImageView view);
	}

	public synchronized void loadBitmap(final ImageView view, final String url,
			final LoadBitmapListener listener) {
		// 停掉之前的任务
		synchronized (views) {
			LoadingTask oldTask = views.get(view);
			if (null != oldTask && !oldTask.isWorking) {
				oldTask.isStop = true;
			}
		}

		synchronized (listeners) {
			listeners.add(listener);
		}
		LoadingTask task = new LoadingTask(url, view);
		synchronized (views) {
			views.put(view, task);
		}
		try {
			tasks.put(task);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * 从本地加载图片
	 * */
	private SafeBitmap loadLocal(String url) {

		SafeBitmap safeBitmap = new SafeBitmap();
		safeBitmap.bitmap = BitmapDecodeTool.decodeBitmap(path + File.separator
				+ NameUtils.generateKey(url), defaultWidth, defaultHeight, 3,
				Bitmap.Config.RGB_565, false);
		safeBitmap.config = Bitmap.Config.RGB_565;
		return safeBitmap;
	}

	public SafeBitmap getCahe(String key) {
		return this.cache.get(key);
	}

	public void cleanCache(boolean isRecycle) {
		this.cache.isRecycleWhenRemove = isRecycle;
		this.cache.evictAll();
		this.isStop = true;
		synchronized (views) {
			views.clear();
		}
		synchronized (listeners) {
			listeners.clear();
		}
	}
}

4. 如果使用ListView等列表控件快速滑动时请不要进行加载,否则oom也很容易出现。


完整源码下载地址

http://download.csdn.net/detail/anuojieyanjiu/5331190

优化后的版本下载地址,以下的为修改后的代码。

http://download.csdn.net/detail/anuojieyanjiu/5331505

发现如果要显示默认图片,比如加载图片时的loading页面,最好只解码一次,以后直接使用该bitmap对象,而不要每次使用的时候都进行decode,那样内存会消耗很大。

上个版本由于及时标记了bitmap的recycle标记位,有时候会发生recycled bitmap use的异常,修复之后的版本下载地址

http://download.csdn.net/detail/anuojieyanjiu/5331961

如果还不放心,可以关掉某个activity的硬件加速,

AndroidManifest.xml中某个activity中配置以下信息即可关闭硬件加速

android:hardwareAccelerated="false"

如果你关闭掉了硬件加速,那么释放缓存可以不用那么及时,例子中的BigImageActivity类的destroyItem方法中的

//硬件加速关闭的情况下以下代码可以屏蔽掉

String url = TestData.imageUrls[position];
SafeBitmap safeBitmap = loader.getCahe(NameUtils.generateKey(url));
if(null !=safeBitmap && null !=safeBitmap.bitmap&& !safeBitmap.bitmap.isRecycled()){
safeBitmap.bitmap.recycle();
loader.removeCache(NameUtils.generateKey(url));
System.runFinalization();
}

这几行代码都可以注释掉

关闭硬件加速后的版本下载地址

http://download.csdn.net/detail/anuojieyanjiu/5332383

你可能感兴趣的:(android,bitmap,oom,图片处理)