編輯:關於Android編程
Android二級緩存之物理存儲介質上的緩存DiskLruCache
Android DiskLruCache屬於物理性質的緩存,相較於LruCache緩存,則DiskLruCache屬於Android二級緩存中的最後一級。通常Android緩存分為兩級,第一級是內存緩存,第二級是物理緩存也即DiskLruCache。顧名思義,DiskLruCache就是將數據緩存到Android的物理介質如外部存儲器存儲卡、內部存儲器存儲卡上。
關於LruCache緩存即內存緩存,我在之前寫過一系列文章,詳情請見附錄文章2,3。本文介紹Android硬件級的緩存策略:DiskLruCache。
事實上,由於DiskLruCache實現原理和過程透明公開,有不少第三方實現,在github上有一個比較流行的DiskLruCache開源實現版本,其項目主頁:
本文將基於JakeWharton實現的DiskLruCache開源庫為例說明。使用JakeWharton實現的DiskLruCache,需要先將github上的代碼下載,下載後,直接復制到自己項目代碼java目錄下作為自己的源代碼直接使用即可。
(1)DiskLruCache的初始化。
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize);
DiskLruCache使用前首先需要從一個靜態方法open創建一個DiskLruCache實例。
一個緩存目錄directory,緩存目錄directory可以用Android系統提供的默認緩存目錄,也可以自己指定一個顯而易見的目錄。
DiskLruCache在open緩存目錄時候,如果前後appVersion不同則銷魂緩存。
valueCount類似於指明一個數組的長度,通常是1,是1 的話,那麼在後面寫緩存newOutputStream時候是newOutputStream(0),因為類似數組下標。長度為1的數組,那麼數組中只有一個元素且該元素的下標是0。同樣,讀的時候也是getInputStream(0)。
maxSize意義簡單,指定DiskLruCache緩存的大小,總不能讓DiskLruCache無限制緩存吧。所以一般要給DiskLruCache指定一個適當的緩存尺寸和限制,一般是10 * 1024 * 1024,10MB。
我寫的初始化例子:
private void makeDiskLruCache() {
try {
File cacheDir = getDiskCacheDir(this, UNIQUENAME);
if (!cacheDir.exists()) {
Log.d(TAG, "緩存目錄不存在,創建之...");
cacheDir.mkdirs();
} else
Log.d(TAG, "緩存目錄已存在,不需創建.");
//第二個參數我選取APP的版本code。DiskLruCache如果發現第二個參數version不同則銷毀緩存
//第三個參數為1,在寫緩存的流時候,newOutputStream(0),0為索引,類似數組的下標
mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);
} catch (Exception e) {
e.printStackTrace();
}
}
(2)往DiskLruCache寫緩存的一般過程。
DiskLruCache的緩存是
//把byte字節寫入緩存DiskLruCache
private void writeToDiskLruCache(String url, byte[] buf) throws Exception {
Log.d(TAG, url + " : 開始寫入緩存...");
//DiskLruCache緩存需要一個key,我先把url轉換成md5字符串,
//然後以md5字符串作為key鍵
String key=urlToKey(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OutputStream os = editor.newOutputStream(0);
os.write(buf);
os.flush();
editor.commit();
mDiskLruCache.flush();
Log.d(TAG, url + " : 寫入緩存完成.");
}
(3)從DiskLruCache讀緩存的一般過程。
直接的以之前寫緩存時候的key鍵從DiskLruCache中讀取:DiskLruCache.get(key),獲得一個快照DiskLruCache.Snapshot,如果這個DiskLruCache.Snapshot為null,則說明沒有緩存,如果有,則說明已經緩存,然後從DiskLruCache.Snapshot組建一個輸入流把緩存數據恢復出來即可。讀緩存的代碼:
//從DiskLruCache中讀取緩存
private Bitmap readBitmapFromDiskLruCache(String url) {
DiskLruCache.Snapshot snapShot = null;
try {
//把url轉換成一個md5字符串,然後以這個md5字符串作為key
String key = urlToKey(url);
snapShot = mDiskLruCache.get(key);
} catch (Exception e) {
e.printStackTrace();
}
if (snapShot != null) {
Log.d(TAG, "發現緩存:" + url);
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
Log.d(TAG, "從緩存中讀取Bitmap.");
return bitmap;
} else
return null;
}
再寫一個完整的簡單例子說明。一個ImageView,ImageView需要加載一個網路圖片,該圖片是我的csdn博客頭像。例子中,代碼啟動後,在為ImageView加載網絡圖片時候,會首先檢查本地DiskLruCache中是否有緩存,如果有則直接使用緩存,如果沒有,則重新開啟一個線程下載圖片資源,圖片下載完成後,一方面要設置到ImageView中,同時要把圖片數據寫入DiskLruCache緩存中。
package zhangphil.app;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import com.jakewharton.disklrucache.DiskLruCache;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private Handler handler;
private ExecutorService pool;
// 默認的線程池容量
private int DEFAULT_TASK_NUMBER = 10;
private final int WHAT = 0xe001;
private String TAG = "zhangphil_tag";
private String UNIQUENAME = "zhangphil_cache";
private DiskLruCache mDiskLruCache = null;
//緩存大小
private int DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化DiskLruCache,創建DiskLruCache實例
makeDiskLruCache();
//創建容量為 asyncTaskNumber 的線程池。
pool = Executors.newFixedThreadPool(DEFAULT_TASK_NUMBER);
final ImageView image = (ImageView) findViewById(R.id.image);
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT:
image.setImageBitmap((Bitmap) msg.obj);
}
}
};
//一個測試的URL連接,從這個鏈接下載一個圖片加載到ImageView中
String image_url = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg";
getBitmap(image_url);
}
private void getBitmap(String url) {
//首先從DiskLruCache讀取緩存,緩存是否有該url的圖片緩存
Bitmap bmp = readBitmapFromDiskLruCache(url);
if (bmp == null) {
//如果緩存中沒有,則創建一個線程下載
Thread t = new DownloadThread(url);
//把線程放到線程池中下載
pool.execute(t);
} else {
//在DiskLruCache中發現緩存,直接復用
sendResult(bmp);
}
}
//從DiskLruCache中讀取緩存
private Bitmap readBitmapFromDiskLruCache(String url) {
DiskLruCache.Snapshot snapShot = null;
try {
//把url轉換成一個md5字符串,然後以這個md5字符串作為key
String key = urlToKey(url);
snapShot = mDiskLruCache.get(key);
} catch (Exception e) {
e.printStackTrace();
}
if (snapShot != null) {
Log.d(TAG, "發現緩存:" + url);
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
Log.d(TAG, "從緩存中讀取Bitmap.");
return bitmap;
} else
return null;
}
//把byte字節寫入緩存DiskLruCache
private void writeToDiskLruCache(String url, byte[] buf) throws Exception {
Log.d(TAG, url + " : 開始寫入緩存...");
//DiskLruCache緩存需要一個key,我先把url轉換成md5字符串,
//然後以md5字符串作為key鍵
String key=urlToKey(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
OutputStream os = editor.newOutputStream(0);
os.write(buf);
os.flush();
editor.commit();
mDiskLruCache.flush();
Log.d(TAG, url + " : 寫入緩存完成.");
}
private void makeDiskLruCache() {
try {
File cacheDir = getDiskCacheDir(this, UNIQUENAME);
if (!cacheDir.exists()) {
Log.d(TAG, "緩存目錄不存在,創建之...");
cacheDir.mkdirs();
} else
Log.d(TAG, "緩存目錄已存在,不需創建.");
//第二個參數我選取APP的版本code。DiskLruCache如果發現第二個參數version不同則銷毀緩存
//第三個參數為1,在寫緩存的流時候,newOutputStream(0),0為索引,類似數組的下標
mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);
} catch (Exception e) {
e.printStackTrace();
}
}
// 開辟一個下載線程
private class DownloadThread extends Thread {
private String url;
public DownloadThread(String url) {
this.url = url;
}
@Override
public void run() {
try {
byte[] imageBytes = loadRawDataFromURL(url);
// 數據下載完畢,把新的bitmap數據寫入DiskLruCache緩存
writeToDiskLruCache(url, imageBytes);
Bitmap bmp = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
sendResult(bmp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 發送消息通知:bitmap已經下載完成。
private void sendResult(Bitmap bitmap) {
Message message = handler.obtainMessage();
message.what = WHAT;
message.obj = bitmap;
handler.sendMessage(message);
}
//從一個url下載原始數據,本例是一個圖片資源。
public byte[] loadRawDataFromURL(String u) throws Exception {
Log.d(TAG, "開始下載 " + u);
URL url = new URL(u);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final int BUFFER_SIZE = 2048;
final int EOF = -1;
int c;
byte[] buf = new byte[BUFFER_SIZE];
while (true) {
c = bis.read(buf);
if (c == EOF)
break;
baos.write(buf, 0, c);
}
conn.disconnect();
is.close();
byte[] data = baos.toByteArray();
baos.flush();
Log.d(TAG, "下載完成! " + u);
return data;
}
/*
*
* 當SD卡存在或者SD卡不可被移除的時候,就調用getExternalCacheDir()方法來獲取緩存路徑,
* 否則就調用getCacheDir()方法來獲取緩存路徑。
* 前者獲取到的就是 /sdcard/Android/data//cache
* 而後者獲取到的是 /data/data//cache 。
*
* */
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
File dir = new File(cachePath + File.separator + uniqueName);
Log.d(TAG, "緩存目錄:" + dir.getAbsolutePath());
return dir;
}
//版本名
public static String getVersionName(Context context) {
return getPackageInfo(context).versionName;
}
//版本號
public static int getVersionCode(Context context) {
return getPackageInfo(context).versionCode;
}
private static PackageInfo getPackageInfo(Context context) {
PackageInfo pi = null;
try {
PackageManager pm = context.getPackageManager();
pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS);
return pi;
} catch (Exception e) {
e.printStackTrace();
}
return pi;
}
public String urlToKey(String url) {
return getMD5(url);
}
/*
* 傳入一個字符串String msg,返回Java MD5加密後的16進制的字符串結果。
* 結果形如:c0e84e870874dd37ed0d164c7986f03a
*/
public static String getMD5(String msg) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
md.reset();
md.update(msg.getBytes());
byte[] bytes = md.digest();
String result = "";
for (byte b : bytes) {
// byte轉換成16進制
result += String.format("%02x", b);
}
return result;
}
}
涉及到Android網絡操作和存儲設備的讀寫,不要忘記加相關權限:
附錄文章:
1,《基於Java LinkedList,實現Android大數據緩存策略》鏈接地址:http://blog.csdn.net/zhangphil/article/details/44116885
2,《使用新式LruCache取代SoftReference緩存圖片,Android異步加載圖片》鏈接地址:http://blog.csdn.net/zhangphil/article/details/43667415
3,《使用Android新式LruCache緩存圖片,基於線程池異步加載圖片》鏈接地址:http://blog.csdn.net/zhangphil/article/details/44082287
4,《Java MD5(字符串)》鏈接地址:http://blog.csdn.net/zhangphil/article/details/44152077
5,《從一個URL下載原始數據,基於byte字節》鏈接地址:http://blog.csdn.net/zhangphil/article/details/43794837
6,《Android獲取App版本號和版本名》鏈接地址:http://blog.csdn.net/zhangphil/article/details/43795099
如何翻查微信消息列表 微信消息列表在哪
小編經常遇到評論了別人的微信,查看別人的回復信息後,過一會想到如何回復朋友,但那條微信評論已經不知別刷到哪裡去了!後來發現,直接去微信消息列表中回復就可以了
Android 開發第三彈:自定義左右菜單(滑動動畫+蒙版效果)
下面的截圖……哎,因為1080P在Windows 10上雖然適配了,但大部分軟件並沒有跟上,比如某個錄制GIF的軟件,所以這裡有一定的偏移導致
Android使用GridView實現日歷功能示例(詳細代碼)
Android使用GridView實現日歷功能示例,代碼有點多,發個圖先:如果懶得往下看的,可以直接下載源碼吧,最近一直有人要,由於時間太久了,懶得找出來整理,今天又看到
Android底部菜單簡單應用
在Android中實現菜單功能有多種方法。 Options Menu:用戶按下menu Button時顯示的菜單。 Context Menu:用戶長時間按下屏幕,所顯示出