android圖片緩存隨筆

圖片緩存原理作為android進階的必備知識,是一名中高級開發(fā)人員必須掌握的,也經(jīng)常在面試中被問到,故做一下記錄。

眾所周知,為避免內(nèi)存溢出,圖片有三級緩存的說法,即內(nèi)存,硬盤,網(wǎng)絡(luò)。

內(nèi)存緩存技術(shù)

核心類是LruCache,它的算法原理是把最近使用的對象用強引用存儲在LinkedHashMap中,并且把最近最少使用的對象在緩存值達到預(yù)設(shè)值之前從內(nèi)存中移除。

代碼解釋:

// 獲取到可用內(nèi)存的最大值,使用內(nèi)存超出這個值會引起OutOfMemory異常。  
// LruCache通過構(gòu)造函數(shù)傳入緩存值,以KB為單位。  
  int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
// 使用最大可用內(nèi)存值的1/8作為緩存的大小。  
int cacheSize = maxMemory / 8;  
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
    @Override  
    protected int sizeOf(String key, Bitmap bitmap) {  
        // 重寫此方法來衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量。  
        return bitmap.getByteCount() / 1024;  
    }  
};

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
  if (getBitmapFromMemCache(key) == null) {  
      mMemoryCache.put(key, bitmap);  
    }  
}  

public Bitmap getBitmapFromMemCache(String key) {  
    return mMemoryCache.get(key);  
}

當(dāng)向ImageView中加載圖片的時候,首先會在緩存中檢查是否存在,如果根據(jù)key值找到該圖片會立即更新,否則開啟一條線程加載這張圖片。
如下:

public void loadBitmap(int resId, ImageView imageView) {  
    final String imageKey = String.valueOf(resId);  
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);  
    if (bitmap != null) {  
        imageView.setImageBitmap(bitmap);  
    } else {  
        imageView.setImageResource(R.drawable.image_placeholder);  
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
        task.execute(resId);  
    }  
}

BitmapWorkerTask 還要把新加載的圖片的鍵值對放到緩存中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
    // 在后臺加載圖片。  
    @Override  
    protected Bitmap doInBackground(Integer... params) {  
        final Bitmap bitmap = decodeSampledBitmapFromResource(  
                getResources(), params[0], 100, 100);  
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
        return bitmap;  
    }  
}

硬盤緩存

核心類DiskLruCache。
DiskLruCache并沒有限制數(shù)據(jù)的緩存位置,可以自由地進行設(shè)定,但是通常情況下多數(shù)應(yīng)用程序都會將緩存的位置選擇為 /sdcard/Android/data/<application package>/cache 這個路徑。選擇在這個位置有兩點好處:第一,這是存儲在SD卡上的,因此即使緩存再多的數(shù)據(jù)也不會對手機的內(nèi)置存儲空間有任何影響,只要SD卡空間足夠就行。第二,這個路徑被Android系統(tǒng)認(rèn)定為應(yīng)用程序的緩存路徑,當(dāng)程序被卸載的時候,這里的數(shù)據(jù)也會一起被清除掉,這樣就不會出現(xiàn)刪除程序之后手機上還有很多殘留數(shù)據(jù)的問題。

DiskLruCache是不能new出實例的,如果我們要創(chuàng)建一個DiskLruCache的實例,則需要調(diào)用它的open()方法。

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

open()方法接收四個參數(shù),第一個參數(shù)指定的是數(shù)據(jù)的緩存地址,第二個參數(shù)指定當(dāng)前應(yīng)用程序的版本號,第三個參數(shù)指定同一個key可以對應(yīng)多少個緩存文件,基本都是傳1,第四個參數(shù)指定最多可以緩存多少字節(jié)的數(shù)據(jù)。

獲取緩存地址方法:

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

接著又將獲取到的路徑和一個uniqueName進行拼接,作為最終的緩存路徑返回。那么這個uniqueName又是什么呢?其實這就是為了對不同類型的數(shù)據(jù)進行區(qū)分而設(shè)定的一個唯一值,比如說在網(wǎng)易新聞緩存路徑下看到的bitmap、object等文件夾。

獲取版本號代碼:

public int getAppVersion(Context context) {  
    try {  
        PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);  
        return info.versionCode;  
    } catch (NameNotFoundException e) {  
        e.printStackTrace();  
    }  
    return 1;  
}

需要注意的是,每當(dāng)版本號改變,緩存路徑下存儲的所有數(shù)據(jù)都會被清除掉,因為DiskLruCache認(rèn)為當(dāng)應(yīng)用程序有版本更新的時候,所有的數(shù)據(jù)都應(yīng)該從網(wǎng)上重新獲取。
后面兩個參數(shù)就沒什么需要解釋的了,第三個參數(shù)傳1,第四個參數(shù)通常傳入10M的大小就夠了,這個可以根據(jù)自身的情況進行調(diào)節(jié)。

綜上所述,一個標(biāo)準(zhǔn)的open方法可以這樣寫:

DiskLruCache mDiskLruCache = null;  
try {  
    File cacheDir = getDiskCacheDir(context, "bitmap");  
    if (!cacheDir.exists()) {  
        cacheDir.mkdirs();  
    }  
    mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);  
} catch (IOException e) {  
    e.printStackTrace();  
}

有了DiskLruCache的實例之后,我們就可以對緩存的數(shù)據(jù)進行操作了,操作類型主要包括寫入、訪問、移除等。

  • 寫入

寫入的操作是借助DiskLruCache.Editor這個類完成的。類似地,這個類也是不能new的,需要調(diào)用DiskLruCache的edit()方法來獲取實例,接口如下所示:

public Editor edit(String key) throws IOException 

可以看到,edit()方法接收一個參數(shù)key,這個key將會成為緩存文件的文件名,并且必須要和圖片的URL是一一對應(yīng)的。那么怎樣才能讓key和圖片的URL能夠一一對應(yīng)呢?直接使用URL來作為key?不太合適,因為圖片URL中可能包含一些特殊字符,這些字符有可能在命名文件時是不合法的。其實最簡單的做法就是將圖片的URL進行MD5編碼,編碼后的字符串肯定是唯一的,并且只會包含0-F這樣的字符,完全符合文件的命名規(guī)則。

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

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

因此,現(xiàn)在就可以這樣寫來得到一個DiskLruCache.Editor的實例:

String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  
String key = hashKeyForDisk(imageUrl);  
DiskLruCache.Editor editor = mDiskLruCache.edit(key);

有了DiskLruCache.Editor的實例之后,我們可以調(diào)用它的newOutputStream()方法來創(chuàng)建一個輸出流。
完整的寫入操作:

new Thread(new Runnable() {  
    @Override  
    public void run() {  
        try {  
            String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  
            String key = hashKeyForDisk(imageUrl);  
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);  
            if (editor != null) {  
                OutputStream outputStream = editor.newOutputStream(0);  
                if (downloadUrlToStream(imageUrl, outputStream)) {  
                    editor.commit();  
                } else {  
                    editor.abort();  
                }  
            }    
            mDiskLruCache.flush();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}).start(); 
  • 讀取
    完整的讀取緩存代碼如下:

      try {  
      String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  
          String key = hashKeyForDisk(imageUrl);  
          DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);  
          if (snapShot != null) {  
              InputStream is = snapShot.getInputStream(0);  
              Bitmap bitmap = BitmapFactory.decodeStream(is);  
              mImage.setImageBitmap(bitmap);  
          }  
      } catch (IOException e) {  
          e.printStackTrace();  
      }  
    
  • 移除

    try {  
          String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";    
          String key = hashKeyForDisk(imageUrl);    
          mDiskLruCache.remove(key);  
      } catch (IOException e) {  
          e.printStackTrace();  
    } 
    

用法雖然簡單,但是你要知道,這個方法我們并不應(yīng)該經(jīng)常去調(diào)用它。因為你完全不需要擔(dān)心緩存的數(shù)據(jù)過多從而占用SD卡太多空間的問題,DiskLruCache會根據(jù)我們在調(diào)用open()方法時設(shè)定的緩存最大值來自動刪除多余的緩存。只有你確定某個key對應(yīng)的緩存內(nèi)容已經(jīng)過期,需要從網(wǎng)絡(luò)獲取最新數(shù)據(jù)的時候才應(yīng)該調(diào)用remove()方法來移除緩存。

兩者的融合使用參考:http://blog.csdn.net/boyupeng/article/details/47127605

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Lru: LRU是Least Recently Used 的縮寫,翻譯過來就是“最近最少使用”,LRU緩存就是使用...
    geegtb閱讀 938評論 0 4
  • 本文會從工作原理到具體實現(xiàn)來詳細介紹如何開發(fā)一個簡潔而實用的Android圖片加載框架,并從內(nèi)存占用與加載圖片所需...
    absfree閱讀 2,407評論 4 34
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,947評論 18 139
  • 那是一座通向天空的橋 橫跨在海上 白色的,軟軟的 蔚藍蔚藍的海 海上有幾艘白色的小船 正慢悠悠地前行著 不知駛向何...
    朗月笑長空閱讀 532評論 0 1
  • 前言 亨利?!ずD谠u價德國思想家赫爾德的時候這樣說道:赫爾德在思想史上的影響如此重大,以致于后人都意識不到他的影...
    朱進偉西農(nóng)閱讀 2,945評論 0 9