Android数据离线缓存

离线缓存就是在网络畅通的情况下将从服务器取到的数据保存到本地,在网络断开的时候直接去本地数据显示。这样避免在没有网路的时候进入应用是一片空白。在网上找了很多实现,这里记录了一种。


将网络数据保存到本地:

你可以自己写一个保存数据成本地文件的方法,保存在android系统的任意目录(当然是有权限的才行),但是在这种情况下使用Context的openFileOutput方法最简便也最符合我们的场景,下面的saveObject方法演示了如何用openFileOutput将数据保存在本地的一个文件中:

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//files下面),然后使用java io中的ObjectOutputStream将 序列化的对象 写入( writeObject )到得到的文件中,你可以看到上面的实现过程有两个关键方法: openFileOutput writeObject 以及调用它们的两个关键对象ContextObjectOutputStream

是将一个序列化的对象保存在本地,跟我们的离线缓存保存网络数据有什么关系呢?

有关系,因为网上获取的数据大多可以转换成String类型的字符串,现在服务端返回的数据一般是json格式的字符串。而String类型的字符串其实就是可序列化的对象。下面是一个服务器返回json数据的例子(其实就是jcodecraeer):

用上面的 saveObject 方法我们可以将数据保存在本地,为了能够取出这个文件我们必须想好如何为这个保存的文件命名,如果是单纯的一篇文章的数据,我们可以直接将文件名命名为这篇文章的id,因为id是唯一的,为了尽可能的不和其他数据发生冲突,你还可以在这个id之前加一个前缀,比如这篇文章是java栏目下的我们可以这样 arc_java_id。如果是文章列表我们可以这样命名:文章类别_分页页码,总之命名的原则是能和其他离线数据区别,有唯一性。为什么不用url作为文件名呢?url肯定是唯一的,但是url不一定符合文件的命名规范。

下面来讲解如何读取本地缓存的数据

读取缓存的时候我们只需要知道文件名就可以了,下面的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是跟网络相关的工具类,这里涉及到它的三个方法:

1
2
3
4
isNetworkConnected()判断网络是否可用
saveObject上面已经给出了实现
readObject上面已经给出了实现
http_get读取指定url的服务器数据

AppContext.getInstance()是我自己写的,是为了方便在HttpUtil的静态方法中获得Context对象。

额外的需求

有时候我们还有这样的需求,当用户在指定间隔时间内读取同一数据源时,从本地获取,超过这个时间间隔从网络获取,这样做的目的是节省用户的流量,同时也避免了每次从网络获取数据造成的界面延迟。

下面实现了如何根据时间间隔判断是否需要刷新服务器数据,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";
}


上面的所有内容就是整个过程了:下面贴出这个HttpUtil类。稍微有点改动,将上下文context以参数的方式传进去了。还有就是序列号对象改成了String型数据。

public class HttpUtil {

    /**
     * 将json文件写入本地储存
     * @param context
     * @param string
     * @param file  文件名
     * @return
     */
    public static boolean saveObject(Context context,String string, String file) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = context.openFileOutput(file, context.MODE_PRIVATE);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(string);
            oos.flush();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                oos.close();
            } catch (Exception e) {
            }
            try {
                fos.close();
            } catch (Exception e) {
            }
        }
    }


    /**
     * 从本地文件读取json
     *
     * @param file
     * @return
     * @throws Exception
     */
    public static String readObject(Context context,String file) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = context.openFileInput(file);
            ois = new ObjectInputStream(fis);
            return (String) 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;
    }

    /**
     * 缓存文件超过CACHE_TIME毫秒才去访问网络取数据
     * @param context
     * @param cachefile
     * @return
     */
    private static int CACHE_TIME = 600000;  //缓存文件超过10分钟才访问网络取数据,10分钟以内直接读取缓存文件中的数据
    public static boolean isCacheDataFailure(Context context,String cachefile) {
        boolean failure = false;
        File data = context.getFileStreamPath(cachefile);
        if (data.exists()
                && (System.currentTimeMillis() - data.lastModified()) > CACHE_TIME)
            failure = true;
        else if (!data.exists())
            failure = true;
        return failure;
    }
}






你可能感兴趣的:(android)