圖片加載框架

之前實(shí)現(xiàn)了一個(gè)選擇本地圖片進(jìn)行加載顯示的選擇器,利用Glide作為圖片加載器,Glide是一個(gè)十分方便的圖片加載庫,在項(xiàng)目中使用Glide也十分方便。由于項(xiàng)目需要自己搭建一個(gè)圖片加載框架,實(shí)現(xiàn)功能比較簡單,查閱資料后開始著手實(shí)現(xiàn)。

一、 實(shí)現(xiàn)目標(biāo)

要實(shí)現(xiàn)的圖片加載器主要是加載網(wǎng)絡(luò)圖片進(jìn)行顯示,加入LruCache進(jìn)行內(nèi)存緩存,在進(jìn)行列表顯示的時(shí)候加載重復(fù)圖片可以直接從緩存中取圖片進(jìn)行顯示,節(jié)約了網(wǎng)絡(luò)資源,不用再重復(fù)下載。但是這個(gè)內(nèi)存緩存只能在程序運(yùn)行時(shí)分配到內(nèi)存才能進(jìn)行緩存,在程序結(jié)束時(shí)緩存也就釋放了,下一次打開程序還是要重新下載。利用硬盤緩存DiskLruCache作為一個(gè)二級(jí)緩存目錄,將下載的圖片保存到本地緩存,由于本地緩存目錄是默認(rèn)創(chuàng)建的可以隨程序卸載而刪除,也可以顯示調(diào)用刪除緩存的方法進(jìn)行清除,因此不用擔(dān)心緩存占用很大空間的問題。

二、 框架實(shí)現(xiàn)

1. 線程池

在進(jìn)行圖片下載的時(shí)候要開辟一個(gè)新線程進(jìn)行耗時(shí)操作,往往在加載列表顯示的圖片時(shí)會(huì)一次性開啟很多線程,這里使用線程池來進(jìn)行管理線程,利用一個(gè)任務(wù)Map進(jìn)行管理,如果該圖片地址存在線程正在進(jìn)行下載,就不會(huì)創(chuàng)建新的線程,而是等待線程的下載完成。之前已經(jīng)進(jìn)行過線程池的使用,在之前的基礎(chǔ)上進(jìn)行開發(fā)。

2. LruCache

上一篇日記是關(guān)于LruCache的使用的,在使用內(nèi)存緩存進(jìn)行下載任務(wù)的緩存已經(jīng)搞懂了,具體的實(shí)現(xiàn)代碼見上一篇。

3. DiskLruCache

硬盤緩存是在LruCache的基礎(chǔ)上進(jìn)行改進(jìn)的緩存機(jī)制,相比于內(nèi)存緩存,硬盤緩存可以把緩存保存到本地,利用生成的key進(jìn)行保存和獲取,這個(gè)key可以根據(jù)傳入的網(wǎng)絡(luò)地址進(jìn)行生成,得到一個(gè)哈希值,在取值的時(shí)候傳入要查找的key就可以找到緩存文件,進(jìn)行解碼顯示。
首先新建一個(gè)類,在這個(gè)類的基礎(chǔ)上進(jìn)行

public class ImageDiskCache {

    /**
     * 得到硬盤緩存目錄
     * @param context
     * @param uniqueName
     * @return
     */
    public static 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);
    }

    /**
     * 得到版本號(hào)
     * @param context
     * @return
     */
    public static int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    /**
     * 獲得緩存文件hash值
     * @param key
     * @return
     */
    public static 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;
    }

    private static 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();
    }
}

創(chuàng)建一個(gè)DiskLruCache對(duì)象不能直接new,要調(diào)用open方法進(jìn)行

try {
            File cacheDir = ImageDiskCache.getDiskCacheDir (context, "bitmap");
            if(!cacheDir.exists ()) {
                cacheDir.mkdirs ();
            }
            //創(chuàng)建硬盤緩存實(shí)例
            mDiskLruCache = DiskLruCache.open (cacheDir, ImageDiskCache.getAppVersion (context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace ();
        }

下載完成時(shí)把緩存文件保存到本地

String key = ImageDiskCache.hashKeyForDisk (imageUrl);
                    DiskLruCache.Editor editor = mDiskLruCache.edit (key);
                    if(editor != null) {
                        OutputStream outputStream = editor.newOutputStream (0);
                        if(downloadStream (imageUrl, outputStream)) {
                            editor.commit ();
                        } else {
                            editor.abort ();
                        }
                    }
                    mDiskLruCache.flush ();

加載圖片時(shí)首先訪問內(nèi)存緩存,如果內(nèi)存緩存有就調(diào)用,沒有繼續(xù)訪問硬盤緩存,如果硬盤緩存也沒用再開啟一個(gè)新的下載線程進(jìn)行下載顯示

public void loadImage(String imageUrl, ImageView iv) {
        iv.setTag (imageUrl);
        Bitmap bitmap = getBitmapFromCache (imageUrl);
        if(bitmap != null) {
            iv.setImageBitmap (bitmap);
            Log.i (TAG, "cache image");
        } else {
            try {
                String key = ImageDiskCache.hashKeyForDisk (imageUrl);
                DiskLruCache.Snapshot snapShot = mDiskLruCache.get (key);
                if(snapShot != null) {
                    InputStream is = snapShot.getInputStream (0);
                    bitmap = BitmapFactory.decodeStream (is);
                    iv.setImageBitmap (bitmap);
                    Log.i (TAG, "disk cache image");
                }
            } catch (IOException e) {
                e.printStackTrace ();
            }
        }
        if(bitmap == null) {
            if(!mLoadMap.containsKey (imageUrl)) {
                ImageDownloaderTask task = new ImageDownloaderTask (imageUrl, iv);
                mLoadMap.put (imageUrl, task);
                try {
                    mThreadPoolExecutor.execute (task);
                } catch (Exception e) {
                    mLoadMap.remove (imageUrl);
                }
            }else {
                ImageDownloaderTask task = mLoadMap.get (imageUrl);
                task.run ();
            }
            Log.i (TAG, "download image");
        }
    }

4. 自定義ImageView

從網(wǎng)絡(luò)上下載的圖片尺寸不一,在使用一般的ImageView進(jìn)行顯示的時(shí)候不能實(shí)現(xiàn)按圖片比例進(jìn)行填充屏幕的顯示,在顯示圖片的時(shí)候?qū)挾裙潭槭謾C(jī)屏幕尺寸,高度隨圖片比例進(jìn)行顯示。

public class ImageLoadView extends android.support.v7.widget.AppCompatImageView {
    public ImageLoadView(Context context) {
        super (context);
    }

    public ImageLoadView(Context context, @Nullable AttributeSet attrs) {
        super (context, attrs);
    }

    public ImageLoadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super (context, attrs, defStyleAttr);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Drawable drawable = getDrawable ();
        if(drawable != null){
            int width = drawable.getMinimumWidth();
            int height = drawable.getMinimumHeight();
            //確定顯示比例,寬高比不變
            float scale = (float)height/width;
            int widthMeasure = MeasureSpec.getSize(widthMeasureSpec);
            int heightMeasure = (int)(widthMeasure*scale);
            heightMeasureSpec =  MeasureSpec.makeMeasureSpec(heightMeasure, MeasureSpec.EXACTLY);
        }
        super.onMeasure (widthMeasureSpec, heightMeasureSpec);
    }


}

三、 完整代碼

package com.ruadong.utils.imageloader;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.Toast;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* @author ruandong
* @create 2018/8/18
* @package com.ruadong.utils
* @Describe 圖片加載器
*/
public class ImageLoader {

   private static final String TAG = "ImageLoader";
   /**
    * 返回Java虛擬機(jī)可用的處理器數(shù)量
    */
   private static final int CPU_COUNT = Runtime.getRuntime ().availableProcessors ();
   /**
    * 線程池基本大小
    */
   private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
   /**
    * 線程池最大線程數(shù)
    */
   private static final int MAXIMUM_POOL_SIZE = CPU_COUNT + 1;
   /**
    * 空閑線程存活時(shí)間為2秒
    */
   private static final int KEEP_ALIVE_TIME = 2;
   /**
    * 最大任務(wù)隊(duì)列數(shù)
    */
   private static final int MAX_TASK_COUNT = 1000;
   /**
    * 內(nèi)存緩存最大值
    */
   private static final int MAX_CACHE_SIZE = (int) (Runtime.getRuntime ().maxMemory () / 8);
   /**
    * 圖片加載器實(shí)例
    */
   private static volatile ImageLoader mImageLoader;
   /**
    * 圖片加載線程池
    */
   private ThreadPoolExecutor mThreadPoolExecutor;
   /**
    * 任務(wù)隊(duì)列
    */
   private BlockingQueue<Runnable> mBlockingDeque;
   /**
    * 下載集合
    */
   private Map<String, ImageDownloaderTask> mLoadMap;
   /**
    * 主線程handler
    */
   private Handler mMainHandler;
   /**
    * 上下文
    */
   private Context mContext;
   /**
    * 內(nèi)存緩存LruCache
    */
   private static LruCache<String, Bitmap> mLruCache;
   /**
    * 硬盤緩存DiskLruCache作為圖片加載器的二級(jí)緩存,可以緩存從內(nèi)存緩存中remove的bitmap,利用open方法創(chuàng)建實(shí)例
    */
   private DiskLruCache mDiskLruCache;

   /**
    * 圖片加載器構(gòu)造器
    */
   private ImageLoader() {
   }

   /**
    * 單例模式,獲取圖片加載器實(shí)例
    *
    * @return
    */
   public static ImageLoader getInstance() {
       if(mImageLoader == null) {
           synchronized (ImageLoader.class) {
               if(mImageLoader == null) {
                   mImageLoader = new ImageLoader ();
               }
           }
       }
       return mImageLoader;
   }

   /**
    * 初始化ImageLoader
    *
    * @param context
    */
   public void initImageLoader(Context context) {
       this.mContext = context.getApplicationContext ();
       mBlockingDeque = new LinkedBlockingDeque<> ();
       mThreadPoolExecutor = new ThreadPoolExecutor (CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mBlockingDeque);
       mMainHandler = new Handler ();
       mLruCache = new LruCache<String, Bitmap> (MAX_CACHE_SIZE) {
           @Override
           protected int sizeOf(String key, Bitmap value) {
               return value.getByteCount ();
           }
       };
       try {
           File cacheDir = ImageDiskCache.getDiskCacheDir (context, "bitmap");
           if(!cacheDir.exists ()) {
               cacheDir.mkdirs ();
           }
           //創(chuàng)建硬盤緩存實(shí)例
           mDiskLruCache = DiskLruCache.open (cacheDir, ImageDiskCache.getAppVersion (context), 1, 10 * 1024 * 1024);
       } catch (IOException e) {
           e.printStackTrace ();
       }
   }

   /**
    * 獲取內(nèi)存緩存
    *
    * @return
    */
   public LruCache<String, Bitmap> getLruCache() {
       return mLruCache;
   }

   /**
    * 清除內(nèi)存緩存
    */
   public void clearLruCache() {
       mLruCache.evictAll ();
   }

   /**
    * 保存bitmap到內(nèi)存緩存
    *
    * @param key
    * @param bitmap
    */
   public void saveBitmapToCache(String key, Bitmap bitmap) {
       if(getBitmapFromCache (key) == null) {
           mLruCache.put (key, bitmap);
           Log.i (TAG, "cache size is " + mLruCache.size ());
       }
   }

   /**
    * 查詢內(nèi)存緩存是否已經(jīng)存在當(dāng)前bitmap
    *
    * @param key
    * @return
    */
   public Bitmap getBitmapFromCache(String key) {
       return mLruCache.get (key);
   }

   /**
    * 從內(nèi)存緩存中移除該bitmap
    */
   public void clearDiskLruCache() {
       try {
           mDiskLruCache.delete ();
           Log.i (TAG, "clear disk cache");
       } catch (IOException e) {
           e.printStackTrace ();
       }
   }

   /**
    * 從外部開啟任務(wù)線程加載圖片
    *
    * @param imageUrl
    * @param iv
    */
   public void loadImage(String imageUrl, ImageView iv) {
       iv.setTag (imageUrl);
       Bitmap bitmap = getBitmapFromCache (imageUrl);
       if(bitmap != null) {
           iv.setImageBitmap (bitmap);
           Log.i (TAG, "cache image");
       } else {
           try {
               String key = ImageDiskCache.hashKeyForDisk (imageUrl);
               DiskLruCache.Snapshot snapShot = mDiskLruCache.get (key);
               if(snapShot != null) {
                   InputStream is = snapShot.getInputStream (0);
                   bitmap = BitmapFactory.decodeStream (is);
                   iv.setImageBitmap (bitmap);
                   Log.i (TAG, "disk cache image");
               }
           } catch (IOException e) {
               e.printStackTrace ();
           }
       }
       if(bitmap == null) {
           if(!mLoadMap.containsKey (imageUrl)) {
               ImageDownloaderTask task = new ImageDownloaderTask (imageUrl, iv);
               mLoadMap.put (imageUrl, task);
               try {
                   mThreadPoolExecutor.execute (task);
               } catch (Exception e) {
                   mLoadMap.remove (imageUrl);
               }
           }else {
               ImageDownloaderTask task = mLoadMap.get (imageUrl);
               task.run ();
           }
           Log.i (TAG, "download image");
       }
   }


   /**
    * 圖片加載任務(wù)類
    */
   private class ImageDownloaderTask implements Runnable {

       private static final int CONNECT_TIMEOUT = 5 * 1000;
       private static final int READ_TIMEOUT = 20 * 1000;
       protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
       private Bitmap bitmap;
       private String imageUrl;
       private ImageView ivLoader;

       public ImageDownloaderTask(String imageUrl, ImageView ivLoader) {
           this.imageUrl = imageUrl;
           this.ivLoader = ivLoader;
       }

       @Override
       public void run() {
           boolean flag = true;
           try {
               while (flag) {
                   bitmap = downloadBitmap (imageUrl);
                   mImageLoader.saveBitmapToCache (imageUrl, bitmap);
                   String key = ImageDiskCache.hashKeyForDisk (imageUrl);
                   DiskLruCache.Editor editor = mDiskLruCache.edit (key);
                   if(editor != null) {
                       OutputStream outputStream = editor.newOutputStream (0);
                       if(downloadStream (imageUrl, outputStream)) {
                           editor.commit ();
                       } else {
                           editor.abort ();
                       }
                   }
                   mDiskLruCache.flush ();
                   Log.i (TAG, "disk cache success");
                   //圖片下載完成,handler通知主線程更新界面
                   if(mMainHandler != null) {
                       mMainHandler.post (new Runnable () {
                           @Override
                           public void run() {
                               if(ivLoader != null && imageUrl.equals (ivLoader.getTag ())) {
                                   ivLoader.setImageBitmap (bitmap);
                                   Toast.makeText (mContext, "download success", Toast.LENGTH_SHORT).show ();
                               }
                           }
                       });
                   }
                   flag = false;
               }
           } catch (Exception e) {
               e.printStackTrace ();
           }
       }

       /**
        * 解析圖片流為bitmap
        *
        * @param imageUrl
        * @return
        */
       protected Bitmap downloadBitmap(String imageUrl) {
           Bitmap bitmap;
           InputStream is = getInputStreamFromURL (imageUrl);
           bitmap = BitmapFactory.decodeStream (is);
           return bitmap;
       }

       /**
        * 根據(jù)傳入的圖片地址獲取網(wǎng)絡(luò)連接
        *
        * @param imageUrl
        * @return
        */
       protected HttpURLConnection getConnection(String imageUrl) {
           HttpURLConnection connection = null;
           String encodedUrl = Uri.encode (imageUrl, ALLOWED_URI_CHARS);
           try {
               if(connection != null) {
                   connection.disconnect ();
               } else {
                   URL url = new URL (encodedUrl);
                   connection = (HttpURLConnection) url.openConnection ();
                   connection.setConnectTimeout (CONNECT_TIMEOUT);
                   connection.setReadTimeout (READ_TIMEOUT);
               }
           } catch (MalformedURLException e) {
               e.printStackTrace ();
           } catch (IOException e) {
               e.printStackTrace ();
           }
           return connection;
       }

       /**
        * 從網(wǎng)絡(luò)連接獲取圖片流
        *
        * @param imageUrl
        * @return
        */
       protected InputStream getInputStreamFromURL(String imageUrl) {
           InputStream inputStream = null;
           HttpURLConnection connection = getConnection (imageUrl);
           try {
               inputStream = connection.getInputStream ();
           } catch (IOException e) {
               e.printStackTrace ();
               if(inputStream != null) {
                   try {
                       inputStream.close ();
                   } catch (IOException e1) {
                       e1.printStackTrace ();
                   }
               }
           }
           return inputStream;
       }

       /**
        * @param imageUrl
        * @param outputStream
        * @return
        */
       protected Boolean downloadStream(String imageUrl, OutputStream outputStream) {
           BufferedOutputStream out = null;
           BufferedInputStream in = null;
           try {
               in = new BufferedInputStream (getInputStreamFromURL (imageUrl), 8 * 1024);
               out = new BufferedOutputStream (outputStream, 8 * 1024);
               int b;
               while ((b = in.read ()) != -1) {
                   out.write (b);
               }
               return true;
           } catch (final IOException e) {
               e.printStackTrace ();
           } finally {
               if(getConnection (imageUrl) != null) {
                   getConnection (imageUrl).disconnect ();
               }
               try {
                   if(out != null) {
                       out.close ();
                   }
                   if(in != null) {
                       in.close ();
                   }
               } catch (final IOException e) {
                   e.printStackTrace ();
               }
           }
           return false;
       }
   }
}

使用也很方便

ImageLoader imageLoader = ImageLoader.getInstance ();
String imageUrl = "...圖片地址";
ImageLoadView imageView = findViewById(R.id.iv);
imageLoader.initImageLoader (context);
imageLoader.loadImage(imageUrl,imageView);
TIM圖片20180928111005.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容