做一个简单的Android图片缓存

对于图片资源,如果每次引用图片资源都从远程服务器去获取,这样会大大浪费资源,而且用户等待时间长,影响体验,甚至遇到体积较大的资源时发生OOM。图片缓存机制就则是为了解决这一问题而存在。


设计流程
Created with Raphaël 2.1.0 请求资源 内存缓存 资源命中 返回资源 本地缓存 资源命中 资源加入内存 返回资源 远程请求 加入本地缓存及内存 返回资源 yes no yes no
内存缓存

内存缓存使用Android提供的LruCache。

LruCache采用LRU(最近最少使用)算法,原理是把最近使用的对象以键值对的形式保存在内存,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除,而这些操作在android-support-v4包中给我提供的LruCache类已经实现,我们在使用的时候只需要指定缓存的大小,并重写一些相关方法。

比如建立一个大小为可用内存的1/8的LruCache

首先获取内存大小

int maxMemory = (int) Runtime.getRuntime().maxMemory();//单位是Byte

直接实例化LruCache对象

LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmep>(maxMemory / 8){
    /*
     * 重写sizeOf(String,BitMap)以计算LruCache的大小
     */
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount(); //返回图片大小(单位同样是Byte)
    }
};

注:
当LruCache中所有对象sizeOf方法中返回的int值的和达到LruCache的size(即上面指定的maxMemory / 8)时,对缓存进行清理,所以必须保证前后计算单位的一致,否则会导致LruCache失效或是OOM的发生。

关于LruCache的更多详细解析建议参考:
【Android 开发】深入理解内存缓存类LruCache:源码分析

之后就可以将图片的URL地址作为关键字在LruCache中存取。


本地缓存

本地缓存,在应用的私有目录下创建一个目录以供缓存使用即可,需要上下文context.getFilesDir()来获取应用的私有目录。

String cacheDir = context.getFilesDir() + "/ImgCache/";
File localCache = new File(cacheDir);
cacheFile.mkdir();

之后,从远程服务器请求来的图片可暂存到这里。

接着就是对本地缓存做管理,包括本地缓存的数量、大小以及清理等,同样可以采用LRU(最近最少使用)策略,不过在这里则要自己来实现。

先定义缓存数量和大小

int size;   //数量
int space;  //容量(以KB作为单位)

整理缓存

当缓存数量或大小达到一定量时,淘汰缓存空间中的一半最近最少使用的资源,将它们移除。

private void reorganize(){

    //获取缓存文件列表
    File[] fileList = localCache.listFiles();

    //将缓存文件列表按最后修改时间升序排序
    Arrays.sort(files, new Comparator<File>() {
        @Override
        public int compare(File file, File t1) {
            if (file.lastModified() > t1.lastModified()) return 1;
            else if (file.lastModified() == t1.lastModified()) return 0;
            else return -1;
        }
    });

    //删除一半缓存文件
    for (int i = 0; i < files.length * 0.5; i++) {
        files[i].delete();
    }
}

注:
这里排序的关键字是文件的最后修改时间,实际上在访问时并不会修改文件的最后修改时间,这就要求后面做文件访问时手动修改文件的最后修改时间了。

还有就是何时对缓存进行整理呢,我采用的策略是缓存文件数量达到上限或缓存文件占用了指定空间的90%时整理本地缓存。

private void reorganizeLocalCache(){
    File[] files = localCache.listFiles();
    if(files == null) 
        return;
    int count = files.length;
    if(count >= size) {
        reorganize();
        return;
    }
    int used = 0;
    for(File file : files){
        used += file.length();
    }
    if(used /1024 >= 0.9 * space)
        organize();
}

远程请求

提供一个默认的请求图片的方法,声明为protected方便重写以供适应不同应用环境的不同请求方式。

protected InputStream requestImage(String url) throws IOException {
    HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
    connection.setRequestMethod("GET");
    connection.setConnectTimeout(5000);
    connection.setDoInput(true);
    return connection.getInputStream();
}

当请求图片的方式发生变化,可以重写此方法即可。


获取图片

提供一个图片访问方法原型:

public Bitmap getImage(String url){}

在这里组织整个访问图片的过程。

第一部分:访问内存缓存

LruCache提供给我们访问其中内容的方法是get(String key),由其返回值可以直接判断是内存缓存否命中。

//将图片的URL作为关键字在LruCache中存取
Bitmap bitmap = memoryCache.get(url);
if(bitmap == null){
    /*
     * 内存缓存未命中,查询本地缓存
     */
}
//内存缓存命中,直接返回图片对象
return bitmap;
第二部分:本地缓存

以图片的URL为关键字查找,直接存取是有问题的,因为URL里面包含了协议名称和资源定位存在一些非法字符,所以要先处理掉。

String fileName = url.replace("/", "_");
fileName = url.replace("http:", "");

接着就可以根据文件是否存在来判断是否命中

若本地缓存命中,先将图片加入内存,再从内存取出返回,并修改本地文件的最后访问时间;否则向远程服务器请求图片。

if(bitmap == null){
    File file = new File(cacheDir + fileName);
    if(!file.exist()){
        /*
         * 本地缓存未命中,向远程服务器请求图片
         */
    }
    //本地缓存命中
    InputStream inputStream = new FileInputStream(file);
    bitmap = BitmapFactory.decodeStream(inputStream);
    //加入内存
    memoryCache.put(uri, bitmap);
    //修改文件最后访问时间为当前系统时间
    file.setLastModified(System.currentTimeMillis());
}
第三部分:远程请求

调用上面写好的请求方法获取远程数据的输入流,保存到本地,还有对本地缓存进行整理

if(!file.exist()){
    InputStream inputStream = requestImage(uri);
    bitmap = BitmapFactory.decodeStream(inputStream);
    OutputStream outputStream = new FileOutputStream(cacheDir + localUrl);
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
    outputStream.flush();
    outputStream.close();
}

最后把上述各部分连接起来就可以封装成一个类ImageCache来使用了,一个简单的图片缓存就做好了。

在应用中,配合一个异步线程,就可以实现图片的缓存加载了。

Example:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final ImageView imageView = (ImageView) findViewById(R.id.image);

    /*
     * 实例化缓存
     */
    ImageCache imageCache = new ImageCache(this);

    /*
     * 提供请求远程图片的URL
     */
    String url = /* 远程URL */

    /*
     * 获取图片
     */
    new AsyncTask<String, String, Bitmap>() {

        @Override
        protected Bitmap doInBackground(String... params) {
            return imageCache.getImage(params[0]);
        }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                imageView.setImageBitmap(bitmap);
            }

        }.execute(url);

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值