高效加载本地相册图片的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
原创粉丝点击