【安卓筆記】硬碟快取工具類的編寫

rowandjj發表於2015-02-16

DiskLruCache(https://github.com/JakeWharton/DiskLruCache)想必大家都很熟悉。不熟悉的請看這裡),它是jakewharton大神寫的一個開源庫,提供了硬碟快取的方案。

       但是該庫的API比較簡單,有時候並不能滿足我們使用。比如說如果你想把快取中的資料以Bitmap的形式返回,API並沒有提供這樣的方法,我們必須通過DiskLruCache#get方法返回來的Snapshot獲得輸入流,然後將流轉化為Bitmap。另外,構建DiskLruCache時必須要傳入VersionCode,CachePath等變數,而實際開發中,Versioncode通常是應用版本號,Cachepath也相對固定(要麼是sd的快取目錄要麼是本地的快取目錄),每次構建DiskLruCache都要寫獲取版本號等等重複的程式碼實在是太枯燥了。再比如說,寫快取時需要指定key,而這個key在專案中通常是url,而url中可能有特殊字元,這將導致寫快取失敗,我們最好將url進行編碼,然後使用編碼後的key,而在API中並沒有提供類似的方法。鑑於此,我寫了一個基於DiskLruCache的工具類,進一步簡化快取操作。
    這個工具類主中的方法全部是靜態的,可直接呼叫。它的功能有:1.構建快取物件;2.讀快取。將快取讀為String/流/Bitmap;3.寫快取。可以將String/File/流/Bitmap寫入快取,並支援非同步寫入。

   說了這麼多,下面來看這個工具類是怎樣寫的吧。
package com.jakewharton.disklrucache;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.text.TextUtils;
/**
 * @author Rowandjj
 *
 *DiskLruCache的輔助工具類(DiskLruCache:http://jakewharton.github.io/DiskLruCache)
 * 提供建立/讀取/寫入快取的方法,並支援非同步快取寫入.
 */
public class DiskLruCacheHelper
{
	private static final long DEFAULT_MAX_SIZE = 10*1024*1024;
	
	private static ExecutorService service = null;
	/**
	 * 
	 * 建立DiskLruCache例項,預設版本號為當前應用版本號,快取位置由getDiskCacheDir指定
	 * @param context 上下文
	 * @param cacheDirName 快取資料夾名稱
	 * @param maxSize 快取最大值,單位是byte
	 * @return 建立成功返回DiskLruCache例項否則返回null
	 */
	public static DiskLruCache createCache(Context context,String cacheDirName,long maxSize)
	{
		DiskLruCache cache = null;
		try
		{
			 cache = DiskLruCache.open(getDiskCacheDir(cacheDirName, context), getAppVersion(context),1, maxSize);
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return cache;
	}
	/**
	 * 返回一個具有預設大小的DiskLruCache例項,預設大小為10Mb
	 * @param context
	 * @param cacheDirName
	 * @return 建立成功返回DiskLruCache例項否則返回null
	 */
	public static DiskLruCache createCache(Context context,String cacheDirName)
	{
		return createCache(context, cacheDirName, DEFAULT_MAX_SIZE);
	}
	
	/**
	 * 將圖片寫入快取
	 */
	public static boolean writeBitmapToCache(DiskLruCache cache,Bitmap bitmap,String url)
	{
		return writeBitmapToCache(cache, bitmap, url, CompressFormat.JPEG,100);
	}
	
	/**
	 * 非同步地將圖片寫入快取。將不會不會寫入結果。
	 * @param cache
	 * @param bitmap
	 * @param url
	 */
	public static void asyncWriteBitmapToCache(final DiskLruCache cache,final Bitmap bitmap,final String url)
	{
		if(service == null)
			service = Executors.newSingleThreadExecutor();
		service.execute(new Runnable()
		{
			@Override
			public void run()
			{
				writeBitmapToCache(cache, bitmap, url);
			}
		});
	}
	
	/**
	 * 將圖片寫入快取
	 * @param cache 快取物件
	 * @param bitmap 圖片物件
	 * @param url 用於標識bitmap的唯一名稱,通常為圖片url
	 * @return true表示寫入快取成功否則為false
	 */
	public static boolean writeBitmapToCache(DiskLruCache cache,Bitmap bitmap,String url,CompressFormat format, int quality)
	{
		if(cache == null || bitmap == null || url == null || TextUtils.isEmpty(url))
			return false;
		try
		{
			DiskLruCache.Editor editor = cache.edit(generateKey(url));
			if(editor != null)
			{
				OutputStream out = editor.newOutputStream(0);
				if(bitmap.compress(format,quality, out))
				{
					editor.commit();
					return true;
				}					
				else
				{
					editor.abort();
				}
			}
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return false;
	}
	/**
	 * 非同步地將圖片寫入快取。將不會不會寫入結果。
	 */
	public static void asyncWriteBitmapToCache(final DiskLruCache cache,final Bitmap bitmap,final String url,final CompressFormat format, final int quality)
	{
		if(service == null)
			service = Executors.newSingleThreadExecutor();
		service.execute(new Runnable()
		{
			@Override
			public void run()
			{
				writeBitmapToCache(cache, bitmap, url,format,quality);
			}
		});
	}
	
	/**
	 * 非同步地將inputStram流寫入快取,將不會返回寫入結果。
	 */
	public static void asyncWriteStreamToCache(final DiskLruCache cache,final InputStream in,final String url)
	{
		if(service == null)
			service = Executors.newSingleThreadExecutor();
		service.execute(new Runnable()
		{
			@Override
			public void run()
			{
				asyncWriteStreamToCache(cache, in, url);
			}
		});
	}
	
	/**
	 * 將inputStram流寫入快取
	 * @param cache
	 * @param in
	 * @param url
	 * @return
	 */
	public static boolean writeStreamToCache(DiskLruCache cache,InputStream in,String url)
	{
		if(cache == null || in == null || url == null|| TextUtils.isEmpty(url))
			return false;
		DiskLruCache.Editor editor = null;
		try
		{
			editor = cache.edit(generateKey(url));
			if(editor != null)
			{
				OutputStream out = editor.newOutputStream(0);
				BufferedInputStream bin = new BufferedInputStream(in);
				byte[] buffer = new byte[1024];
				int len = 0;
				while((len = bin.read(buffer)) != -1)
				{
					out.write(buffer, 0, len);
					out.flush();
				}
				editor.commit();
				return true;
			}
			
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return false;
	}
	/**
	 * 非同步地將檔案寫入快取,將不會返回寫入結果。
	 */
	public static void asyncWriteFileToCache(final DiskLruCache cache,final File file,final String url)
	{
		if(service == null)
			service = Executors.newSingleThreadExecutor();
		service.execute(new Runnable()
		{
			@Override
			public void run()
			{
				writeFileToCache(cache, file, url);
			}
		});
	}
	/**
	 * 將檔案寫入快取
	 * @return true表示寫入成功否則寫入失敗
	 */
	public static boolean writeFileToCache(DiskLruCache cache,File file,String url)
	{
		if(cache == null || file == null || url == null || !file.exists() || TextUtils.isEmpty(url))
		{
			return false;
		}
		FileInputStream fin = null;
		try
		{
			fin = new FileInputStream(file);
		} catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		return writeStreamToCache(cache, fin, url);
	}
	
	/**
	 * 非同步地將字串寫入快取,將不會返回寫入結果
	 */
	public static void asyncWriteStringToCache(final DiskLruCache cache,final String str,final String url)
	{
		if(service == null)
			service = Executors.newSingleThreadExecutor();
		service.execute(new Runnable()
		{
			@Override
			public void run()
			{
				writeStringToCache(cache, str, url);
			}
		});
	}
	/**
	 * 將字串寫入快取
	 * @param cache
	 * @param str
	 * @param url
	 * @return
	 */
	public static boolean writeStringToCache(DiskLruCache cache,String str,String url)
	{
		if(cache == null || str == null || url == null || TextUtils.isEmpty(url) || TextUtils.isEmpty(str))
		{
			return false;
		}
		DiskLruCache.Editor editor = null;
		try
		{
			editor = cache.edit(generateKey(url));
			if(editor != null)
			{
				OutputStream out = editor.newOutputStream(0);
				out.write(str.getBytes());
				out.flush();
			}
			editor.commit();
			return true;
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return false;
	}
	
	
	/**
	 * 停止內部正在寫快取的執行緒,
	 * 這將導致部分寫快取任務不能進行。
	 */
	public static void stop()
	{
		if(service != null)
			service.shutdownNow();
	}
	
	/**
	 * 
	 * 根據url獲取快取,並將結果以String形式返回
	 * @param cache
	 * @param url
	 * @return 成功則返回String否則返回null
	 */
	public static String readCacheToString(DiskLruCache cache,String url)
	{
		if(cache == null || url == null || TextUtils.isEmpty(url))
			return null;
		String key = generateKey(url);
		DiskLruCache.Snapshot snapshot = null;
		try
		{
			snapshot = cache.get(key);
			if(snapshot != null)
			{
				InputStream in = snapshot.getInputStream(0);
				StringBuilder builder = new StringBuilder(1024*2);
				int len = 0;
				byte[] buffer = new byte[1024];
				while((len = in.read(buffer)) != -1)
				{
					builder.append(new String(buffer,0,len));
				}
				return builder.toString();
			}
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 根據url獲取快取,並將快取以InputStream形式返回
	 * 
	 * @param cache DiskLruCache例項
	 * @param url 快取名
	 * @return 命中則返回InputStream流否則返回null
	 */
	public static InputStream readCacheToInputStream(DiskLruCache cache,String url)
	{
		if(cache == null || url == null || TextUtils.isEmpty(url))
			return null;
		String key = generateKey(url);
		DiskLruCache.Snapshot snapshot = null;
		try
		{
			snapshot = cache.get(key);
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		if(snapshot != null)
			return snapshot.getInputStream(0);
		return null;
	}
	
	
	/**
	 * 根據url獲取快取,並將快取以Bitmap形式返回
	 * @param cache
	 * @param url
	 * @return 成功返回bitmap,否則返回null
	 */
	public static Bitmap readCacheToBitmap(DiskLruCache cache,String url)
	{
		InputStream in = readCacheToInputStream(cache, url);
		if(in != null)
			return BitmapFactory.decodeStream(in);
		return null;
	}
	
	/**
	 * 獲取快取檔案路徑(優先選擇sd卡)
	 * @param cacheDirName 快取資料夾名稱
	 * @param context 上下文
	 * @return
	 */
	public static File getDiskCacheDir(String cacheDirName,Context context)
	{
		String cacheDir;
		
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)&&!Environment.isExternalStorageRemovable())
		{
			cacheDir = getExternalCacheDir(context);
			if(cacheDir == null)//部分機型返回了null
				cacheDir = getInternalCacheDir(context);
		}else
		{
			cacheDir = getInternalCacheDir(context);
		}
		File dir =  new File(cacheDir,cacheDirName);
		if(!dir.exists())
			dir.mkdirs();
		return dir;
	}
	
	/**
	 * 獲取當前app版本號
	 * @param context 上下文
	 * @return 當前app版本號
	 */
	public static int getAppVersion(Context context)
	{
		PackageManager manager = context.getPackageManager();
		int code = 1;
		try
		{
			code = manager.getPackageInfo(context.getPackageName(),0).versionCode;
		} catch (NameNotFoundException e)
		{
			e.printStackTrace();
		}
		return code;
	}
	
	
	/**
	 * 根據指定的url移除指定快取
	 * Note:請不要是使用DiskLruCache.remove()
	 * 
	 * @param cache
	 * @param url
	 * @return
	 */
	public static boolean remove(DiskLruCache cache,String url)
	{
		if(cache == null || url == null || TextUtils.isEmpty(url))
		{
			return false;
		}
		try
		{
			return cache.remove(generateKey(url));
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return false;
	}
	
	/**
	 * 根據原始鍵生成新鍵,以保證鍵的名稱的合法性
	 * @param key 原始鍵,通常是url
	 * @return
	 */
	public static String generateKey(String key)
	{
		String cacheKey;
		try
		{
			MessageDigest digest = MessageDigest.getInstance("md5");
			digest.update(key.getBytes());
			cacheKey = bytesToHexString(digest.digest());
		} catch (NoSuchAlgorithmException e)
		{
			e.printStackTrace();
			cacheKey = String.valueOf(key.hashCode());  
		}
		return cacheKey;
	}
	
	private static String bytesToHexString(byte[] bytes)
	{
		StringBuilder builder = new StringBuilder();
		for(int i = 0; i < bytes.length; i++)
		{
			String hex = Integer.toHexString(0xff&bytes[i]);
			if(hex.length() == 1)
				builder.append('0');
			builder.append(hex);
		}
		return builder.toString();
	}
	
	private static String getExternalCacheDir(Context context)
	{
		File dir = context.getExternalCacheDir();
		if(dir == null)
			return null;
		if(!dir.exists())
			dir.mkdirs();
		return dir.getPath();
	}
	
	private static String getInternalCacheDir(Context context)
	{
		File dir = context.getCacheDir();
		if(!dir.exists())
			dir.mkdirs();
		return dir.getPath();
	}
}
程式碼比較簡單,而且都有註釋,我就不過多解釋了。
以後在開發中,如果需要構建DiskLruCache,呼叫DiskLruCacheHelper.createCache,取快取呼叫DiskLruCacheHelper.readCacheToXX,寫快取呼叫DiskLruCacheHelper.writeXXtoCache,非同步寫呼叫DiskLruCacheHelper.asyncWriteXXtoCache,移除指定快取呼叫DiskLruCacheHelper.remove。是不是很方便?

github地址:https://github.com/Rowandjj/DiskLruCacheHelper

相關文章