Android缓存学习入门(二)
本文主要包括以下内容
- 内存缓存策略
- 文件缓存策略
内存缓存策略
当有一个图片要去从网络下载的时候,我们并不会直接去从网络下载,因为在这个时代,用户的流量是宝贵的,耗流量的应用是不会得到用户的青睐的。那我们该怎么办呢?这样,我们会先从内存缓存中去查找是否有该图片,如果没有就去文件缓存中查找是否有该图片,如果还没有,我们就从网络下载图片。本博文的侧重点是如何做内存缓存,内存缓存的查找策略是:先从强引用缓存中查找,如果没有再从软引用缓存中查找,如果在软引用缓存中找到了,就把它移入强引用缓存;如果强引用缓存满了,就会根据Lru算法把某些图片移入软引用缓存,如果软引用缓存也满了,最早的软引用就会被删除。这里,我有必要说明下几个概念:强引用、软引用、弱引用、Lru。
- 强引用:就是直接引用一个对象,一般的对象引用均是强引用
- 软引用:引用一个对象,当内存不足并且除了我们的引用之外没有其他地方引用此对象的情况 下,该对象会被gc回收
- 弱引用:引用一个对象,当除了我们的引用之外没有其他地方引用此对象的情况下,只要gc被调用,它就会被回收(请注意它和软引用的区别)
- Lru:Least Recently Used 近期最少使用算法,是一种页面置换算法,其思想是在缓存的页面数目固定的情况下,那些最近使用次数最少的页面将被移出,对于我们的内存缓存来说,强引用缓存大小固定为4M,如果当缓存的图片大于4M的时候,有些图片就会被从强引用缓存中删除,哪些图片会被删除呢,就是那些近期使用次数最少的图片。
注意
In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
在android2.3之后,软引用与弱引用已经不可靠了,所以慎用。
public class ImageMemoryCache {
/**
* 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
* 强引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
*/
private static final String TAG = "ImageMemoryCache";
private static LruCache<String, Bitmap> mLruCache; // 强引用缓存
private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存
private static final int LRU_CACHE_SIZE = 4 * 1024 * 1024; // 强引用缓存容量:4MB
private static final int SOFT_CACHE_NUM = 20; // 软引用缓存个数
// 在这里分别初始化强引用缓存和弱引用缓存
public ImageMemoryCache() {
mLruCache = new LruCache<String, Bitmap>(LRU_CACHE_SIZE) {
@Override
// sizeOf返回为单个hashmap value的大小
protected int sizeOf(String key, Bitmap value) {
if (value != null)
return value.getRowBytes() * value.getHeight();
else
return 0;
}
@Override
protected void entryRemoved(boolean evicted, String key,
Bitmap oldValue, Bitmap newValue) {
if (oldValue != null) {
// 强引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
Logger.d(TAG, "LruCache is full,move to SoftRefernceCache");
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
}
};
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(
SOFT_CACHE_NUM, 0.75f, true) {
private static final long serialVersionUID = 1L;
/**
* 当软引用数量大于20的时候,最旧的软引用将会被从链式哈希表中移出
*/
@Override
protected boolean removeEldestEntry(
Entry<String, SoftReference<Bitmap>> eldest) {
if (size() > SOFT_CACHE_NUM) {
Logger.d(TAG, "should remove the eldest from SoftReference");
return true;
}
return false;
}
};
}
/**
* 从缓存中获取图片
*/
public Bitmap getBitmapFromMemory(String url) {
Bitmap bitmap;
// 先从强引用缓存中获取
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if (bitmap != null) {
// 如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
mLruCache.remove(url);
mLruCache.put(url, bitmap);
Logger.d(TAG, "get bmp from LruCache,url=" + url);
return bitmap;
}
}
// 如果强引用缓存中找不到,到软引用缓存中找,找到后就把它从软引用中移到强引用缓存中
synchronized (mSoftCache) {
SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
if (bitmapReference != null) {
bitmap = bitmapReference.get();
if (bitmap != null) {
// 将图片移回LruCache
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
Logger.d(TAG, "get bmp from SoftReferenceCache, url=" + url);
return bitmap;
} else {
mSoftCache.remove(url);
}
}
}
return null;
}
/**
* 添加图片到缓存
*/
public void addBitmapToMemory(String url, Bitmap bitmap) {
if (bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
public void clearCache() {
mSoftCache.clear();
}
}
文件缓存策略
当一张图片从网络下载成功以后,这个图片会被加入内存缓存和文件缓存,对于文件缓存来说,这张图片将被以url的哈希值加cach后缀名的形式存储在SD卡上,这样,当下一次再需要同一个url的图片的时候,就不需要从网络下载了,而是直接通过url来进行查找。同时一张图片被访问时,它的最后修改时间将被更新,这样的意义在于:当SD卡空间不足的时候,将会按照最后修改时间来删除40%缓存的图片,确切来说,那些修改时间比较早的图片将会被删除。
public class ImageFileCache
{
private static final String TAG = "ImageFileCache";
//图片缓存目录
private static final String IMGCACHDIR = "/sdcard/ImgCach";
//保存的cache文件宽展名
private static final String CACHETAIL = ".cach";
private static final int MB = 1024*1024;
private static final int CACHE_SIZE = 1;
//当SD卡剩余空间小于10M的时候会清理缓存
private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
public ImageFileCache()
{
//清理部分文件缓存
removeCache(IMGCACHDIR);
}
/**
* 从缓存中获取图片
*/
public Bitmap getImageFromFile(final String url)
{
final String path = IMGCACHDIR + "/" + convertUrlToFileName(url);
File file = new File(path);
if (file != null && file.exists())
{
Bitmap bmp = BitmapFactory.decodeFile(path);
if (bmp == null)
{
file.delete();
}
else
{
updateFileTime(path);
Logger.d(TAG, "get bmp from FileCache,url=" + url);
return bmp;
}
}
return null;
}
/**
* 将图片存入文件缓存
*/
public void saveBitmapToFile(Bitmap bm, String url)
{
if (bm == null) {
return;
}
//判断sdcard上的空间
if (FREE_SD_SPACE_NEEDED_TO_CACHE > SdCardFreeSpace())
{
//SD空间不足
return;
}
String filename = convertUrlToFileName(url);
File dirFile = new File(IMGCACHDIR);
if (!dirFile.exists())
dirFile.mkdirs();
File file = new File(IMGCACHDIR +"/" + filename);
try
{
file.createNewFile();
OutputStream outStream = new FileOutputStream(file);
bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
outStream.flush();
outStream.close();
}
catch (FileNotFoundException e)
{
Logger.d(TAG, "FileNotFoundException");
}
catch (IOException e)
{
Logger.d(TAG, "IOException");
}
}
/**
* 计算存储目录下的文件大小,
* 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
* 那么删除40%最近没有被使用的文件
*/
private boolean removeCache(String dirPath)
{
File dir = new File(dirPath);
File[] files = dir.listFiles();
if (files == null)
{
return true;
}
if (!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
{
return false;
}
int dirSize = 0;
for (int i = 0; i < files.length; i++)
{
if (files[i].getName().contains(CACHETAIL))
{
dirSize += files[i].length();
}
}
if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > SdCardFreeSpace())
{
int removeFactor = (int) (0.4 * files.length);
Arrays.sort(files, new FileLastModifSort());
for (int i = 0; i < removeFactor; i++)
{
if (files[i].getName().contains(CACHETAIL))
{
files[i].delete();
}
}
}
if (SdCardFreeSpace() <= CACHE_SIZE)
{
return false;
}
return true;
}
/**
* 修改文件的最后修改时间
*/
public void updateFileTime(String path)
{
File file = new File(path);
long newModifiedTime = System.currentTimeMillis();
file.setLastModified(newModifiedTime);
}
/**
* 计算SD卡上的剩余空间
*/
private int SdCardFreeSpace()
{
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
return (int) sdFreeMB;
}
/**
* 将url转成文件名
*/
private String convertUrlToFileName(String url)
{
return url.hashCode() + CACHETAIL;
}
/**
* 根据文件的最后修改时间进行排序
*/
private class FileLastModifSort implements Comparator<File>
{
public int compare(File file0, File file1)
{
if (file0.lastModified() > file1.lastModified())
{
return 1;
}
else if (file0.lastModified() == file1.lastModified())
{
return 0;
}
else
{
return -1;
}
}
}
}
References
Android缓存学习入门(二)的更多相关文章
- Android缓存学习入门
本文主要包括以下内容 利用LruCache实现内存缓存 利用DiskLruCache实现磁盘缓存 LruCache与DiskLruCache结合实例 利用了缓存机制的瀑布流实例 内存缓存的实现 pub ...
- Android Animation学习(二) ApiDemos解析:基本Animators使用
Android Animation学习(二) ApiDemos解析:基本Animatiors使用 Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.O ...
- Android FrameWork学习(二)Android系统源码调试
通过上一篇 Android FrameWork学习(一)Android 7.0系统源码下载\编译 我们了解了如何进行系统源码的下载和编译工作. 为了更进一步地学习跟研究 Android 系统源码,今天 ...
- Android Animation学习(二) ApiDemos解析:基本Animatiors使用
Animator类提供了创建动画的基本结构,但是一般使用的是它的子类: ValueAnimator.ObjectAnimator.AnimatorSet ApiDemos中Animation部分是单独 ...
- Android WiFiDirect 学习(二)——Service Discovery
Service Discovery 简介 在Android WifiDirect学习(一 )中,简单介绍了如何使用WifiDirect进行搜索——连接——传输. 这样会有一个问题,那就是你会搜索到到附 ...
- Android开发学习总结(二)——使用Android Studio搭建Android集成开发环境
有很长一段时间没有更新博客了,最近实在是太忙了,没有时间去总结,现在终于可以有时间去总结一些Android上面的东西了,很久以前写过这篇关于使用Android Studio搭建Android集成开发环 ...
- android 项目学习随笔二十一(IM、语音识别、机器人、统计、扫描二维码、条形码)
语音识别:科大讯飞语音云 http://www.xfyun.cn/ 语音机器人模拟 public class TalkBean { public String text; public boolean ...
- Java缓存学习之二:浏览器缓存机制
浏览器端的九种缓存机制介绍 浏览器缓存是浏览器端保存数据用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和浏览器快速地读取本地数据,整体上加速网页展示给用户.浏览器端缓存 ...
- Android NDK学习(二):编译脚本语法Android.mk和Application.mk
一.Android.mk Android.mk分为一下几部分: LOCAL_PATH:= $(call my-dir), 返回当前文件在系统中的路径,Android.mk文件开始时必须定义该变量. i ...
随机推荐
- 5分钟教你Windows 10中将“运行”固定到开始菜单
导读 “运行”功能深受很多资深IT之家用户喜爱,因为它简约.方便.实用.在Win7等旧版系统中,用户可以让该功能直接在开始菜单显示,方便操作.但在Win10中,由于开始菜单已经重新编写,原有的设定已经 ...
- Android自定义标题栏
预览一下效果: 素材: 新建一个布局title_bar.xml,代码如下: <?xml version="1.0" encoding="utf-8"?&g ...
- HDU 3999 二叉排序树
The order of a Tree Problem Description The shape of a binary search tree is greatly related to the ...
- CodeVS 2845 排序的代价
Description 给你一个数列使他递增,交换两个元素的代价为两个数的和,最小化代价. Sol 置换群+离散化. 使一个数列恢复递增顺序,那么,他和他要到达的位置的数需要交换,这样就形成了一个置换 ...
- 36 网络相关函数(四)——live555源码阅读(四)网络
36 网络相关函数(四)——live555源码阅读(四)网络 36 网络相关函数(四)——live555源码阅读(四)网络 简介 7)createSocket创建socket方法 8)closeSoc ...
- linux下合并两个文件夹
一.我想把自己自定义的软件统一放到man手册路径里.如何和现有的/usr/local/share文件夹合并起来,原来的文件还在? (1)下面是解压出的自定义的bashdb调试软件==>
- java servlet的工作原理
servlet本质上就是java类嘛.不过是有特殊规范的java类而已.下面就说一说为什么servlet要有特殊规范. 首先,考虑一下什么地方用servlet,WEB应用,而且是需要servlet容器 ...
- js中val()和value的区别
val()是在有jQuery插件的时候才能用,value是在没有jQuery插件的情况下也能用.val()是jQuery根据原生JS里面的value写出来的函数 $(this).val(); 有四个重 ...
- hdu 1195
题意:就是给你n组的四位数,在一次变化中又一位数字可以变化,而变化的方式为加一减一或者是与隔壁的互换,注意,是每一个数字都可以, 求最少的变化次数到达目标的数字 一看这个就应该知道这是一个bfs的题目 ...
- hdu2196
基本的树形dp,需要dfs三次,第一次求每个点最远的后代,第二次和第三次每个点的孩子分别从左到右和从右到左遍历. #include <cstdio> #include <vector ...