功能: 線程池異步加載圖片(對圖片進行壓縮,內存控制)
可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);
做開發,需要腳踏實地,日積月累,願你我共勉