最近真夠忙的,瞎忙!好久沒(méi)寫(xiě)博客。不知道寫(xiě)什么,就寫(xiě)些最近對(duì)用戶(hù)體驗(yàn)這塊的一個(gè)小的見(jiàn)解吧。
無(wú)論大型或小型應(yīng)用, 靈活的緩存 可以說(shuō)不僅大大減輕了服務(wù)器的壓力,而且因?yàn)楦焖俚挠脩?hù)體驗(yàn)而方便了用戶(hù)。從事Android開(kāi)發(fā)工作以來(lái),個(gè)人認(rèn)為Android應(yīng)用可以說(shuō)是作為小型應(yīng)用,只是其中很多的開(kāi)發(fā)時(shí)間花費(fèi)在細(xì)節(jié)之上(UI交互方式、響應(yīng)速度、效果、字體、顏色等等),其中90%乃至99的應(yīng)用并不是需要實(shí)時(shí)更新的(即時(shí)通訊類(lèi)的除外:QQ),而且詬病于蝸牛般的移動(dòng)網(wǎng)速,3G也是狗屎(至少中國(guó)目前是這樣的),與服務(wù)器的數(shù)據(jù)交互是能少則少,這樣用戶(hù)體驗(yàn)才更好。
這是我公司產(chǎn)品(感興趣的朋友可以到這里下載,提有用建議有回報(bào)的哦http://www.hulutan.net/ 目前仍在不斷完善,后邊還有驚喜)的截圖,其實(shí)這個(gè)列表基本上可以全部用緩存數(shù)據(jù)來(lái)處理,即便你有新游戲發(fā)布,我不信你一個(gè)上百個(gè)應(yīng)用出世。
采用緩存,可以進(jìn)一步大大緩解數(shù)據(jù)交互的壓力,又能提供一定的離線(xiàn)瀏覽。下邊我簡(jiǎn)略列舉一下緩存管理的適用環(huán)境:
1. 提供網(wǎng)絡(luò)服務(wù)的應(yīng)用
2. 數(shù)據(jù)更新不需要實(shí)時(shí)更新,哪怕是3-5分鐘的延遲也是可以采用緩存機(jī)制。
3. 緩存的過(guò)期時(shí)間是可以接受的(類(lèi)似網(wǎng)易的新聞閱讀,支持離線(xiàn)離線(xiàn)閱讀)
這樣所帶來(lái)的好處:
1. 減小服務(wù)器的壓力
2. 提高客戶(hù)端的響應(yīng)速度(本地?cái)?shù)據(jù)提取嘛)
3. 一定程度上支持離線(xiàn)瀏覽(可以參考網(wǎng)易的那個(gè)新聞應(yīng)用,個(gè)人感覺(jué)離線(xiàn)閱讀做得非常棒。)
一、 緩存管理的方法
緩存管理的原理很簡(jiǎn):通過(guò)時(shí)間的設(shè)置來(lái)判斷是否讀取緩存還是重新下載;斷網(wǎng)下就沒(méi)什么好說(shuō)的,直接去緩存即可。如圖:
里面會(huì)有一些細(xì)節(jié)的處理,后面會(huì)詳細(xì)闡述。基于這個(gè)原理,目前個(gè)人用過(guò)的兩種比較常見(jiàn)的緩存管理方法是:數(shù)據(jù)庫(kù)和文件(txt)。
二、 數(shù)據(jù)庫(kù)(SQLite)緩存方式
這種方法是在下載完數(shù)據(jù)文件后,把文件的相關(guān)信息如url,路經(jīng),下載時(shí)間,過(guò)期時(shí)間等存放到數(shù)據(jù)庫(kù),當(dāng)然我個(gè)人建議把url作為唯一的標(biāo)識(shí)。下次下載的時(shí)候根據(jù)url先從數(shù)據(jù)庫(kù)中查詢(xún),如果查詢(xún)到當(dāng)前時(shí)間并未過(guò)期,就根據(jù)路徑讀取本地文件,從而實(shí)現(xiàn)緩存的效果。
從實(shí)現(xiàn)上我們可以看到這種方法可以靈活存放文件的屬性,進(jìn)而提供了很大的擴(kuò)展性,可以為其它的功能提供一定的支持。
從操作上需要?jiǎng)?chuàng)建數(shù)據(jù)庫(kù),每次查詢(xún)數(shù)據(jù)庫(kù),如果過(guò)期還需要更新數(shù)據(jù)庫(kù),清理緩存的時(shí)候還需要?jiǎng)h除數(shù)據(jù)庫(kù)數(shù)據(jù),稍顯麻煩,而數(shù)據(jù)庫(kù)操作不當(dāng)又容易出現(xiàn)一系列的性能,ANR問(wèn)題,指針錯(cuò)誤問(wèn)題,實(shí)現(xiàn)的時(shí)候要謹(jǐn)慎,具體作的話(huà),但也只是增加一個(gè)工具類(lèi)或方法的事情。
還有一個(gè)問(wèn)題,緩存的數(shù)據(jù)庫(kù)是存放在/data/data/<package>/databases/目錄下,是占用內(nèi)存空間的,如果緩存累計(jì),容易浪費(fèi)內(nèi)存,需要及時(shí)清理緩存。
當(dāng)然這種方法從目前一些應(yīng)用的實(shí)用上看,我沒(méi)有發(fā)現(xiàn)什么問(wèn)題,估計(jì)使用的量還比較少吧。
本文本人不太喜歡數(shù)據(jù)庫(kù),原因操作麻煩,尤其是要自己寫(xiě)建表那些語(yǔ)句,你懂的。我側(cè)重文件緩存方式。
三、文件緩存方式
這種方法,使用File.lastModified()方法得到文件的最后修改時(shí)間,與當(dāng)前時(shí)間判斷是否過(guò)期,從而實(shí)現(xiàn)緩存效果。
實(shí)現(xiàn)上只能使用這一個(gè)屬性,沒(méi)有為其它的功能提供技術(shù)支持的可能。操作上倒是簡(jiǎn)單,比較時(shí)間即可,而且取的數(shù)據(jù)也就是文件里的JSON數(shù)據(jù)而已。本身處理也不容易帶來(lái)其它問(wèn)題,代價(jià)低廉。
四、文件法緩存方式的兩點(diǎn)說(shuō)明
1. 不同類(lèi)型的文件的緩存時(shí)間不一樣。
籠統(tǒng)的說(shuō),不變文件的緩存時(shí)間是永久,變化文件的緩存時(shí)間是最大忍受不變時(shí)間。說(shuō)白點(diǎn),圖片文件內(nèi)容是不變的,一般存在SD卡上直到被清理,我們是可以永遠(yuǎn)讀取緩存的。配置文件內(nèi)容是可能更新的,需要設(shè)置一個(gè)可接受的緩存時(shí)間。
2. 不同環(huán)境下的緩存時(shí)間標(biāo)準(zhǔn)不一樣。
無(wú)網(wǎng)絡(luò)環(huán)境下,我們只能讀取緩存文件,為了應(yīng)用有東西顯示,沒(méi)有什么過(guò)期之說(shuō)了。
WiFi網(wǎng)絡(luò)環(huán)境下,緩存時(shí)間可以設(shè)置短一點(diǎn),一是網(wǎng)速較快,而是流量不要錢(qián)。
3G流量環(huán)境下,緩存時(shí)間可以設(shè)置長(zhǎng)一點(diǎn),節(jié)省流量,就是節(jié)省金錢(qián),而且用戶(hù)體驗(yàn)也更好。
GPS就別說(shuō)更新什么的,已經(jīng)夠慢的了。緩存時(shí)間能多長(zhǎng)就多長(zhǎng)把。
當(dāng)然,作為一款好的應(yīng)用,不會(huì)死定一種情況,針對(duì)于不同網(wǎng)絡(luò)變換不同形式的緩存功能是必須有的。而且這個(gè)時(shí)間根據(jù)自己的實(shí)際情況來(lái)設(shè)置:數(shù)據(jù)的更新頻率,數(shù)據(jù)的重要性等。
五、何時(shí)刷新
開(kāi)發(fā)者一方面希望盡量讀取緩存,用戶(hù)一方面希望實(shí)時(shí)刷新,但是響應(yīng)速度越快越好,流量消耗越少越好(關(guān)于這塊,的確開(kāi)發(fā)中我沒(méi)怎么想到,畢竟接口就是這么多,現(xiàn)在公司的產(chǎn)品幾乎點(diǎn)一下就訪(fǎng)問(wèn)一下,而且還有些雞肋多余的功能。慢慢修改哈哈),是一個(gè)矛盾。
其實(shí)何時(shí)刷新我也不知道,這里我提供兩點(diǎn)建議:
1. 數(shù)據(jù)的最長(zhǎng)多長(zhǎng)時(shí)間不變,對(duì)應(yīng)用無(wú)大的影響。
比如,你的數(shù)據(jù)更新時(shí)間為4小時(shí),則緩存時(shí)間設(shè)置為1~2小時(shí)比較合適。也就是更新時(shí)間/緩存時(shí)間=2,但用戶(hù)個(gè)人修改、網(wǎng)站編輯人員等一些人為的更新就另說(shuō)。一天用戶(hù)總會(huì)看到更新,即便有延遲也好,視你產(chǎn)品的用途了;如果你覺(jué)得你是資訊類(lèi)應(yīng)用,再減少,2~4小時(shí),如果你覺(jué)得數(shù)據(jù)比較重要或者比較受歡迎,用戶(hù)會(huì)經(jīng)常把玩,再減少,1~2小時(shí),依次類(lèi)推。
當(dāng)然類(lèi)似這個(gè)界面的數(shù)據(jù)我認(rèn)為更新時(shí)間能多長(zhǎng)就多長(zhǎng)了,盡可能長(zhǎng)。如果你拿后邊那個(gè)有多少數(shù)據(jù)會(huì)變動(dòng)來(lái)搪塞。我會(huì)告訴你:這個(gè)只是一個(gè)引導(dǎo)性的界面,你有多少款游戲跟用戶(hù)半毛錢(qián)關(guān)系都沒(méi)有,10億也跟他沒(méi)關(guān),他只要確定這里能找到他要找的 湯姆貓 就行。否則你又失去了一個(gè)用戶(hù)。
2. 提供刷新按鈕。
必要時(shí)候或最保險(xiǎn)的方法使在相關(guān)界面提供一個(gè)刷新按鈕,或者當(dāng)下流行的下拉列表刷新方式。為緩存,為加載失敗提供一次重新來(lái)過(guò)的機(jī)會(huì)。畢竟喝骨頭湯的時(shí)候,我也不介意碗旁多雙筷子。
總而言之,一切用戶(hù)至上,為了更好的用戶(hù)體驗(yàn),方法也會(huì)層出不窮。 期待更好的辦法
關(guān)鍵代碼:
package com.hulutan.gamestore.cache; import java.io.File; import java.io.IOException; import android.os.Environment; import android.util.Log; import com.hulutan.gamestore.Constants; import com.hulutan.gamestore.GameStoreApplication; import com.hulutan.gamestore.util.EncryptUtils; import com.hulutan.gamestore.util.FileUtils; import com.hulutan.gamestore.util.NetworkUtils; import com.hulutan.gamestore.util.NetworkUtils.NetworkState; import com.hulutan.gamestore.util.StringUtils; /** * 緩存工具類(lèi) * @author naibo-liao * @時(shí)間: 2013-1-4下午02:30:52 */ public class ConfigCacheUtil { private static final String TAG=ConfigCacheUtil.class.getName(); /** 1秒超時(shí)時(shí)間 */ public static final int CONFIG_CACHE_SHORT_TIMEOUT=1000 * 60 * 5; // 5 分鐘 /** 5分鐘超時(shí)時(shí)間 */ public static final int CONFIG_CACHE_MEDIUM_TIMEOUT=1000 * 3600 * 2; // 2小時(shí) /** 中長(zhǎng)緩存時(shí)間 */ public static final int CONFIG_CACHE_ML_TIMEOUT=1000 * 60 * 60 * 24 * 1; // 1天 /** 最大緩存時(shí)間 */ public static final int CONFIG_CACHE_MAX_TIMEOUT=1000 * 60 * 60 * 24 * 7; // 7天 /** * CONFIG_CACHE_MODEL_LONG : 長(zhǎng)時(shí)間(7天)緩存模式 <br> * CONFIG_CACHE_MODEL_ML : 中長(zhǎng)時(shí)間(12小時(shí))緩存模式<br> * CONFIG_CACHE_MODEL_MEDIUM: 中等時(shí)間(2小時(shí))緩存模式 <br> * CONFIG_CACHE_MODEL_SHORT : 短時(shí)間(5分鐘)緩存模式 */ public enum ConfigCacheModel { CONFIG_CACHE_MODEL_SHORT, CONFIG_CACHE_MODEL_MEDIUM, CONFIG_CACHE_MODEL_ML, CONFIG_CACHE_MODEL_LONG; } /** * 獲取緩存 * @param url 訪(fǎng)問(wèn)網(wǎng)絡(luò)的URL * @return 緩存數(shù)據(jù) */ public static String getUrlCache(String url, ConfigCacheModel model) { if(url == null) { return null; } String result=null; String path=Constants.ENVIROMENT_DIR_CACHE + StringUtils.replaceUrlWithPlus(EncryptUtils.encryptToMD5(url)); File file=new File(path); if(file.exists() && file.isFile()) { long expiredTime=System.currentTimeMillis() - file.lastModified(); Log.d(TAG, file.getAbsolutePath() + " expiredTime:" + expiredTime / 60000 + "min"); // 1。如果系統(tǒng)時(shí)間是不正確的 // 2。當(dāng)網(wǎng)絡(luò)是無(wú)效的,你只能讀緩存 if(NetworkUtils.getNetworkState(GameStoreApplication.getInstance().getContext()) != NetworkState.NETWORN_NONE) { if(expiredTime < 0) { return null; } if(model == ConfigCacheModel.CONFIG_CACHE_MODEL_SHORT) { if(expiredTime > CONFIG_CACHE_SHORT_TIMEOUT) { return null; } } else if(model == ConfigCacheModel.CONFIG_CACHE_MODEL_MEDIUM) { if(expiredTime > CONFIG_CACHE_MEDIUM_TIMEOUT) { return null; } } else if(model == ConfigCacheModel.CONFIG_CACHE_MODEL_ML) { if(expiredTime > CONFIG_CACHE_ML_TIMEOUT) { return null; } } else if(model == ConfigCacheModel.CONFIG_CACHE_MODEL_LONG) { if(expiredTime > CONFIG_CACHE_MEDIUM_TIMEOUT) { return null; } } else { if(expiredTime > CONFIG_CACHE_MAX_TIMEOUT) { return null; } } } try { result=FileUtils.readTextFile(file); } catch(IOException e) { e.printStackTrace(); } } return result; } /** * 設(shè)置緩存 * @param data * @param url */ public static void setUrlCache(String data, String url) { if(Constants.ENVIROMENT_DIR_CACHE == null) { return; } File dir=new File(Constants.ENVIROMENT_DIR_CACHE); if(!dir.exists() && Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) { dir.mkdirs(); } File file=new File(Constants.ENVIROMENT_DIR_CACHE + StringUtils.replaceUrlWithPlus(EncryptUtils.encryptToMD5(url))); try { // 創(chuàng)建緩存數(shù)據(jù)到磁盤(pán),就是創(chuàng)建文件 FileUtils.writeTextFile(file, data); } catch(IOException e) { Log.d(TAG, "write " + file.getAbsolutePath() + " data failed!"); e.printStackTrace(); } catch(Exception e) { e.printStackTrace(); } } /** * 刪除歷史緩存文件 * @param cacheFile */ public static void clearCache(File cacheFile) { if(cacheFile == null) { if(Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) { try { File cacheDir=new File(Environment.getExternalStorageDirectory().getPath() + "/hulutan/cache/"); if(cacheDir.exists()) { clearCache(cacheDir); } } catch(Exception e) { e.printStackTrace(); } } } else if(cacheFile.isFile()) { cacheFile.delete(); } else if(cacheFile.isDirectory()) { File[] childFiles=cacheFile.listFiles(); for(int i=0; i < childFiles.length; i++) { clearCache(childFiles[i]); } } } }
獲取緩存:
String cacheConfigString=ConfigCacheUtil.getUrlCache(Net.API_HELP, ConfigCacheModel.CONFIG_CACHE_MODEL_LONG); if(cacheConfigString != null) { //do something }
設(shè)置緩存:
ConfigCacheUtil.setUrlCache(data, Net.API_HELP);
產(chǎn)品設(shè)計(jì)之路:Android應(yīng)用-開(kāi)發(fā)技術(shù)【數(shù)據(jù)緩存】
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
