android離線緩存

轉自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1209/2136.html


離線緩存就是在網絡暢通的情況下將從服務器收到的數據保存到本地,當網絡斷開之後直接讀取本地文件中的數據。
將網絡數據保存到本地:
你可以自己寫一個保存數據成本地文件的方法,保存在android系統的任意目錄(當然是有權限的才行),但是在這種情況下使用Context的openFileOutput方法最簡便也最符合我們的場景,下面的saveObject方法演示瞭如何用openFileOutput將數據保存在本地的一個文件中:

saveObject

public static boolean saveObject(Serializable ser, String file) {
    FileOutputStream fos = null;
    ObjectOutputStream oos = null;
    try {
        fos = AppContext.getInstance().openFileOutput(file, AppContext.getInstance().MODE_PRIVATE);
        oos = new ObjectOutputStream(fos);
        oos.writeObject(ser);
        oos.flush();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    } finally {
        try {
            oos.close();
        } catch (Exception e) {
        }
        try {
            fos.close();
        } catch (Exception e) {
        }
    }
}

openFileOutput可以直接獲得一個和應用關聯的文件路徑(在/data/data/<package name>/files下面),然後使用java io中的ObjectOutputStream將序列化的對象寫入(writeObject)到得到的文件中,你可以看到上面的實現過程有兩個關鍵方法:openFileOutput、writeObject以及調用它們的兩個關鍵對象Context和ObjectOutputStream。關於序列化可以參看這篇文章:Java對象的序列化和反序列化實踐  

這是將一個序列化的對象保存在本地,跟我們的離線緩存保存網絡數據有什麼關係呢?
有關係,因爲網上獲取的數據大多可以轉換成String類型的字符串,現在服務端返回的數據一般是json格式的字符串。而String類型的字符串其實就是可序列化的對象。下面是一個服務器返回json數據的例子(其實就是jcodecraeer):
{"url":"http://jcodecraeer.com/uploads/soft/android/CodeBox.apk","versionCode":"7","updateMessage":"增加離線緩存,分類篩選功能修正了版本兼容性問題 "}

用上面的saveObject方法我們可以將數據保存在本地,爲了能夠取出這個文件我們必須想好如何爲這個保存的文件命名,如果是單純的一篇文章的數據,我們可以直接將文件名命名爲這篇文章的id,因爲id是唯一的,爲了儘可能的不和其他數據發生衝突,你還可以在這個id之前加一個前綴,比如這篇文章是java欄目下的我們可以這樣 arc_java_id。如果是文章列表我們可以這樣命名:文章類別_分頁頁碼,總之命名的原則是能和其他離線數據區別,有唯一性。爲什麼不用url作爲文件名呢?url肯定是唯一的,但是url不一定符合文件的命名規範。


下面來講解如何讀取本地緩存的數據


讀取緩存的時候我們只需要知道文件名就可以了,下面的readObject方法實現了根據文件名讀取緩存數據。其實很多東西是和上面保存數據對應的。


readObject

/**
 * 讀取對象
 *
 * @param file
 * @return
 * @throws IOException
 */
public static Serializable readObject(String file) {
    FileInputStream fis = null;
    ObjectInputStream ois = null;
    try {
        fis = AppContext.getInstance().openFileInput(file);
        ois = new ObjectInputStream(fis);
        return (Serializable) ois.readObject();
    } catch (FileNotFoundException e) {
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            ois.close();
        } catch (Exception e) {
        }
        try {
            fis.close();
        } catch (Exception e) {
        }
    }
    return null;
}

運用

下面的代碼演示瞭如何用上面的知識存儲和讀取網絡數據

String key = "codelist_" +  mCategory.getValue()  + "_" + + page ;
String result = "";
//cache
if (HttpUtil.isNetworkConnected()) {
        result = HttpUtil.http_get(AppContext.getInstance(), url );
        HttpUtil.saveObject(result, key);
        result = (String) HttpUtil.readObject(key);
} else {
    result = (String) HttpUtil.readObject(key);
    if (result == null)
        result = "erro";
}

當網絡暢通時,從服務器獲取數據( HttpUtil.http_get(AppContext.getInstance(), url )),同時將數據保存到本地(HttpUtil.saveObject),而當網絡不可用時,直接從本地讀取緩存的數據,不跟服務器發生交互。
其中HttpUtil是跟網絡相關的工具類,這裏涉及到它的三個方法:
isNetworkConnected()判斷網絡是否可用
saveObject上面已經給出了實現
readObject上面已經給出了實現
http_get讀取指定url的服務器數據
而AppContext.getInstance()是我自己寫的,是爲了方便在HttpUtil的靜態方法中獲得Context對象。
這裏的key就是文件名。

額外的需求

有時候我們還有這樣的需求,當用戶在指定間隔時間內讀取同一數據源時,從本地獲取,超過這個時間間隔從網絡獲取,這樣做的目的是節省用戶的流量,同時也避免了每次從網絡獲取數據造成的界面延遲。


下面實現瞭如何根據時間間隔判斷是否需要刷新服務器數據,true表示不需要,false表示需要(很彆扭是吧,這跟isCacheDataFailure這個命名有關係):
public static boolean isCacheDataFailure(String cachefile) {
    boolean failure = false;
    File data = AppContext.getInstance().getFileStreamPath(cachefile);
    if (data.exists()
            && (System.currentTimeMillis() - data.lastModified()) > CACHE_TIME)
        failure = true;
    else if (!data.exists())
        failure = true;
    return failure;
}

將當前時間和文件的修改時間做比較 ,CACHE_TIME是一個固定值(毫秒),你可以替換成任意int類型。
將這個判斷條件加入,然後上面的代碼改成:

String key = "codelist_" +  mCategory.getValue()  + "_" + + page ;
String result = "";
//cache
if (HttpUtil.isNetworkConnected() && HttpUtil.isCacheDataFailure(key)) {
        result = HttpUtil.http_get(AppContext.getInstance(), url );
        HttpUtil.saveObject(result, key);
        result = (String) HttpUtil.readObject(key);
} else {
    result = (String) HttpUtil.readObject(key);
    if (result == null)
        result = "erro";
}

完善

上面的步驟對於一般應用來說已經夠用了,但是在要求比較高的情況下,我們還得考慮隨着時間的流逝,緩存數據會越來越多,因此我們需要增加刪除過期緩存的功能,原理就是設置一個閥值,在保存緩存的時候,判斷當前緩存的總量是否大於閥值,如果是則刪除時間較早的緩存。


這個實現起來有點複雜,可以考慮更簡單的方案,定期檢查(或者用戶每打開一次程序)緩存總量,當大於閥值,提示用戶主動刪除。具體實現就不多說了。


注:openFileOutput()方法的第一參數用於指定文件名稱,不能包含路徑分隔符“/” ,如果文件不存在,Android 會自動創建它。創建的文件保存在/data/data/<package name>/files目錄,如: /data/data/cn.itcast.action/files/itcast.txt ,通過點擊Eclipse菜單“Window”-“Show View”-“Other”,在對話窗口中展開android文件夾,選擇下面的File Explorer視圖,然後在File Explorer視圖中展開/data/data/<package name>/files目錄就可以看到該文件。
openFileOutput()方法的第二參數用於指定操作模式,有四種模式,分別爲: Context.MODE_PRIVATE    =  0
Context.MODE_APPEND    =  32768
Context.MODE_WORLD_READABLE =  1
Context.MODE_WORLD_WRITEABLE =  2
Context.MODE_PRIVATE:爲默認操作模式,代表該文件是私有數據,只能被應用本身訪問,在該模式下,寫入的內容會覆蓋原文件的內容,如果想把新寫入的內容追加到原文件中。可以使用Context.MODE_APPEND
Context.MODE_APPEND:模式會檢查文件是否存在,存在就往文件追加內容,否則就創建新文件。
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用來控制其他應用是否有權限讀寫該文件。
MODE_WORLD_READABLE:表示當前文件可以被其他應用讀取;MODE_WORLD_WRITEABLE:表示當前文件可以被其他應用寫入。
如果希望文件被其他應用讀和寫,可以傳入:
openFileOutput(“itcast.txt”, Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章