Android线程池异步加载图片,可LIFO,可FIFO加载,四级缓存
功能: 线程池异步加载图片(对图片进行压缩,内存控制)
可LIFO加载(后进先出)快速滑动时,优先加载当前页面显示的图片,防止等待
可FIFO加载(先进先出)
四级缓存(内存缓存,磁盘缓存,文件缓存,网络缓存)
项目地址:https://github.com/AndroidCloud/LIFOandFIFOImageLoader 如有不足,欢迎各位issues和开支优化
GitHub地址:https://github.com/AndroidCloud
最终实现效果:
技术路线(简要技术思路,具体实现详见GitHub的Demo):
1,LIFO和FIFO线程池
首先是加载方式
//两种加载方式,先进先出和先进后出 public enum LoadType { FIFO, LIFO }其次,封装ImageRunnable对象,实现线程优先级接口,此处优先级使用加入任务队列的时间来判断。
LIFO为任务时间迟的优先进行,FIFO为任务时间早的优先进行
public class ImageRunnable implements Runnable,Comparable<ImageRunnable>{ @Override public int compareTo(ImageRunnable ImageRunnable) { long my = this.getPriority(); long other = ImageRunnable.getPriority(); if (loadType==null)return (int)(other-my); if (loadType.equals(LoadType.LIFO)){ return (int)(other-my); }else { return (int)(my-other); } } //其余代码省略最后是线程池,此处控制线程数为可运行的处理器个数+1
private void initThreadPool() { int number=Runtime.getRuntime().availableProcessors()+1; imgThreadPool = new ThreadPoolExecutor(number, number, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>()); }2,缓存处理
首先是内存缓存的处理,此处取可用缓存的1/8来做缓存。
public class LruCacheHelper { private static LruCache<String, Bitmap> memCache; public LruCacheHelper(){ initMemCache(); } /** * 初始化内存缓存 */ public void initMemCache() { int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024); int cacheSize = maxMemory / 8; memCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount()/1024; } }; } /** * 从内存缓存中拿 */ public Bitmap getBitmapFromMem(String url) { return memCache.get(url); } /** * 加入到内存缓存中 */ public void putBitmapToMem(String url, Bitmap bitmap) { if (getBitmapFromMem(url) == null) { if (bitmap != null){ memCache.put(url, bitmap); } } } /**删除某个缓存*/ public void removeBitmapFromMem(String url){ memCache.remove(url); } /**删除全部缓存*/ public void removeBitmapAll(){ memCache.evictAll(); } }其次是磁盘缓存,此处默认磁盘缓存最大为100M,如果有SD卡则存在SD卡,没有则存在自带内存中
public class DiskLruCacheHelper { private DiskLruCache diskLruCache; private int DISK_CACHE_DEFAULT_SIZE = 100 * 1024 * 1024;// 文件缓存默认 100M public DiskLruCacheHelper(){ initDiskLruCache(); } /** * 初始化磁盘缓存 */ public void initDiskLruCache() { try { File cacheDir = getDiskCacheDir(MyApplication.context, "vmeet_bitmap"); if (!cacheDir.exists()) { cacheDir.mkdirs(); } diskLruCache = DiskLruCache.open(cacheDir, getAppVersion(MyApplication.context), 1, DISK_CACHE_DEFAULT_SIZE); } catch (IOException e) { e.printStackTrace(); } } public File getDiskCacheDir(Context context, String uniqueName) { String cachePath; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } return new File(cachePath + File.separator + uniqueName); } public int getAppVersion(Context context) { try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); return info.versionCode; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return 1; } public String hashKeyForDisk(String key) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(key.hashCode()); } return cacheKey; } public String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } /** * 从磁盘缓存中拿 */ public Bitmap getBitmapFromDisk(String url) { try { String key = hashKeyForDisk(url); DiskLruCache.Snapshot snapShot = diskLruCache.get(key); if (snapShot != null) { InputStream is = snapShot.getInputStream(0); Bitmap bitmap = BitmapFactory.decodeStream(is); return bitmap; } } catch (IOException e) { e.printStackTrace(); } return null; } /**加入磁盘缓存*/ public void putBitmapToDiskMem(String url,Bitmap bitmap){ try { String key = hashKeyForDisk(url); DiskLruCache.Editor editor = diskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); editor.commit(); } diskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } /**删除某个磁盘缓存*/ public void removeBitmapFromDiskMem(String url){ try { String key = hashKeyForDisk(url); diskLruCache.remove(key); } catch (IOException e) { e.printStackTrace(); } } /**删除全部磁盘缓存*/ public void removeBitmapAll(){ try { diskLruCache.delete(); /**重新初始化次磁盘缓存*/ initDiskLruCache(); } catch (IOException e) { e.printStackTrace(); } } public long getSize(){ return diskLruCache.size(); } }3,图片压缩,压缩过程为:得到ImageView的大小,再取到图片大小,计算出需要压缩的比例,然后进行压缩,得到大小合适的Bitmap,放入缓存中。防止Bitmap占用过多缓存。
public class ImageHelper { public ImageHelper(){ } /**取到图片大小,传入ImageView的大小,按照该大小进行压缩*/ public Bitmap getLocalImg(String path,int width, int height){ File file=new File(path); if (file.exists()){ // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, width, height); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile(path, options); return bitmap; }else{ return null; } } /**计算图片压缩大小*/ public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源图片的宽度 int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; if (width > reqWidth && height > reqHeight) { // 计算出实际宽度和目标宽度的比率 int widthRatio = Math.round((float) width / (float) reqWidth); int heightRatio = Math.round((float) width / (float) reqWidth); inSampleSize = Math.max(widthRatio, heightRatio); } return inSampleSize; } /**反射得到ImageView的大小*/ public ImgSize getImageViewSize(ImageView imageView) { ImgSize imageSize = new ImgSize(); final DisplayMetrics displayMetrics = imageView.getContext() .getResources().getDisplayMetrics(); final ViewGroup.LayoutParams params = imageView.getLayoutParams(); int width = params.width == ViewGroup.LayoutParams.WRAP_CONTENT ? 0 : imageView .getWidth(); // Get actual image width if (width <= 0) width = params.width; // Get layout width parameter if (width <= 0) width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check // maxWidth // parameter if (width <= 0) width = displayMetrics.widthPixels; int height = params.height == ViewGroup.LayoutParams.WRAP_CONTENT ? 0 : imageView .getHeight(); // Get actual image height if (height <= 0) height = params.height; // Get layout height parameter if (height <= 0) height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check // maxHeight // parameter if (height <= 0) height = displayMetrics.heightPixels; imageSize.setWidth(width); imageSize.setHeight(height); return imageSize; } public int getImageViewFieldValue(Object object, String fieldName) { int value = 0; try { Field field = ImageView.class.getDeclaredField(fieldName); field.setAccessible(true); int fieldValue = (Integer) field.get(object); if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) { value = fieldValue; Log.e("TAG", value + ""); } } catch (Exception e) { } return value; } }
4,图片加载流程
加载时使用Handler进行线程通信,通信介质为ImgViewBean,加载过程为内存缓存→磁盘缓存→文件缓存 → 网络资源(实现四级缓存)
public class ImgViewBean { public ImageView imageView;//对应的ImageView public String url;//图片的地址 public Bitmap bitmap;//用来显示的Bitmap
public class ImageRunnable implements Runnable,Comparable<ImageRunnable>{ @Override public void run() { Bitmap bitmap; bitmap=lruCacheHelper.getBitmapFromMem(url); if (bitmap!=null){ sendImgToUiThread(bitmap); }else{ bitmap=diskLruCacheHelper.getBitmapFromDisk(url); if (bitmap!=null){ lruCacheHelper.putBitmapToMem(url, bitmap); sendImgToUiThread(bitmap); }else{ ImgSize imgSize= imageHelper.getImageViewSize(imageView); bitmap= imageHelper.getLocalImg(url, imgSize.getWidth(), imgSize.getHeight()); if (bitmap!=null){ lruCacheHelper.putBitmapToMem(url, bitmap); diskLruCacheHelper.putBitmapToDiskMem(url,bitmap); sendImgToUiThread(bitmap); }else { //去网络上加载 } } } } /**将Bitmap发送到UI线程*/ private void sendImgToUiThread(Bitmap bitmap) { ImgViewBean imgViewBean=new ImgViewBean(imageView,url,bitmap); Message message = Message.obtain(); message.obj = imgViewBean; handler.sendMessage(message); } //其余代码省略UI线程刷新界面,为防止错位,使用图片的Path作为Tag
handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //显示图片,防止错位 ImgViewBean imgViewBean=(ImgViewBean)msg.obj; if (imgViewBean.getImageView().getTag().toString().equals(imgViewBean.getUrl())){ imgViewBean.getImageView().setImageBitmap(imgViewBean.getBitmap()); } } };5,准备工作就绪,封装图片加载类,留出加载接口。
首先是保证单例模式,此处采用懒汉模式
public class ImageShowUtil { private static ImageShowUtil imageUtil; private ExecutorService imgThreadPool;/**加载图片线程池*/ private Handler handler;/**线程间通信*/ private ImageHelper imageHelper;/**加载图片帮助类*/ private LruCacheHelper lruCacheHelper;/**内存缓存帮助类*/ private DiskLruCacheHelper diskLruCacheHelper;/**磁盘缓存帮助类*/ private ImageShowUtil(){ initThreadPool(); imageHelper=new ImageHelper(); lruCacheHelper=new LruCacheHelper(); diskLruCacheHelper=new DiskLruCacheHelper(); handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //显示图片,防止错位 ImgViewBean imgViewBean=(ImgViewBean)msg.obj; if (imgViewBean.getImageView().getTag().toString().equals(imgViewBean.getUrl())){ imgViewBean.getImageView().setImageBitmap(imgViewBean.getBitmap()); } } }; } private void initThreadPool() { int number=Runtime.getRuntime().availableProcessors()+1; imgThreadPool = new ThreadPoolExecutor(number, number, 0L, TimeUnit.SECONDS, new PriorityBlockingQueue<Runnable>()); } /**单例模式*/ public static ImageShowUtil getInstance(){ if (imageUtil == null) { synchronized (ImageShowUtil.class) { if (imageUtil == null) { imageUtil = new ImageShowUtil(); } } } return imageUtil; }其次是图片加载方法,初步展示4中加载方式
/**加载本地图片指定了任务队列的加载方式,且使用占位图*/ public void loadImg(LoadType loadType,String path,ImageView imageView,int placeHolder){ if (imageView.getTag()==null){ imageView.setTag(path); imageView.setImageResource(placeHolder); }else if (!imageView.getTag().toString().equals(path)) { imageView.setTag(path); imageView.setImageResource(placeHolder); } imgThreadPool.execute(new ImageRunnable(loadType, imageView, path, handler, imageHelper, lruCacheHelper, diskLruCacheHelper)); } /**加载本地图片未指定任务队列的加载方式(默认LIFO),且使用占位图*/ public void loadImg(String path,ImageView imageView,int placeHolder){ if (imageView.getTag()==null){ imageView.setTag(path); imageView.setImageResource(placeHolder); }else if (!imageView.getTag().toString().equals(path)) { imageView.setTag(path); imageView.setImageResource(placeHolder); } imgThreadPool.execute(new ImageRunnable(imageView, path,handler,imageHelper,lruCacheHelper,diskLruCacheHelper)); } /**加载本地图片指定了任务队列的加载方式,未使用占位图*/ public void loadImg(LoadType loadType,String path,ImageView imageView){ if (imageView.getTag()==null){ imageView.setTag(path); }else if (!imageView.getTag().toString().equals(path)) { imageView.setTag(path); } imgThreadPool.execute(new ImageRunnable(loadType,imageView, path,handler,imageHelper, lruCacheHelper,diskLruCacheHelper)); } /**加载本地图片未指定任务队列的加载方式(默认LIFO),未使用占位图*/ public void loadImg(String path,ImageView imageView){ if (imageView.getTag()==null){ imageView.setTag(path); }else if (!imageView.getTag().toString().equals(path)) { imageView.setTag(path); } imgThreadPool.execute(new ImageRunnable(imageView, path,handler,imageHelper,lruCacheHelper,diskLruCacheHelper)); }
6,在ListView中使用示例,在Adapter的getView方法中使用
ImageShowUtil.getInstance().loadImg(list.get(i),viewHolder.iv_listview,R.drawable.b);
做开发,需要脚踏实地,日积月累,愿你我共勉