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);


                              做開發,需要腳踏實地,日積月累,願你我共勉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章