Android 数据缓存

采用缓存,可以进一步大大缓解数据交互的压力,又能提供一定的离线浏览。下边简略列举一下缓存管理的适用环境:

1. 提供网络服务的应用

2. 数据更新不需要实时更新,哪怕是3-5分钟的延迟也是可以采用缓存机制。

3. 缓存的过期时间是可以接受的(类似网易的新闻阅读,支持离线离线阅读)

这样所带来的好处:

1. 减小服务器的压力

2. 提高客户端的响应速度(本地数据提取嘛)

3. 一定程度上支持离线浏览(可以参考网易的那个新闻应用,个人感觉离线阅读做得非常棒。)

 

一、缓存管理的方法

缓存管理的原理很简:通过时间的设置来判断是否读取缓存还是重新下载;断网下就没什么好说的,直接去缓存即可。

二、数据库(SQLite)缓存方式

这种方法是在下载完数据文件后,把文件的相关信息如url,路经,下载时间,过期时间等存放到数据库,当然我个人建议把url作为唯一的标识。下次下载的时候根据url先从数据库中查询,如果查询到当前时间并未过期,就根据路径读取本地文件,从而实现缓存的效果。

从实现上我们可以看到这种方法可以灵活存放文件的属性,进而提供了很大的扩展性,可以为其它的功能提供一定的支持。

从操作上需要创建数据库,每次查询数据库,如果过期还需要更新数据库,清理缓存的时候还需要删除数据库数据,稍显麻烦,而数据库操作不当又容易出现一系列的性能,ANR问题,指针错误问题,实现的时候要谨慎,具体作的话,但也只是增加一个工具类或方法的事情。

还有一个问题,缓存的数据库是存放在/data/data/<package>/databases/目录下,是占用内存空间的,如果缓存累计,容易浪费内存,需要及时清理缓存。

 

三、文件缓存方式

这种方法,使用File.lastModified()方法得到文件的最后修改时间,与当前时间判断是否过期,从而实现缓存效果。

实现上只能使用这一个属性,没有为其它的功能提供技术支持的可能。操作上倒是简单,比较时间即可,而且取的数据也就是文件里的JSON数据而已。本身处理也不容易带来其它问题,代价低廉。

 

四、文件法缓存方式的两点说明

1. 不同类型的文件的缓存时间不一样。

笼统的说,不变文件的缓存时间是永久,变化文件的缓存时间是最大忍受不变时间。说白点,图片文件内容是不变的,一般存在SD卡上直到被清理,我们是可以永远读取缓存的。配置文件内容是可能更新的,需要设置一个可接受的缓存时间。

2. 不同环境下的缓存时间标准不一样。

无网络环境下,我们只能读取缓存文件,为了应用有东西显示,没有什么过期之说了。

WiFi网络环境下,缓存时间可以设置短一点,一是网速较快,而是流量不要钱。

3G流量环境下,缓存时间可以设置长一点,节省流量,就是节省金钱,而且用户体验也更好。

GPS就别说更新什么的,已经够慢的了。缓存时间能多长就多长把。

当然,作为一款好的应用,不会死定一种情况,针对于不同网络变换不同形式的缓存功能是必须有的。而且这个时间根据自己的实际情况来设置:数据的更新频率,数据的重要性等。

 

五、何时刷新

        开发者一方面希望尽量读取缓存,用户一方面希望实时刷新,但是响应速度越快越好,流量消耗越少越好(关于这块,的确开发中我没怎么想到,毕竟接口就是这么多,现在公司的产品几乎点一下就访问一下,而且还有些鸡肋多余的功能。慢慢修改哈哈),是一个矛盾。

其实何时刷新我也不知道,这里我提供两点建议:

1. 数据的最长多长时间不变,对应用无大的影响。

        比如,你的数据更新时间为4小时,则缓存时间设置为1~2小时比较合适。也就是更新时间/缓存时间=2,但用户个人修改、网站编辑人员等一些人为的更新就另说。一天用户总会看到更新,即便有延迟也好,视你产品的用途了;如果你觉得你是资讯类应用,再减少,2~4小时,如果你觉得数据比较重要或者比较受欢迎,用户会经常把玩,再减少,1~2小时,依次类推。

2. 提供刷新按钮。

        必要时候或最保险的方法使在相关界面提供一个刷新按钮,或者当下流行的下拉列表刷新方式。为缓存,为加载失败提供一次重新来过的机会。

 

 

package com.huancun.cache;

import java.io.File;
import java.io.IOException;

import android.os.Environment;
import android.util.Log;

import com.huancun.Constants;
import com.huancun.GameStoreApplication;
import com.huancun.util.EncryptUtils;
import com.huancun.util.FileUtils;
import com.huancun.util.NetworkUtils;
import com.huancun.util.NetworkUtils.NetworkState;
import com.huancun.util.StringUtils;

/**
 * 缓存工具类
 * @author LAXlerbo
 * @时间: 2015-3-18下午01:35:26
 */
public class ConfigCacheUtil {

    private static final String TAG=ConfigCacheUtil.class.getName();

    /** 1秒超时时间 */
    public static final int CONFIG_CACHE_SHORT_TIMEOUT=1000 * 60 * 5; // 5 分钟

    /** 5分钟超时时间 */
    public static final int CONFIG_CACHE_MEDIUM_TIMEOUT=1000 * 3600 * 2; // 2小时

    /** 中长缓存时间 */
    public static final int CONFIG_CACHE_ML_TIMEOUT=1000 * 60 * 60 * 24 * 1; // 1天

    /** 最大缓存时间 */
    public static final int CONFIG_CACHE_MAX_TIMEOUT=1000 * 60 * 60 * 24 * 7; // 7天

    /**
     * CONFIG_CACHE_MODEL_LONG : 长时间(7天)缓存模式 
* CONFIG_CACHE_MODEL_ML : 中长时间(12小时)缓存模式
* CONFIG_CACHE_MODEL_MEDIUM: 中等时间(2小时)缓存模式
* CONFIG_CACHE_MODEL_SHORT : 短时间(5分钟)缓存模式 */ public enum ConfigCacheModel { CONFIG_CACHE_MODEL_SHORT, CONFIG_CACHE_MODEL_MEDIUM, CONFIG_CACHE_MODEL_ML, CONFIG_CACHE_MODEL_LONG; } /** * 获取缓存 * @param url 访问网络的URL * @return 缓存数据 */ 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。如果系统时间是不正确的 // 2。当网络是无效的,你只能读缓存 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; } /** * 设置缓存 * @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 { // 创建缓存数据到磁盘,就是创建文件 FileUtils.writeTextFile(file, data); } catch(IOException e) { Log.d(TAG, "write " + file.getAbsolutePath() + " data failed!"); e.printStackTrace(); } catch(Exception e) { e.printStackTrace(); } } /** * 处理特殊字符 * */ public static String getCacheDecodeString(String url) { //1. 处理特殊字符 //2. 去除后缀名带来的文件浏览器的视图凌乱(特别是图片更需要如此类似处理,否则有的手机打开图库,全是我们的缓存图片) if (url != null) { return url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+"); } return null; } /** * 删除历史缓存文件 * @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 } //设置缓存 ConfigCacheUtil.setUrlCache(data, Net.API_HELP); */
 
 
 
从实现上我们全面考虑了几个细节,注释已经说明,不再赘述。
      然后我们调用方法如下:
 
void getConfig(){
        //首先尝试读取缓存
        String cacheConfigString = ConfigCache.getUrlCache(CONFIG_URL);
        //根据结果判定是读取缓存,还是重新读取
        if (cacheConfigString != null) {
            showConfig(cacheConfigString);
        } else {
            //如果缓存结果是空,说明需要重新加载
            //缓存为空的原因可能是1.无缓存;2. 缓存过期;3.读取缓存出错
            AsyncHttpClient client = new AsyncHttpClient();
            client.get(CONFIG_URL, new AsyncHttpResponseHandler(){
 
                @Override
                public void onSuccess(String result){
                    //成功下载,则保存到本地作为后面缓存文件
                    ConfigCache.setUrlCache(result,  CONFIG_URL);
                    //后面可以是UI更新,仅供参考
                    showConfig(result);
                }
 
                @Override
                public void onFailure(Throwable arg0) {
                    //根据失败原因,考虑是显示加载失败,还是再读取缓存
                }
 
            });
        }
    }

 

/**
 * 文件处理工具类
 * 
 * @author LAXlerbo
 * @时间: 2015-3-18下午01:23:36
 */
public class FileUtils {

	public static final long B = 1;
	public static final long KB = B * 1024;
	public static final long MB = KB * 1024;
	public static final long GB = MB * 1024;
	private static final int BUFFER = 8192;
	/**
	 * 格式化文件大小 带有单位
	 * 
	 * @param size
	 * @return
	 */
	public static String formatFileSize(long size) {
		StringBuilder sb = new StringBuilder();
		String u = null;
		double tmpSize = 0;
		if (size < KB) {
			sb.append(size).append("B");
			return sb.toString();
		} else if (size < MB) {
			tmpSize = getSize(size, KB);
			u = "KB";
		} else if (size < GB) {
			tmpSize = getSize(size, MB);
			u = "MB";
		} else {
			tmpSize = getSize(size, GB);
			u = "GB";
		}
		return sb.append(twodot(tmpSize)).append(u).toString();
	}

	/**
	 * 保留两位小数
	 * 
	 * @param d
	 * @return
	 */
	public static String twodot(double d) {
		return String.format("%.2f", d);
	}

	public static double getSize(long size, long u) {
		return (double) size / (double) u;
	}

	/**
	 * sd卡挂载且可用
	 * 
	 * @return
	 */
	public static boolean isSdCardMounted() {
		return android.os.Environment.getExternalStorageState().equals(
				android.os.Environment.MEDIA_MOUNTED);
	}

	/**
	 * 递归创建文件目录
	 * 
	 * @param path
	 * */
	public static void CreateDir(String path) {
		if (!isSdCardMounted())
			return;
		File file = new File(path);
		if (!file.exists()) {
			try {
				file.mkdirs();
			} catch (Exception e) {
				Log.e("hulutan", "error on creat dirs:" + e.getStackTrace());
			}
		}
	}

	/**
	 * 读取文件
	 * 
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public static String readTextFile(File file) throws IOException {
		String text = null;
		InputStream is = null;
		try {
			is = new FileInputStream(file);
			text = readTextInputStream(is);;
		} finally {
			if (is != null) {
				is.close();
			}
		}
		return text;
	}

	/**
	 * 从流中读取文件
	 * 
	 * @param is
	 * @return
	 * @throws IOException
	 */
	public static String readTextInputStream(InputStream is) throws IOException {
		StringBuffer strbuffer = new StringBuffer();
		String line;
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(is));
			while ((line = reader.readLine()) != null) {
				strbuffer.append(line).append("\r\n");
			}
		} finally {
			if (reader != null) {
				reader.close();
			}
		}
		return strbuffer.toString();
	}

	/**
	 * 将文本内容写入文件
	 * 
	 * @param file
	 * @param str
	 * @throws IOException
	 */
	public static void writeTextFile(File file, String str) throws IOException {
		DataOutputStream out = null;
		try {
			out = new DataOutputStream(new FileOutputStream(file));
			out.write(str.getBytes());
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}

	/**
	 * 将Bitmap保存本地JPG图片
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static String saveBitmap2File(String url) throws IOException {

		BufferedInputStream inBuff = null;
		BufferedOutputStream outBuff = null;

		SimpleDateFormat sf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
		String timeStamp = sf.format(new Date());
		File targetFile = new File(Constants.ENVIROMENT_DIR_SAVE, timeStamp
				+ ".jpg");
		File oldfile = ImageLoader.getInstance().getDiscCache().get(url);
		try {

			inBuff = new BufferedInputStream(new FileInputStream(oldfile));
			outBuff = new BufferedOutputStream(new FileOutputStream(targetFile));
			byte[] buffer = new byte[BUFFER];
			int length;
			while ((length = inBuff.read(buffer)) != -1) {
				outBuff.write(buffer, 0, length);
			}
			outBuff.flush();
			return targetFile.getPath();
		} catch (Exception e) {

		} finally {
			if (inBuff != null) {
				inBuff.close();
			}
			if (outBuff != null) {
				outBuff.close();
			}
		}
		return targetFile.getPath();
	}

	/**
	 * 读取表情配置文件
	 * 
	 * @param context
	 * @return
	 */
	public static List
    
    
     
      getEmojiFile(Context context) {
		try {
			List
     
     
      
       list = new ArrayList
      
      
       
       ();
			InputStream in = context.getResources().getAssets().open("emoji");// 文件名字为rose.txt
			BufferedReader br = new BufferedReader(new InputStreamReader(in,
					"UTF-8"));
			String str = null;
			while ((str = br.readLine()) != null) {
				list.add(str);
			}

			return list;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 获取一个文件夹大小
	 * 
	 * @param f
	 * @return
	 * @throws Exception
	 */
	public static long getFileSize(File f) {
		long size = 0;
		File flist[] = f.listFiles();
		for (int i = 0; i < flist.length; i++) {
			if (flist[i].isDirectory()) {
				size = size + getFileSize(flist[i]);
			} else {
				size = size + flist[i].length();
			}
		}
		return size;
	}

	/**
	 * 删除文件
	 * 
	 * @param file
	 */
	public static void deleteFile(File file) {

		if (file.exists()) { // 判断文件是否存在
			if (file.isFile()) { // 判断是否是文件
				file.delete(); // delete()方法 你应该知道 是删除的意思;
			} else if (file.isDirectory()) { // 否则如果它是一个目录
				File files[] = file.listFiles(); // 声明目录下所有的文件 files[];
				for (int i = 0; i < files.length; i++) { // 遍历目录下所有的文件
					deleteFile(files[i]); // 把每个文件 用这个方法进行迭代
				}
			}
			file.delete();
		}
	}
}

      
      
     
     
    
    


 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LAXlerbo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值