其實,在真正的項目實戰當中如果僅僅是使用硬碟緩存的話,程式是有明顯短闆的。而如果隻使用記憶體緩存的話,程式當然也會有很大的缺陷。是以,一個優秀的程式必然會将記憶體緩存和硬碟緩存結合到一起使用,那麼本篇文章我們就來看一看,如何才能将lrucache和disklrucache完美結合到一起。
那我們開始動手吧,建立一個android項目,起名叫photowalldemo,這裡我使用的是android 4.0的api。然後建立一個libcore.io包,并将disklrucache.java檔案拷貝到這個包下,這樣就把準備工作完成了。
接下來首先需要考慮的仍然是圖檔源的問題,簡單起見,我仍然是吧所有圖檔都上傳到了我的csdn相冊當中,然後建立一個images類,将所有相冊中圖檔的網址都配置進去,代碼如下所示:
public class images {
public final static string[] imagethumburls = new string[] {
"http://img.my.csdn.net/uploads/201407/26/1406383299_1976.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383291_6518.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383291_8239.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383290_9329.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383290_1042.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383275_3977.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383265_8550.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383264_3954.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383264_4787.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383264_8243.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383248_3693.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383243_5120.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383242_3127.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383242_9576.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383242_1721.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383219_5806.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383214_7794.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383213_4418.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383213_3557.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383210_8779.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383172_4577.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383166_3407.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383166_2224.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383166_7301.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383165_7197.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383150_8410.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383131_3736.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383130_5094.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383130_7393.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383129_8813.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383100_3554.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383093_7894.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383092_2432.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383092_3071.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383091_3119.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383059_6589.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383059_8814.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383059_2237.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383058_4330.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406383038_3602.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382942_3079.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382942_8125.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382942_4881.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382941_4559.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382941_3845.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382924_8955.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382923_2141.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382923_8437.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382922_6166.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382922_4843.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382905_5804.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382904_3362.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382904_2312.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382904_4960.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382900_2418.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382881_4490.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382881_5935.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382880_3865.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382880_4662.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382879_2553.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382862_5375.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382862_1748.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382861_7618.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382861_8606.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382861_8949.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382841_9821.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382840_6603.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382840_2405.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382840_6354.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382839_5779.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382810_7578.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382810_2436.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382809_3883.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382809_6269.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382808_4179.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382790_8326.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382789_7174.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382789_5170.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382789_4118.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382788_9532.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382767_3184.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382767_4772.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382766_4924.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382766_5762.jpg",
"http://img.my.csdn.net/uploads/201407/26/1406382765_7341.jpg"
};
}
設定好了圖檔源之後,我們需要一個gridview來展示照片牆上的每一張圖檔。打開或修改activity_main.xml中的代碼,如下所示:
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<gridview
android:id="@+id/photo_wall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnwidth="@dimen/image_thumbnail_size"
android:gravity="center"
android:horizontalspacing="@dimen/image_thumbnail_spacing"
android:numcolumns="auto_fit"
android:stretchmode="columnwidth"
android:verticalspacing="@dimen/image_thumbnail_spacing" >
</gridview>
</linearlayout>
很簡單,隻是在linearlayout中寫了一個gridview而已。接着我們要定義gridview中每一個子view的布局,建立一個photo_layout.xml布局,加入如下代碼:
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<imageview
android:id="@+id/photo"
android:layout_centerinparent="true"
android:scaletype="fitxy"
/>
</relativelayout>
仍然很簡單,photo_layout.xml布局中隻有一個imageview控件,就是用它來顯示圖檔的。這樣我們就把所有的布局檔案都寫好了。
接下來建立photowalladapter做為gridview的擴充卡,代碼如下所示:
public class photowalladapter extends arrayadapter<string> {
/**
* 記錄所有正在下載下傳或等待下載下傳的任務。
*/
private set<bitmapworkertask> taskcollection;
* 圖檔緩存技術的核心類,用于緩存所有下載下傳好的圖檔,在程式記憶體達到設定值時會将最少最近使用的圖檔移除掉。
private lrucache<string, bitmap> mmemorycache;
* 圖檔硬碟緩存核心類。
private disklrucache mdisklrucache;
* gridview的執行個體
private gridview mphotowall;
* 記錄每個子項的高度。
private int mitemheight = 0;
public photowalladapter(context context, int textviewresourceid, string[] objects,
gridview photowall) {
super(context, textviewresourceid, objects);
mphotowall = photowall;
taskcollection = new hashset<bitmapworkertask>();
// 擷取應用程式最大可用記憶體
int maxmemory = (int) runtime.getruntime().maxmemory();
int cachesize = maxmemory / 8;
// 設定圖檔緩存大小為程式最大可用記憶體的1/8
mmemorycache = new lrucache<string, bitmap>(cachesize) {
@override
protected int sizeof(string key, bitmap bitmap) {
return bitmap.getbytecount();
}
};
try {
// 擷取圖檔緩存路徑
file cachedir = getdiskcachedir(context, "thumb");
if (!cachedir.exists()) {
cachedir.mkdirs();
// 建立disklrucache執行個體,初始化緩存資料
mdisklrucache = disklrucache
.open(cachedir, getappversion(context), 1, 10 * 1024 * 1024);
} catch (ioexception e) {
e.printstacktrace();
}
}
@override
public view getview(int position, view convertview, viewgroup parent) {
final string url = getitem(position);
view view;
if (convertview == null) {
view = layoutinflater.from(getcontext()).inflate(r.layout.photo_layout, null);
} else {
view = convertview;
final imageview imageview = (imageview) view.findviewbyid(r.id.photo);
if (imageview.getlayoutparams().height != mitemheight) {
imageview.getlayoutparams().height = mitemheight;
// 給imageview設定一個tag,保證異步加載圖檔時不會亂序
imageview.settag(url);
imageview.setimageresource(r.drawable.empty_photo);
loadbitmaps(imageview, url);
return view;
* 将一張圖檔存儲到lrucache中。
*
* @param key
* lrucache的鍵,這裡傳入圖檔的url位址。
* @param bitmap
* lrucache的鍵,這裡傳入從網絡上下載下傳的bitmap對象。
public void addbitmaptomemorycache(string key, bitmap bitmap) {
if (getbitmapfrommemorycache(key) == null) {
mmemorycache.put(key, bitmap);
* 從lrucache中擷取一張圖檔,如果不存在就傳回null。
* @return 對應傳入鍵的bitmap對象,或者null。
public bitmap getbitmapfrommemorycache(string key) {
return mmemorycache.get(key);
* 加載bitmap對象。此方法會在lrucache中檢查所有螢幕中可見的imageview的bitmap對象,
* 如果發現任何一個imageview的bitmap對象不在緩存中,就會開啟異步線程去下載下傳圖檔。
public void loadbitmaps(imageview imageview, string imageurl) {
bitmap bitmap = getbitmapfrommemorycache(imageurl);
if (bitmap == null) {
bitmapworkertask task = new bitmapworkertask();
taskcollection.add(task);
task.execute(imageurl);
} else {
if (imageview != null && bitmap != null) {
imageview.setimagebitmap(bitmap);
}
} catch (exception e) {
* 取消所有正在下載下傳或等待下載下傳的任務。
public void cancelalltasks() {
if (taskcollection != null) {
for (bitmapworkertask task : taskcollection) {
task.cancel(false);
* 根據傳入的uniquename擷取硬碟緩存的路徑位址。
public file getdiskcachedir(context context, string uniquename) {
string cachepath;
if (environment.media_mounted.equals(environment.getexternalstoragestate())
|| !environment.isexternalstorageremovable()) {
cachepath = context.getexternalcachedir().getpath();
cachepath = context.getcachedir().getpath();
return new file(cachepath + file.separator + uniquename);
* 擷取目前應用程式的版本号。
public int getappversion(context context) {
packageinfo info = context.getpackagemanager().getpackageinfo(context.getpackagename(),
0);
return info.versioncode;
} catch (namenotfoundexception e) {
return 1;
* 設定item子項的高度。
public void setitemheight(int height) {
if (height == mitemheight) {
return;
mitemheight = height;
notifydatasetchanged();
* 使用md5算法對傳入的key進行加密并傳回。
public string hashkeyfordisk(string key) {
string cachekey;
final messagedigest mdigest = messagedigest.getinstance("md5");
mdigest.update(key.getbytes());
cachekey = bytestohexstring(mdigest.digest());
} catch (nosuchalgorithmexception e) {
cachekey = string.valueof(key.hashcode());
return cachekey;
* 将緩存記錄同步到journal檔案中。
public void fluchcache() {
if (mdisklrucache != null) {
try {
mdisklrucache.flush();
} catch (ioexception e) {
e.printstacktrace();
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();
* 異步下載下傳圖檔的任務。
* @author guolin
class bitmapworkertask extends asynctask<string, void, bitmap> {
/**
* 圖檔的url位址
*/
private string imageurl;
@override
protected bitmap doinbackground(string... params) {
imageurl = params[0];
filedescriptor filedescriptor = null;
fileinputstream fileinputstream = null;
snapshot snapshot = null;
// 生成圖檔url對應的key
final string key = hashkeyfordisk(imageurl);
// 查找key對應的緩存
snapshot = mdisklrucache.get(key);
if (snapshot == null) {
// 如果沒有找到對應的緩存,則準備從網絡上請求資料,并寫入緩存
disklrucache.editor editor = mdisklrucache.edit(key);
if (editor != null) {
outputstream outputstream = editor.newoutputstream(0);
if (downloadurltostream(imageurl, outputstream)) {
editor.commit();
} else {
editor.abort();
}
}
// 緩存被寫入後,再次查找key對應的緩存
snapshot = mdisklrucache.get(key);
if (snapshot != null) {
fileinputstream = (fileinputstream) snapshot.getinputstream(0);
filedescriptor = fileinputstream.getfd();
// 将緩存資料解析成bitmap對象
bitmap bitmap = null;
if (filedescriptor != null) {
bitmap = bitmapfactory.decodefiledescriptor(filedescriptor);
if (bitmap != null) {
// 将bitmap對象添加到記憶體緩存當中
addbitmaptomemorycache(params[0], bitmap);
return bitmap;
} finally {
if (filedescriptor == null && fileinputstream != null) {
try {
fileinputstream.close();
} catch (ioexception e) {
return null;
protected void onpostexecute(bitmap bitmap) {
super.onpostexecute(bitmap);
// 根據tag找到相應的imageview控件,将下載下傳好的圖檔顯示出來。
imageview imageview = (imageview) mphotowall.findviewwithtag(imageurl);
if (imageview != null && bitmap != null) {
imageview.setimagebitmap(bitmap);
taskcollection.remove(this);
* 建立http請求,并擷取bitmap對象。
*
* @param imageurl
* 圖檔的url位址
* @return 解析後的bitmap對象
private boolean downloadurltostream(string urlstring, outputstream outputstream) {
httpurlconnection urlconnection = null;
bufferedoutputstream out = null;
bufferedinputstream in = null;
final url url = new url(urlstring);
urlconnection = (httpurlconnection) url.openconnection();
in = new bufferedinputstream(urlconnection.getinputstream(), 8 * 1024);
out = new bufferedoutputstream(outputstream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
return true;
} catch (final ioexception e) {
if (urlconnection != null) {
urlconnection.disconnect();
try {
if (out != null) {
out.close();
if (in != null) {
in.close();
} catch (final ioexception e) {
e.printstacktrace();
return false;
代碼有點長,我們一點點進行分析。首先在photowalladapter的構造函數中,我們初始化了lrucache類,并設定了記憶體緩存容量為程式最大可用記憶體的1/8,緊接着調用了disklrucache的open()方法來建立執行個體,并設定了硬碟緩存容量為10m,這樣我們就把lrucache和disklrucache的初始化工作完成了。
接着在getview()方法中,我們為每個imageview設定了一個唯一的tag,這個tag的作用是為了後面能夠準确地找回這個imageview,不然異步加載圖檔會出現亂序的情況。然後在getview()方法的最後調用了loadbitmaps()方法,加載圖檔的具體邏輯也就是在這裡執行的了。
進入到loadbitmaps()方法中可以看到,實作是調用了getbitmapfrommemorycache()方法來從記憶體中擷取緩存,如果擷取到了則直接調用imageview的setimagebitmap()方法将圖檔顯示到界面上。如果記憶體中沒有擷取到,則開啟一個bitmapworkertask任務來去異步加載圖檔。
那麼在bitmapworkertask的doinbackground()方法中,我們就靈活運用了上篇文章中學習的disklrucache的各種用法。首先根據圖檔的url生成對應的md5 key,然後調用disklrucache的get()方法來擷取硬碟緩存,如果沒有擷取到的話則從網絡上請求圖檔并寫入硬碟緩存,接着将bitmap對象解析出來并添加到記憶體緩存當中,最後将這個bitmap對象顯示到界面上,這樣一個完整的流程就執行完了。
那麼我們再來分析一下上述流程,每次加載圖檔的時候都優先去記憶體緩存當中讀取,當讀取不到的時候則回去硬碟緩存中讀取,而如果硬碟緩存仍然讀取不到的話,就從網絡上請求原始資料。不管是從硬碟緩存還是從網絡擷取,讀取到了資料之後都應該添加到記憶體緩存當中,這樣的話我們下次再去讀取圖檔的時候就能迅速從記憶體當中讀取到,而如果該圖檔從記憶體中被移除了的話,那就重複再執行一遍上述流程就可以了。
這樣我們就把lrucache和disklrucache完美結合到一起了。接下來還需要編寫mainactivity的代碼,非常簡單,如下所示:
public class mainactivity extends activity {
* 用于展示照片牆的gridview
* gridview的擴充卡
private photowalladapter madapter;
private int mimagethumbsize;
private int mimagethumbspacing;
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
mimagethumbsize = getresources().getdimensionpixelsize(
r.dimen.image_thumbnail_size);
mimagethumbspacing = getresources().getdimensionpixelsize(
r.dimen.image_thumbnail_spacing);
mphotowall = (gridview) findviewbyid(r.id.photo_wall);
madapter = new photowalladapter(this, 0, images.imagethumburls,
mphotowall);
mphotowall.setadapter(madapter);
mphotowall.getviewtreeobserver().addongloballayoutlistener(
new viewtreeobserver.ongloballayoutlistener() {
@override
public void ongloballayout() {
final int numcolumns = (int) math.floor(mphotowall
.getwidth()
/ (mimagethumbsize + mimagethumbspacing));
if (numcolumns > 0) {
int columnwidth = (mphotowall.getwidth() / numcolumns)
- mimagethumbspacing;
madapter.setitemheight(columnwidth);
mphotowall.getviewtreeobserver()
.removeglobalonlayoutlistener(this);
});
protected void onpause() {
super.onpause();
madapter.fluchcache();
protected void ondestroy() {
super.ondestroy();
// 退出程式時結束所有的下載下傳任務
madapter.cancelalltasks();
上述代碼中,我們通過getviewtreeobserver()的方式監聽view的布局事件,當布局完成以後,我們重新修改一下gridview中子view的高度,以保證子view的寬度和高度可以保持一緻。
到這裡還沒有結束,最後還需要配置一下androidmanifest.xml檔案,并加入相應的權限,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.photoswalldemo"
android:versioncode="1"
android:versionname="1.0" >
<uses-sdk
android:minsdkversion="14"
android:targetsdkversion="17" />
<uses-permission android:name="android.permission.internet" />
<uses-permission android:name="android.permission.write_external_storage" />
<application
android:allowbackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/apptheme" >
<activity
android:name="com.example.photoswalldemo.mainactivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.main" />
<category android:name="android.intent.category.launcher" />
</intent-filter>
</activity>
</application>
</manifest>
好了,全部代碼都在這兒了,讓我們來運作一下吧,效果如下圖所示:
第一次從網絡上請求圖檔的時候有點慢,但之後加載圖檔就會非常快了,滑動起來也很流暢。
那麼我們最後再檢查一下這些圖檔是不是已經正确緩存在指定位址了,進入 /sdcard/android/data/<application package>/cache/thumb 這個路徑,如下圖所示:
可以看到,每張圖檔的緩存以及journal檔案都在這裡了,說明我們的硬碟緩存已經成功了。
好了,今天的講解就到這裡,有疑問的朋友可以在下面留言。
原文位址http://blog.csdn.net/guolin_blog/article/details/34093441
<a target="_blank" href="http://download.csdn.net/detail/sinyu890807/7754669">源碼下載下傳,請點選這裡</a>