图片缓存的原理
实现图片缓存也不难,需要有相应的cache策略。这里采用 内存-文件-网络
三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结
构中。

当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图
片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个
url被下载过,其图片就被缓存起来了。

关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空
间足够,垃
圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高
速缓存。使用软引用能防止内存泄露,增强程序的健壮性。

--以上内容选自网络--

1、内存缓存

/**
 * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
 * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
 */
private static LruCache<String, Bitmap> mLruCache; // 硬引用缓存

private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache; // 软引用缓存

private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量

主要的构造初始化方法

public ImageLoader(Context context) {
    int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
    int cacheSize = 1024 * 1024 * memClass / 4; // 硬引用缓存容量,为系统可用内存的1/4
    mLruCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        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算法把最近没有被使用的图片转入此软引用缓存
                mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
        }
    };
    mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f, true) {
        private static final long serialVersionUID = 6040103833179403725L;
        @Override
        protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
            if (size() > SOFT_CACHE_SIZE){
                return true;
            }
            return false;
        }
    };
}

 ***最主要的获取图片的逻辑方法***

/**
 * 加载获取图片的主要方法
 */
public Bitmap getBitmap(String url) {
    // 从内存缓存中获取图片
    Bitmap result = getBitmapFromCache(url);
    if (result == null) {
        // 文件缓存中获取
        result = getBitmapFromFile(url);
        if (result == null) {
            // 从网络获取
            result = getBitmapFromNet(url);
            if (result != null) {
                saveBitmap(result, url);
                addBitmapToCache(url, result);
            }
        } else {
            // 添加到内存缓存
            addBitmapToCache(url, result);
        }
    }
    return result;
}

从内存缓存中获取图片

/**
 * 从内存缓存中获取图片
 */
public Bitmap getBitmapFromCache(String url) {
    Bitmap bitmap;
    //先从硬引用缓存中获取
    synchronized (mLruCache) {
        bitmap = mLruCache.get(url);
        if (bitmap != null) {
            //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
            mLruCache.remove(url);
            mLruCache.put(url, bitmap);
            return bitmap;
        }
    }
    //如果硬引用缓存中找不到,到软引用缓存中找
    synchronized (mSoftCache) {
        SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
        if (bitmapReference != null) {
            bitmap = bitmapReference.get();
            if (bitmap != null) {
                //将图片移回硬缓存
                mLruCache.put(url, bitmap);
                mSoftCache.remove(url);
                return bitmap;
            } else {
                mSoftCache.remove(url);
            }
        }
    }
    return null;
} 

2、文件缓存

图片文件的保存

/** 将图片存入文件缓存 **/
public void saveBitmap(Bitmap bm, String url) {
    if (bm == null) {
        return;
    }
    // 判断sdcard上的空间
    if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
        // SD空间不足
        return;
    }
    String filename = convertUrlToFileName(url);
    String dir = getDirectory();
    File dirFile = new File(dir);
    if (!dirFile.exists())
        dirFile.mkdirs();
    File file = new File(dir + "/" + filename);
    try {
        file.createNewFile();
        OutputStream outStream = new FileOutputStream(file);
        bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
        outStream.flush();
        outStream.close();
    } catch (FileNotFoundException e) {
    } catch (IOException e) {
    }
}

/** 修改文件的最后修改时间 **/
public void updateFileTime(String path) {
    File file = new File(path);
    long newModifiedTime = System.currentTimeMillis();
    file.setLastModified(newModifiedTime);
}

/** 计算sdcard上的剩余空间 **/
private int freeSpaceOnSd() {
    StatFs stat = new StatFs(Environment.getExternalStorageDirectory()
            .getPath());
    @SuppressWarnings("deprecation")
    double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat
            .getBlockSize()) / MB;
    return (int) sdFreeMB;
}

/** 将url转成文件名 **/
private String convertUrlToFileName(String url) {
    String[] strs = url.split("/");
    return strs[strs.length - 1] + WHOLESALE_CONV;
}

/** 获得缓存目录 **/
private String getDirectory() {
    String dir = getSDPath() + "/" + CACHEDIR;
    return dir;
}

/** 取SD卡路径 **/
private String getSDPath() {
    File sdDir = null;
    boolean sdCardExist = Environment.getExternalStorageState().equals(
            android.os.Environment.MEDIA_MOUNTED); // 判断sd卡是否存在
    if (sdCardExist) {
        sdDir = Environment.getExternalStorageDirectory(); // 获取根目录
    }
    if (sdDir != null) {
        return sdDir.toString();
    } else {
        return "";
    }
}

图片文件的获取

/**
 * 从文件缓存中获取图片
 **/
public Bitmap getBitmapFromFile(final String url) {
    final String path = getDirectory() + "/" + convertUrlToFileName(url);
    File file = new File(path);
    if (file.exists()) {
        Bitmap bmp = BitmapFactory.decodeFile(path);
        if (bmp == null) {
            file.delete();
        } else {
            updateFileTime(path);
            return bmp;
        }
    }
    return null;
}

3、网络获取图片

/**
 * 从网络获取图片
 */
public Bitmap getBitmapFromNet(String url) {
    final HttpClient client = new DefaultHttpClient();
    final HttpGet getRequest = new HttpGet(url);

    try {
        HttpResponse response = client.execute(getRequest);
        final int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
            return null;
        }

        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                inputStream = entity.getContent();
                FilterInputStream fit = new FlushedInputStream(inputStream);
                return BitmapFactory.decodeStream(fit);
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                    inputStream = null;
                }
                entity.consumeContent();
            }
        }
    } catch (IOException e) {
        getRequest.abort();
    } catch (IllegalStateException e) {
        getRequest.abort();
    } catch (Exception e) {
        getRequest.abort();
    } finally {
        client.getConnectionManager().shutdown();
    }
    return null;
}

/*
 * An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
 */
static class FlushedInputStream extends FilterInputStream {
    public FlushedInputStream(InputStream inputStream) {
        super(inputStream);
    }

    @Override
    public long skip(long n) throws IOException {
        long totalBytesSkipped = 0L;
        while (totalBytesSkipped < n) {
            long bytesSkipped = in.skip(n - totalBytesSkipped);
            if (bytesSkipped == 0L) {
                int b = read();
                if (b < 0) {
                    break;  // we reached EOF
                } else {
                    bytesSkipped = 1; // we read one byte
                }
            }
            totalBytesSkipped += bytesSkipped;
        }
        return totalBytesSkipped;
    }
}

源码文件http://files.cnblogs.com/files/pear-lemon/ImageLoader.zip

Android 图片三级缓存的更多相关文章

  1. Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))

    因为之前项目同事使用了图片三级缓存,今天整理项目的时候发现同事还是使用了软引用(SoftRefrerence)和弱引用(WeakReference),来管理在内存中的缓存.看到这个我就感觉不对了.脑海 ...

  2. 【Android - 进阶】之图片三级缓存的原理及实现

    在Android开发中,如果图片过多,而我们又没有对图片进行有效的缓存,就很容易导致OOM(Out Of Memory)错误.因此,图片的缓存是非常重要的,尤其是对图片非常多的应用.现在很多框架都做了 ...

  3. picasso_强大的Android图片下载缓存库

    tag: android pic skill date: 2016/07/09 title: picasso-强大的Android图片下载缓存库 [本文转载自:泡在网上的日子 参考:http://bl ...

  4. 毕加索的艺术——Picasso,一个强大的Android图片下载缓存库,OkHttpUtils的使用,二次封装PicassoUtils实现微信精选

    毕加索的艺术--Picasso,一个强大的Android图片下载缓存库,OkHttpUtils的使用,二次封装PicassoUtils实现微信精选 官网: http://square.github.i ...

  5. Android图片二级缓存

    点击下载源代码 想起刚開始写代码的时候,领导叫我写一个头像下载的方法,当时屁颠屁颠就写了一个图片下载的,每次都要去网络上请求,最后直接被pass掉了 当时的思路是这种 后来渐渐地就知道了有二级缓存这东 ...

  6. picasso-强大的Android图片下载缓存库

    编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识.前端.后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过! pica ...

  7. android图片的缓存--节约内存提高程序效率

    如今android应用占内存一个比一个大,android程序的质量亟待提高. 这里简单说说网络图片的缓存,我这边就简单的说说思路 1:网络图片,无疑须要去下载图片,我们不须要每次都去下载. 维护一张表 ...

  8. 如何使用picasso 对Android图片下载缓存

    相比较其他,picasso的图片缓存更加简单一些,他只需要一行代码就可以表述:导入相关jar包 Picasso.with(context).load("图片路径").into(Im ...

  9. Android图片载入缓存框架Glide

    Glide开源框架是Google推荐的图片载入和缓框架,其在Github上的开源地址是:https://github.com/bumptech/glide 当然一个Google推荐的框架肯定就是Vol ...

随机推荐

  1. zoj 3888 线段树 ***

    卡n^2,用线段树降到nlogn 记录每个点上所覆盖线段的次小值,保证能有两条路径能走 #include<cstdio> #include<iostream> #include ...

  2. 物化视图刷新慢--有可能是mv log被多个mv使用造成的

    同事说物化视图刷新慢,经检生产环境,发现部分物化视图刷新慢的原因是:由于同一个物化视图日志(mv log)被多个物化视图(mv)使用,不同的物化视图(mv)使用不同的刷新间隔,导致物化视图日志(mv ...

  3. 第十篇:扩展SOUI的控件及绘图对象(ISkinObj)

    尽管SOUI已经内置了大部分常用的控件,很显然内置控件很难满足各种应用的形式各异的需求. 因此只有提供足够的扩展性才能满足真实应用场景. 除了将系统尽可能的组件化外,SOUI在控件自绘(SWindow ...

  4. Android开发的教程和资源

    Android 设计指南非官方简体中文版 http://www.apkbus.com/design/index.html NDK下载 http://developer.android.com/tool ...

  5. 在Asp.Net MVC中PartialView与EditorFor和DisplayFor的区别

    相同之处: PartialView, EditorFor 和 DisplayFor 都可以用作来实现页面的公共部分,其他页面可以根据需求来引用. 不同之处: PartialView 是从Page的角度 ...

  6. 通讯录(ios自带有界面)

    1.添加AddressBookUI.framework框架 2控制器中实现 #import "ViewController.h" #import <AddressBookUI ...

  7. Linux学习笔记(16)shell基础之Bash变量

    1. 用户自定义变量 (1)变量设置规则 ① 变量名称可由字母.数字和下划线组成,但不能以数字开头: ② 变量的默认类型为字符串类型,如果要对数值运算,则必须指定变量类型为数值型: ③ 变量用等号连接 ...

  8. 【MongoDB】2.可视化工具的安装和使用

    首先:关于  能支持MongoDB新版本的可视化工具,争议不断,各人都有各人的支持. 因此之前选择安装的时候就安装了MongoDB  3.0.14版本的. 最终,确定使用Robomongo作为我第一个 ...

  9. 用PHP链接mysql数据库

    PHP提供了两套数据库可用于访问mysql数据库 1)MySQL扩展函数数据库 2)MySQLI扩展数据库(improved) 使用MySQLI函数访问MySQL数据库步骤 1)链接数据库管理系统 m ...

  10. python 简单的txt文件读写

    1 读取txt文件.跟c相比,python的文件读写简直是方便的可怕 首先是读取文件 首先获得文件名称,然后通过 open函数打开文件,通过for循环逐行读出文件内容 #!python file by ...