Android中圖片的三級緩存詳解

圖片的三級緩存機制一般是指應用加載圖片的時候,分別去訪問內容,文件,網絡獲取圖片的一種行為。

一、三級緩存流程圖

三級緩存流程圖

二、代碼框架搭建

  • 這里我仿造 Picasso 的加載圖片代碼,也做出了with,load,into等方法。

2.1 with(context)

  • 這個方法傳入上下文,返回ImageManager對象。

    /**
       * 初始化對象
       *
       * @param context
       * @return
       */
      public static ImageManager with(Context context) {
          mContext = context;
          return getInstance();
      }
  • 因為ImageManager會不斷的調用,所以要做成單利。

    /**
       * 獲取對象的單利
       *
       * @return
       */
      private static ImageManager instance;
      private static ImageManager getInstance() {
          if (instance == null) {
              instance = new ImageManager();
          }
    
          return instance;
      }

2.2 load(url)

  • 這個方法返回一個自定義的RequestCreator內部類,對圖片的操作都在這個內部類中進行。

    /**
       * 加載圖片的url地址,返回RequestCreator對象
       *
       * @param url
       * @return
       */
      public RequestCreator load(String url) {
    
          return new RequestCreator(url);
      }
  • RequestCreator的構造方法中接收傳入的url 。

    // 初始化圖片的url地址
      public RequestCreator(String url) {
          this.url = url;
      }

2.3 into(imageview)

  • 這是RequestCreator類的方法,也是工具類的核心方法,這個方法里進行圖片的三個緩存處理。

三、內存緩存

3.1 Java中對象的四種引用類型介紹

  • 強引用

    • Java中所有new出來的對象都是強引用類型,回收的時候,GC寧愿拋出OOM異常,也不回收它。

      Map<String, Bitmap> mImageCache = new HashMap<>();
  • 軟引用,SoftReference

    • 內存足夠時,不回收。內存不夠時,就回收。這里使用這種方式緩存對象。

      Map<String, SoftReference<Bitmap>> mImageCache = new HashMap<>();
  • 弱引用,WeakReference

    • GC一出來工作就回收它。
  • 虛引用,PhantomReference

    • 用完就消失。

3.2 使用LruCache類來做緩存

  • LruCache其實是一個Hash表,內部使用的是LinkedHashMap存儲數據。使用LruCache類可以規定緩存內存的大小,并且這個類內部使用到了最近最少使用算法來管理緩存內存。

    LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);

3.3 代碼實現內存緩存

  • 創建緩存集合

    /**
       * 內存儲存圖片的集合
       * 使用lrucache緩存圖片,這里不能申明在方法里,不然會被覆蓋掉
       * 使用軟引用類型對象
       * 4兆的大小作為緩存
       */
      private LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
  • 訪問內存時先從集合中取出軟引用,獲取BitMap

    // 1 去內存之中找,有就顯示,沒有就往下走
      SoftReference<Bitmap> reference = mImageCache.get(url);
      Bitmap cacheBitmap;
      if(reference != null){
          cacheBitmap = reference.get();
          // 有就顯示圖片
          imageView.setImageBitmap(cacheBitmap);
    
          Log.d("RequestCreator:", "內存中有圖片顯示");
          // 不往下走了
          return;
      }
  • 如果緩存集合中的數據為空,就繼續往下走。

四、文件緩存

4.1 緩存文件存儲的路徑設定

  • 存儲的路徑首先要考慮SD卡的緩存目錄,當SD卡不存在時,就只能存到內部存儲的緩存目錄了。

    /**
       * 獲取緩存路徑目錄
       */
      private File getCacheDir() {
    
          // 獲取保存的文件夾路徑
          File file;
          if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
              // 有SD卡就保存到sd卡
              file = mContext.getExternalCacheDir();
          } else {
              // 沒有就保存到內部儲存
              file = mContext.getCacheDir();
          }
          return file;
      }

4.2 解析文件生成Bitmap對象

  • 存儲的文件的名字截取URL中的名字。
  • 文件名使用Md5加密。

    /**
       * 從文件中獲取bitmap
       *
       * @return
       */
      private Bitmap getBitmapFromFile() {
          // 從url中獲取文件名字
          String fileName = url.substring(url.lastIndexOf("/") + 1);
    
          File file = new File(getCacheDir(),MD5Util.encodeMd5(fileName));
          // 確保路徑沒有問題
          if (file.exists() && file.length() > 0) {
              // 返回圖片
              return BitmapFactory.decodeFile(file.getAbsolutePath());
          } else {
              return null;
          }
      }

4.3 判斷是否有緩存

  • 有緩存則讀取出來顯示,并且將緩存存入內存,沒有就繼續往下走。

    // 2 去本地硬盤中找,有就顯示,沒有就繼續往下走
      // 將文件轉換成bitmap對象
      Bitmap diskBitmap = getBitmapFromFile();
      if (diskBitmap != null) {
          // 本地磁盤有就顯示圖片
          imageView.setImageBitmap(diskBitmap);
    
          // 保存到內存中去
          mImageCache.put(url, new SoftReference<Bitmap>(diskBitmap));
    
          Log.d("RequestCreator:", "磁盤中有圖片顯示");
    
          // 不往下走了
          return;
      }

五、聯網加載

5.1 簡單線程池處理耗時的網絡請求

  • 創建線程池對象

    /**
       * 構建出線程池,5條線程
       */
      private  ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
  • 提交任務,讓RequestCreator實現Runnable接口,run方法中執行任務

    // 3 聯網請求數據
      // 前面兩步都沒有的話就去聯網加載數據
      // 將從網絡上獲取的數據放到線程池去執行
      mExecutorService.submit(this);

5.2 聯網加載數據

  • 使用HttpUrlConnection連接網絡

    // 子線程
      // 處理網絡請求
      try {
          URL loadUrl = new URL(url);
          HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection();
    
          conn.setRequestMethod("GET");
          conn.setConnectTimeout(2000);
    
          if (conn.getResponseCode() == 200) {
              InputStream is = conn.getInputStream();
    
              // 獲取到圖片進行顯示
              final Bitmap bm = BitmapFactory.decodeStream(is);
    
              mHandler.post(new Runnable() {
                  @Override
                  public void run() {
                      // 主線程
                      imageView.setImageBitmap(bm);
                  }
              });
    
              Log.d("RequestCreator:", "聯網顯示圖片");
          } else {
              // 聯網失敗,顯示失敗圖片
              showError();
          }
      } catch (Exception e) {
          e.printStackTrace();
          // 發生異常顯示失敗圖片
          showError();
      }

5.3 保存數據到內存和文件

  • 使用緩存保存數據

    // 3.1 保存到內存
      mImageCache.put(url, new SoftReference<>(bm));
    
      // 3.2 保存到磁盤
      // 從url中獲取文件名字
      String fileName = url.substring(url.lastIndexOf("/") + 1);
    
      // 獲取存儲路徑
      File file = new File(getCacheDir(), MD5Util.encodeMd5(fileName));
      FileOutputStream os = new FileOutputStream(file);
      // 將圖片轉換為文件進行存儲
      bm.compress(Bitmap.CompressFormat.JPEG, 100, os);

六、細節處理

6.1 設置占位圖

  • 界面一上來加載圖片時肯定是空白的,所以需要一張占位圖。
  • RequestCreator類提供一個方法,將占位圖片資源ID傳進來。

    /**
       * 設置默認圖片,占位圖片
       *
       * @param holderResId
       */
      public RequestCreator placeholder(int holderResId) {
          this.holderResId = holderResId;
    
          return this;
      }
  • 在into方法中,讀取緩存之前,就讓默認圖顯示。

    // 一進來先設置占位圖片
       imageView.setImageResource(holderResId);

    6.2 設置錯誤圖片

  • 加載數據出現錯誤和異常都顯示錯誤圖片。

    /**
       * 顯示錯誤圖片
       */
      private void showError() {
          mHandler.post(new Runnable() {
              @Override
              public void run() {
                  imageView.setImageResource(errorResId);
              }
          });
      }

七、使用自己封裝的小框架加載圖片

  • 使用很簡單,和 Picasso 一樣,一行代碼就搞定。

    // 使用自己封裝的圖片緩存工具類加載圖片
      ImageManager.with(mContext).load(imgUrl).placeholder(R.drawable.ic_default).error(R.drawable.ic_error).into(ivImage);
  • 效果圖

 

 

 

來自:http://www.jianshu.com/p/97455f080065

 

 本文由用戶 designerbags 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!