采用缓存,可以进一步大大缓解数据交互的压力,又能提供一定的离线浏览。下边我简略列举一下缓存管理的适用环境:
1. 提供网络服务的应用
2. 数据更新不需要实时更新,哪怕是3-5分钟的延迟也是可以采用缓存机制。
3. 缓存的过期时间是可以接受的(类似网易的新闻阅读,支持离线离线阅读)
这样所带来的好处:
1. 减小服务器的压力
2. 提高客户端的响应速度(本地数据提取嘛)
3. 一定程度上支持离线浏览(可以参考网易的那个新闻应用,个人感觉离线阅读做得非常棒。)
一、缓存管理的方法
缓存管理的原理很简:通过时间的设置来判断是否读取缓存还是重新下载;断网下就没什么好说的,直接去缓存即可。如图:
里面会有一些细节的处理,后面会详细阐述。基于这个原理,目前个人用过的两种比较常见的缓存管理方法是:数据库和文件(txt)。
这种方法是在下载完数据文件后,把文件的相关信息如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小时,依次类推。
当然类似这个界面的数据我认为更新时间能多长就多长了,尽可能长。如果你拿后边那个有多少数据会变动来搪塞。我会告诉你:这个只是一个引导性的界面,你有多少款游戏跟用户半毛钱关系都没有,10亿也跟他没关,他只要确定这里能找到他要找的 汤姆猫 就行。否则你又失去了一个用户。
2. 提供刷新按钮。
必要时候或最保险的方法使在相关界面提供一个刷新按钮,或者当下流行的下拉列表刷新方式。为缓存,为加载失败提供一次重新来过的机会。毕竟喝骨头汤的时候,我也不介意碗旁多双筷子。
总而言之,一切用户至上,为了更好的用户体验,方法也会层出不穷。期待更好的办法
(参考代码:http://blog.csdn.net/lnb333666/article/details/8460159)
图片缓存:
译文:
加载一个Bitmap(位图)到你的UI界面是非常简单的,但是如果你要一次加载一大批,事情就变得复杂多了。在大多数的情况下(如ListView、GridView或者ViewPager这样的组件),屏幕上的图片以及马上要在滚动到屏幕上显示的图片的总量,在本质上是不受限制的。
像这样的组件在子视图移出屏幕后会进行视图回收,内存使用仍被保留。但假设你不保留任何长期存活的引用,垃圾回收器也会释放你所加载的Bitmap。这自然再好不过了,但是为了保持流畅且快速加载的UI,你要避免继续在图片回到屏幕上的时候重新处理。使用内存和硬盘缓存通常能解决这个问题,使用缓存允许组件快速加载并处理图片。
这节课将带你使用内存和硬盘缓存Bitmap,以在加载多个Bitmap的时候提升UI的响应性和流畅性。
使用内存缓存
以牺牲宝贵的应用内存为代价,内存缓存提供了快速的Bitmap访问方式。LruCache类(可以在Support Library中获取并支持到API Level 4以上,即1.6版本以上)是非常适合用作缓存Bitmap任务的,它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。
注意:以前有一个非常流行的内存缓存实现是SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,然而现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,这使得上述的方案相当无效。此外,Android 3.0(API Level 11)之前的版本中,Bitmap的备份数据直接存储在本地内存中并以一种不可预测的方式从内存中释放,很可能短暂性的引起程序超出内存限制而崩溃。
为了给LruCache选择一个合适的大小,要考虑到很多原因,例如:
其他的Activity(活动)和(或)程序都是很耗费内存的吗?
屏幕上一次会显示多少图片?有多少图片将在屏幕上显示?
设备的屏幕大小和密度是多少?一个超高清屏幕(xhdpi)的设备如Galaxy Nexus,相比Nexus S(hdpi)来说,缓存同样数量的图片需要更大的缓存空间。
Bitmap的尺寸、配置以及每张图片需要占用多少内存?
图片的访问是否频繁?有些会比其他的更加被频繁的访问到吗?如果是这样,也许你需要将某些图片一直保留在内存中,甚至需要多个LruCache对象分配给不同组的Bitmap。
你能平衡图片的质量和数量么?有的时候存储大量低质量的图片更加有用,然后可以在后台任务中加载另一个高质量版本的图片。
对于设置缓存大小,并没有适用于所有应用的规范,它取决于你在内存使用分析后给出的合适的解决方案。缓存空间太小并无益处,反而会引起额外的开销,而太大了又可能再次引起java.lang.OutOfMemory异常或只留下很小的空间给应用的其他程序运行。
(参考网址:http://my.oschina.net/ryanhoo/blog/88443)
AsimpleCache框架
框架地址
https://github.com/yangfuhai/ASimpleCache 此框架作者为大名鼎鼎的afinal作者
官方简介:
ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。
(有个问题是作者所说的自动失效,其实是在获取数据时判断存入缓存的数据是否过期,如果过期,则删除数据缓存,返回null。当然,如果真正的自动删除,应该只能开启服务,不断判断是否过期来删除吧,也没有必要)
--------------------------------------------------------------------------------
1、它可以缓存什么东西?
普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。
2、它有什么特色?
特色主要是:
1:轻,轻到只有一个JAVA文件。
2:可配置,可以配置缓存路径,缓存大小,缓存数量等。
3:可以设置缓存超时时间,缓存超时自动失效,并被删除。
4:支持多进程。
3、它在android中可以用在哪些场景?
1、替换SharePreference当做配置文件
2、可以缓存网络请求数据,比如oschina的android客户端可以缓存http请求的新闻内容,缓存时间假设为1个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。
3、您来说...
4、如何使用 ASimpleCache?
以下有个小的demo,希望您能喜欢:
ACache mCache = ACache.get(this);
mCache.put("test_key1", "test value");
mCache.put("test_key2", "test value", 10);//保存10秒,如果超过10秒去获取这个key,将为null
mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);//保存两天,如果超过两天去获取这个key,将为null
获取数据
ACache mCache = ACache.get(this);
String value = mCache.getAsString("test_key1");
更多示例请见Demo
关于作者michael
屌丝程序员一枚,喜欢开源。
个人博客:http://www.yangfuhai.com
交流QQ群 : 192341294(已满) 246710918(未满)
主要分析下集万千宠爱于一身的ACache类吧(以字符串存储为例)
一,首先先要创建缓存
get(Context ctx, String cacheName)方法新建缓存目录
get(File cacheDir, long max_zise, int max_count)方法新建缓存实例,存入实例map,key为缓存目录加上每次应用开启的进程id
- public static ACache get(Context ctx) {
- return get(ctx, "ACache");
- }
- public static ACache get(Context ctx, String cacheName) {
- //新建缓存目录
- ///data/data/com.yangfuhai.asimplecachedemo/cache/ACache
- File f = new File(ctx.getCacheDir(), cacheName);
- return get(f, MAX_SIZE, MAX_COUNT);
- }
- public static ACache get(File cacheDir) {
- return get(cacheDir, MAX_SIZE, MAX_COUNT);
- }
- public static ACache get(Context ctx, long max_zise, int max_count) {
- File f = new File(ctx.getCacheDir(), "ACache");
- return get(f, max_zise, max_count);
- }
- public static ACache get(File cacheDir, long max_zise, int max_count) {
- ///data/data/com.yangfuhai.asimplecachedemo/cache/ACache
- ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid());
- if (manager == null) {
- manager = new ACache(cacheDir, max_zise, max_count);
- //{/data/data/com.yangfuhai.asimplecachedemo/cache/ACache_4137=org.afinal.simplecache.ACache@2bc38270}
- //{/data/data/com.yangfuhai.asimplecachedemo/cache/ACache_12189=org.afinal.simplecache.ACache@2bc3d890}
- mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager);
- }
- return manager;
- }
- private static String myPid() {
- return "_" + android.os.Process.myPid();
- }
- private ACache(File cacheDir, long max_size, int max_count) {
- if (!cacheDir.exists() && !cacheDir.mkdirs()) {
- throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath());
- }
- mCache = new ACacheManager(cacheDir, max_size, max_count);
- }
二,存入数据
put(String key, String value)方法写数据到文件
put(String key, String value)方法中的mCache.put(file)方法做了如下设置
文件放入程序缓存后,统计缓存总量,总数,文件存放到文件map中(value值为文件最后修改时间,便于根据设置的销毁时间进行销毁)
缓存没有超过限制,则增加缓存总量,总数的数值
缓存超过限制,则减少缓存总量,总数的数值
通过removeNext方法找到最老文件的大小
- public void put(String key, String value) {
- File file = mCache.newFile(key);
- BufferedWriter out = null;
- try {
- out = new BufferedWriter(new FileWriter(file), 1024);
- out.write(value);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (out != null) {
- try {
- out.flush();
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- mCache.put(file);
- }
- }
- //文件放入程序缓存后,统计缓存总量,总数,文件存放到文件map中(value值为文件最后修改时间,便于根据设置的销毁时间进行销毁)
- //缓存没有超过限制,则增加缓存总量,总数的数值
- //缓存超过限制,则减少缓存总量,总数的数值
- //通过removeNext方法找到最老文件的大小
- private void put(File file) {
- int curCacheCount = cacheCount.get();
- while (curCacheCount + 1 > countLimit) {
- long freedSize = removeNext();
- cacheSize.addAndGet(-freedSize);
- curCacheCount = cacheCount.addAndGet(-1);
- }
- cacheCount.addAndGet(1);
- long valueSize = calculateSize(file);
- long curCacheSize = cacheSize.get();
- while (curCacheSize + valueSize > sizeLimit) {
- long freedSize = removeNext();
- curCacheSize = cacheSize.addAndGet(-freedSize);
- }
- cacheSize.addAndGet(valueSize);
- Long currentTime = System.currentTimeMillis();
- file.setLastModified(currentTime);
- lastUsageDates.put(file, currentTime);
- }
- /**
- * 移除旧的文件(冒泡,找到最后修改时间最小的文件)
- *
- * @return
- */
- private long removeNext() {
- if (lastUsageDates.isEmpty()) {
- return 0;
- }
- Long oldestUsage = null;
- File mostLongUsedFile = null;
- Set<Entry<File, Long>> entries = lastUsageDates.entrySet();
- synchronized (lastUsageDates) {
- for (Entry<File, Long> entry : entries) {
- if (mostLongUsedFile == null) {
- mostLongUsedFile = entry.getKey();
- oldestUsage = entry.getValue();
- } else {
- Long lastValueUsage = entry.getValue();
- if (lastValueUsage < oldestUsage) {
- oldestUsage = lastValueUsage;
- mostLongUsedFile = entry.getKey();
- }
- }
- }
- }
- long fileSize = calculateSize(mostLongUsedFile);
- if (mostLongUsedFile.delete()) {
- lastUsageDates.remove(mostLongUsedFile);
- }
- return fileSize;
- }
三,获取数据
getAsString(String key)方法从缓存文件中读取数据,其中通过Utils.isDue(readString)方法判断数据是否过期,是否要删除
- public String getAsString(String key) {
- ///data/data/com.yangfuhai.asimplecachedemo/cache/ACache/1727748931
- File file = mCache.get(key);
- if (!file.exists())
- return null;
- boolean removeFile = false;
- BufferedReader in = null;
- try {
- in = new BufferedReader(new FileReader(file));
- String readString = "";
- String currentLine;
- while ((currentLine = in.readLine()) != null) {
- readString += currentLine;
- }
- if (!Utils.isDue(readString)) {
- return Utils.clearDateInfo(readString);
- } else {
- removeFile = true;
- return null;
- }
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (removeFile)
- remove(key);
- }
- }
- /**
- * 判断缓存的String数据是否到期
- *
- * @param str
- * @return true:到期了 false:还没有到期
- */
- private static boolean isDue(String str) {
- return isDue(str.getBytes());
- }
- /**
- * 判断缓存的byte数据是否到期(到期:当前时间大于保存时间加上保存后的存留时间)
- *
- * @param data
- * @return true:到期了 false:还没有到期
- */
- private static boolean isDue(byte[] data) {
- String[] strs = getDateInfoFromDate(data);
- if (strs != null && strs.length == 2) {
- String saveTimeStr = strs[0];
- while (saveTimeStr.startsWith("0")) {
- saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length());
- }
- long saveTime = Long.valueOf(saveTimeStr);
- long deleteAfter = Long.valueOf(strs[1]);
- if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) {
- return true;
- }
- }
- return false;
- }
- //数据有无存留时间设置
- private static boolean hasDateInfo(byte[] data) {
- return data != null && data.length > 15 && data[13] == '-' && indexOf(data, mSeparator) > 14;
- }
- //saveDate文件保存时间毫秒数,deleteAfter文件保存后的保留时间毫秒数
- private static String[] getDateInfoFromDate(byte[] data) {
- if (hasDateInfo(data)) {
- String saveDate = new String(copyOfRange(data, 0, 13));
- String deleteAfter = new String(copyOfRange(data, 14, indexOf(data, mSeparator)));
- return new String[] { saveDate, deleteAfter };
- }
- return null;
- }