高效加载本地相册图片的ImageLoader类
来源:互联网 发布:飞腾排版软件5.0 编辑:程序博客网 时间:2024/06/03 20:50
当我们相册中的图片有几千张的时候,你快速的拖动滚动条到底部,怎么样才能保证图片加载的流畅性以及避免OOM呢
1.使用Lru算法对图片进行缓存保证流畅性以及避免OOM
2.图片加载肯定是要异步进行的,那么就涉及到多线程的并发进行,使用线程池对任务进行调度
3.使用android内部的异步消息机制Looper+Handler对taskQueue进行轮询执行
1.图片加载器,task任务列频繁调用,所以要做成单例模式,编写一个单例的ImageLoader类,并声明必要的成员变量
/** * @author Administrator *图片加载器,task任务列频繁调用,所以要做成单例模式 */public class ImageLoader {private static ImageLoader instance;/** * 图片缓存的核心对象 */private LruCache<String, Bitmap> mLruCache;/** * 线程池 */private ExecutorService mThreadPool;private static final int DEFAULT_THREAD_COUNT=1;/** * 队列调度方式 */private static Type mType=Type.LIFO;/** * 任务队列 */private LinkedList<Runnable> mTaskQueue;/** * 后台轮询线程 */private Thread mPoolThread;private Handler mPoolThreadHandler;/** *UI线程的Handler */private Handler mUIHandler;//任务队列的public enum Type{FIFO,LIFO;}/** * 获取ImageLoader的单利方法 * @return */public static ImageLoader getInstance(){//这里采用双重if判断,可以提高代码的执行效率//外层判断可能会有几个线程进到里面,然后线程同步加条件判断//保证单例唯一性if(instance==null){synchronized (ImageLoader.class) {if(instance==null){instance=new ImageLoader(DEFAULT_THREAD_COUNT,mType);}}}return instance;}
2.初始化成员变量
private ImageLoader(int threadCount,Type type){init(threadCount,type);}private Semaphore mSemaphorePoolThreadHandler=new Semaphore(0);private Semaphore mSemaphoreThreadPool;/** * 构造方法中初始化一大堆成员变量 * @param threadCount * @param type */private void init(int threadCount,Type type) {//初始化后台轮询线程mPoolThread=new Thread(){@Overridepublic void run() {Looper.prepare();//使用安卓异步消息机制处理mtaskQueue中的任务mPoolThreadHandler=new Handler(){@Overridepublic void handleMessage(Message msg) {//从线程池取出一个任务执行mThreadPool.execute(getTask());}};mSemaphorePoolThreadHandler.release();//handler初始化完成,添加一个许可证Looper.loop();}};//启动线程池mPoolThread.start();mTaskQueue=new LinkedList<Runnable>();//任务队列mThreadPool=Executors.newFixedThreadPool(threadCount);//线程池管理者int maxSize=(int) Runtime.getRuntime().maxMemory();int cacheSize=maxSize/8;mLruCache=new LruCache<String, Bitmap>(cacheSize){@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getRowBytes()*value.getHeight();}};mType=type;mSemaphoreThreadPool=new Semaphore(threadCount);}
3.编写和新方法loadImage()
/** * 图片加载的核心方法 * @param path * @param imageView */public void loadImage(final String path,final ImageView imageView){imageView.setTag(path);//带上标记,防止条目复用时出现乱跳现象mUIHandler=new Handler(){@Overridepublic void handleMessage(Message msg) {ImageHolder holder=(ImageHolder) msg.obj;Bitmap bitmap=holder.mBitmap;ImageView imageView=holder.mImageView;String url=holder.path;if(imageView.getTag().toString().equals(url)){imageView.setImageBitmap(bitmap);}}};Bitmap mBitmap=getBitmapFromLruCache(path);if(mBitmap!=null){refreshHandler(path, imageView, mBitmap);}else{addTask(imageView,path);}}private void addTask(final ImageView imageView,final String path) {mTaskQueue.add(new Runnable() {@Overridepublic void run() {//进行加载图片的逻辑//1.获取ImageView要显示的大小ImageSize imageSize = getImageSize(imageView);//2.开始压缩图片Bitmap mBitmap = compressBitmap(imageSize,path);//3.把图片添加到LruCache中去if(mLruCache.get(path)!=null){mLruCache.put(path, mBitmap);}//发送消息,让UiHandler把图片显示到imageview上去refreshHandler(path, imageView, mBitmap);//任务完成,可以释放许可,让后台轮询线程去处理下一个任务mSemaphoreThreadPool.release();}});try {if(mPoolThreadHandler==null){mSemaphorePoolThreadHandler.acquire();}} catch (InterruptedException e) {e.printStackTrace();}//发送消息,让后台轮询线程去处理任务堆mPoolThreadHandler.sendEmptyMessage(0X110);}/** * 把ImageView path Bitmap等信息发送到handler中去执行 * @param path * @param imageView * @param mBitmap */private void refreshHandler(final String path, final ImageView imageView, Bitmap mBitmap) {Message message=Message.obtain();ImageHolder holder=new ImageHolder();holder.mBitmap=mBitmap;holder.mImageView=imageView;holder.path=path;message.obj=holder;mUIHandler.sendMessage(message);}/** * 压缩图片 * @return */private Bitmap compressBitmap(ImageSize imageSize,String path){BitmapFactory.Options option=new BitmapFactory.Options();option.inJustDecodeBounds=true;//只解析图片的宽高等信息,而不把图片加载到内存中去BitmapFactory.decodeFile(path, option);int outWidth = option.outWidth;int outHeight = option.outHeight;int width=imageSize.width;int height=imageSize.height;int inSimpleSize=1;if(outHeight>height||outWidth>height){int widthRadio=Math.round(outWidth*1.0f/width);int heightRadio=Math.round(outHeight*1.0f/height);inSimpleSize=Math.max(widthRadio, heightRadio);}option.inJustDecodeBounds=false;option.inSampleSize=inSimpleSize;return BitmapFactory.decodeFile(path, option);}/** * 根据ImageView获取适当的压缩后的宽和高 * @param iv * @return */private ImageSize getImageSize(ImageView iv){LayoutParams lp = iv.getLayoutParams();//获取屏幕的矩阵DisplayMetrics dm = iv.getContext().getResources().getDisplayMetrics();int width=iv.getWidth();if(width<=0){width=lp.width;}if(width<=0){//使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本width=getMaxValueFromInvoke(iv,"mMaxWidth");}if(width<=0){width=dm.widthPixels;}int height=iv.getHeight();if(height<=0){height=lp.width;}if(height<=0){height=getMaxValueFromInvoke(iv,"mMaxHeight");}if(height<=0){height=dm.heightPixels;}return new ImageSize(width, height);}/** * 使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本 * @param obj * @param fieldName * @return */private int getMaxValueFromInvoke(Object obj,String fieldName){int value=0;try {Field field = ImageView.class.getDeclaredField(fieldName);field.setAccessible(true);int fieldValue = field.getInt(obj);if(fieldValue>0&&fieldValue<Integer.MAX_VALUE){value=fieldValue;}} catch (Exception e) {e.printStackTrace();}return value;}/** * 封装图片宽高的对象 * @author Administrator * */private class ImageSize{int width;int height;public ImageSize(int width,int height){this.width=width;this.height=height;}}//handler发送的msg.obj对象private class ImageHolder{Bitmap mBitmap;ImageView mImageView;String path;}//从缓存中去取图片private Bitmap getBitmapFromLruCache(String key) {return mLruCache.get(key);}
获取ImageView的最大宽高的时候为了兼容低版本,没使用getMaxWidth()这个api,而是采用了反射获取到的
其中后台轮询线程还没初始化mThreadPoolHandler的时候,mThreadPoolHandler就可能在loadImage()方法中被调用了,所以就存在空指针的风险
为了保证并发危险,采用java提供的一个并发信号量的类Semaphore 初始化的时候在构造方法中指定有几个许可证
有两个方法:
1.acquire()每执行一次消耗一个许可证,但许可证为0的时候,阻塞线程,直到Semaphore 再次调用release()获取到许可证,结束阻塞
2.release()每执行一次获取一个许可证
下面我再把整个类的代码全部粘过来
package com.example.fandayimageloader.imageloader;import java.lang.reflect.Field;import java.util.LinkedList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import android.annotation.SuppressLint;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.v4.util.LruCache;import android.util.DisplayMetrics;import android.view.ViewGroup.LayoutParams;import android.widget.ImageView;/** * 这个线程中的mPoolThreadHandler可能还没有初始化完成,但是在addTask到mTaskQueue后就要用到时,就会出现null painter exception * 解决这个可以使用java提供的Semaphore,并发一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(), * 然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数, * 并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。 * 简单来讲就是acquire()消耗掉一个许可,而release()添加一个许可,当许可为0的时候,想要再次acquire()就会阻塞 * public class SemaphoreTest { * * public static void main(String[] args) { * // 线程池 * ExecutorService exec = Executors.newCachedThreadPool(); * // 只能5个线程同时访问 * final Semaphore semp = new Semaphore(5); * // 模拟20个客户端访问 * for (int index = 0; index < 20; index++) { * final int NO = index; * Runnable run = new Runnable() { * public void run() { * try { * // 获取许可 * semp.acquire(); * System.out.println("Accessing: " + NO); * Thread.sleep((long) (Math.random() * 10000)); * // 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印5条记录,之后线程一直阻塞 * semp.release(); * } catch (InterruptedException e) { * } * } * }; * exec.execute(run); * } * // 退出线程池 * exec.shutdown(); * } *} *//** * @author Administrator *图片加载器,task任务列频繁调用,所以要做成单例模式 */public class ImageLoader {private static ImageLoader instance;/** * 图片缓存的核心对象 */private LruCache<String, Bitmap> mLruCache;/** * 线程池 */private ExecutorService mThreadPool;private static final int DEFAULT_THREAD_COUNT=1;/** * 队列调度方式 */private static Type mType=Type.LIFO;/** * 任务队列 */private LinkedList<Runnable> mTaskQueue;/** * 后台轮询线程 */private Thread mPoolThread;private Handler mPoolThreadHandler;/** *UI线程的Handler */private Handler mUIHandler;//任务队列的public enum Type{FIFO,LIFO;}/** * 获取ImageLoader的单利方法 * @return */public static ImageLoader getInstance(){//这里采用双重if判断,可以提高代码的执行效率//外层判断可能会有几个线程进到里面,然后线程同步加条件判断//保证单例唯一性if(instance==null){synchronized (ImageLoader.class) {if(instance==null){instance=new ImageLoader(DEFAULT_THREAD_COUNT,mType);}}}return instance;}private ImageLoader(int threadCount,Type type){init(threadCount,type);}private Semaphore mSemaphorePoolThreadHandler=new Semaphore(0);private Semaphore mSemaphoreThreadPool;/** * 构造方法中初始化一大堆成员变量 * @param threadCount * @param type */private void init(int threadCount,Type type) {//初始化后台轮询线程mPoolThread=new Thread(){@Overridepublic void run() {Looper.prepare();//使用安卓异步消息机制处理mtaskQueue中的任务mPoolThreadHandler=new Handler(){@Overridepublic void handleMessage(Message msg) {//从线程池取出一个任务执行mThreadPool.execute(getTask());}};mSemaphorePoolThreadHandler.release();//handler初始化完成,添加一个许可证Looper.loop();}};//启动线程池mPoolThread.start();mTaskQueue=new LinkedList<Runnable>();//任务队列mThreadPool=Executors.newFixedThreadPool(threadCount);//线程池管理者int maxSize=(int) Runtime.getRuntime().maxMemory();int cacheSize=maxSize/8;mLruCache=new LruCache<String, Bitmap>(cacheSize){@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getRowBytes()*value.getHeight();}};mType=type;mSemaphoreThreadPool=new Semaphore(threadCount);}/** * 从任务队列中根据Type的值取出任务 * @return */public Runnable getTask(){try {//每取一个任务就消耗一个许可,消耗完成将阻塞,等待释放许可mSemaphoreThreadPool.acquire();} catch (InterruptedException e) {e.printStackTrace();}if(mType==Type.FIFO){return mTaskQueue.removeFirst();}else if(mType==Type.LIFO){return mTaskQueue.removeLast();}return null;}/** * 图片加载的核心方法 * @param path * @param imageView */public void loadImage(final String path,final ImageView imageView){imageView.setTag(path);//带上标记,防止条目复用时出现乱跳现象mUIHandler=new Handler(){@Overridepublic void handleMessage(Message msg) {ImageHolder holder=(ImageHolder) msg.obj;Bitmap bitmap=holder.mBitmap;ImageView imageView=holder.mImageView;String url=holder.path;if(imageView.getTag().toString().equals(url)){imageView.setImageBitmap(bitmap);}}};Bitmap mBitmap=getBitmapFromLruCache(path);if(mBitmap!=null){refreshHandler(path, imageView, mBitmap);}else{addTask(imageView,path);}}private void addTask(final ImageView imageView,final String path) {mTaskQueue.add(new Runnable() {@Overridepublic void run() {//进行加载图片的逻辑//1.获取ImageView要显示的大小ImageSize imageSize = getImageSize(imageView);//2.开始压缩图片Bitmap mBitmap = compressBitmap(imageSize,path);//3.把图片添加到LruCache中去if(mLruCache.get(path)!=null){mLruCache.put(path, mBitmap);}//发送消息,让UiHandler把图片显示到imageview上去refreshHandler(path, imageView, mBitmap);//任务完成,可以释放许可,让后台轮询线程去处理下一个任务mSemaphoreThreadPool.release();}});try {if(mPoolThreadHandler==null){mSemaphorePoolThreadHandler.acquire();}} catch (InterruptedException e) {e.printStackTrace();}//发送消息,让后台轮询线程去处理任务堆mPoolThreadHandler.sendEmptyMessage(0X110);}/** * 把ImageView path Bitmap等信息发送到handler中去执行 * @param path * @param imageView * @param mBitmap */private void refreshHandler(final String path, final ImageView imageView, Bitmap mBitmap) {Message message=Message.obtain();ImageHolder holder=new ImageHolder();holder.mBitmap=mBitmap;holder.mImageView=imageView;holder.path=path;message.obj=holder;mUIHandler.sendMessage(message);}/** * 压缩图片 * @return */private Bitmap compressBitmap(ImageSize imageSize,String path){BitmapFactory.Options option=new BitmapFactory.Options();option.inJustDecodeBounds=true;//只解析图片的宽高等信息,而不把图片加载到内存中去BitmapFactory.decodeFile(path, option);int outWidth = option.outWidth;int outHeight = option.outHeight;int width=imageSize.width;int height=imageSize.height;int inSimpleSize=1;if(outHeight>height||outWidth>height){int widthRadio=Math.round(outWidth*1.0f/width);int heightRadio=Math.round(outHeight*1.0f/height);inSimpleSize=Math.max(widthRadio, heightRadio);}option.inJustDecodeBounds=false;option.inSampleSize=inSimpleSize;return BitmapFactory.decodeFile(path, option);}/** * 根据ImageView获取适当的压缩后的宽和高 * @param iv * @return */private ImageSize getImageSize(ImageView iv){LayoutParams lp = iv.getLayoutParams();//获取屏幕的矩阵DisplayMetrics dm = iv.getContext().getResources().getDisplayMetrics();int width=iv.getWidth();if(width<=0){width=lp.width;}if(width<=0){//使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本width=getMaxValueFromInvoke(iv,"mMaxWidth");}if(width<=0){width=dm.widthPixels;}int height=iv.getHeight();if(height<=0){height=lp.width;}if(height<=0){height=getMaxValueFromInvoke(iv,"mMaxHeight");}if(height<=0){height=dm.heightPixels;}return new ImageSize(width, height);}/** * 使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本 * @param obj * @param fieldName * @return */private int getMaxValueFromInvoke(Object obj,String fieldName){int value=0;try {Field field = ImageView.class.getDeclaredField(fieldName);field.setAccessible(true);int fieldValue = field.getInt(obj);if(fieldValue>0&&fieldValue<Integer.MAX_VALUE){value=fieldValue;}} catch (Exception e) {e.printStackTrace();}return value;}/** * 封装图片宽高的对象 * @author Administrator * */private class ImageSize{int width;int height;public ImageSize(int width,int height){this.width=width;this.height=height;}}//handler发送的msg.obj对象private class ImageHolder{Bitmap mBitmap;ImageView mImageView;String path;}//从缓存中去取图片private Bitmap getBitmapFromLruCache(String key) {return mLruCache.get(key);}}
最后要是想在外界调用ImageLoader的时候指定Type属性和线程池最大维护线程个数的话,可以再类中增加一个getInstance的方法重载
/** * 获取ImageLoader的单利方法的重载 * @return */public static ImageLoader getInstance(int threadCount,Type type){//这里采用双重if判断,可以提高代码的执行效率//外层判断可能会有几个线程进到里面,然后线程同步加条件判断//保证单例唯一性if(instance==null){synchronized (ImageLoader.class) {if(instance==null){instance=new ImageLoader(threadCount,type);}}}return instance;}
项目源码下载地址:下载源码
0 0
- 高效加载本地相册图片的ImageLoader类
- ImageLoader加载本地图片的工具类
- ImageLoader 加载本地图片
- imageloader加载本地图片
- imageLoader加载本地图片
- ImageLoader加载本地图片
- ImageLoader加载本地图片
- ImageLoader加载本地图片
- ImageLoader 加载本地图片
- android ImageLoader加载本地图片的工具类(使用方法)
- android ImageLoader加载本地图片的工具类
- android ImageLoader加载本地图片的工具类
- android ImageLoader加载本地图片的工具类
- android ImageLoader加载本地图片的工具类
- android ImageLoader加载本地图片的工具类
- android ImageLoader加载本地图片的工具类
- android ImageLoader加载本地图片的工具类
- ImageLoader本地加载图片记录
- 通过反射操作标识为包内访问权限的类
- 访问jar包外部properties文件
- 给想写操作系统的同学说的前期准备
- 枚举类
- 山东省第五届ACM大学生程序设计竞赛 Colorful Cupcakes
- 高效加载本地相册图片的ImageLoader类
- YTU 2335: 0-1背包问题
- java 中变量存储位置的区别 .
- Ant常见命令
- STL vector用法介绍
- HDU 4503 湫湫系列故事——植树节(组合概率)
- 转载/redhat安装gcc
- C#—异常应用(正则表达式)
- 2016华为软件精英挑战赛:初期算法设计方案及其代码实现