android:图片三级缓存小结

前言:在Android里面,图片的使用可以说是非常重要的,根据自己平时项目中接触到的和空余时间,对图片的三级缓存做了个小结:

一、三级缓存原理:
1.图片的缓存的三级指的是:内存缓存、本地缓存及网络直接获取。针对图片缓存的位置的不同,其呈现加载的效率也不一样,由高到低分别为:
内存–>本地–>网络;内存、本地、网络就构成了缓存的三个级别,大概“三级缓存”就是这么来的吧;
2.针对缓存的位置 内存、本地、网络获取,我们也很容易理解:首先,内存中的图片以Bitmap形式存储,加载呈现的时候自然也是最快、效率更高的;其次,是本地缓存,
图片以File形式存放在本地,呈现的时候,先需要将File转换成Stream流形式读取到内存,再进行呈现;最后,是网络获取,当图片在网络(也是是服务器后台)中存放,需要呈现时,
只能通过联网获取,同样是以ByteArray或者Stream形式获取,再进行呈现,相对于从本地获取,联网获取是比本地更慢。
3.三级缓存的目的:主要是为了提高图片的加载效率,争取对于同一张图片只要加载过一次,之后就不再从网上获取,而是从本地或者内存中加载;
4.内存缓存的分类:
在三级缓存的内存缓存中,主要使用的是java引用级别里面的强引用与软引用(关于java引用级别及强引用、软引用的说明,下面会讲到)
1)强引用、软引用:强引用、软引用缓存其实就是一个Map集合,该集合是属于v4包下的集合,其内部内置了Lru算法(Lru算法:在组织三级缓存的时候,决定移除哪一部分数据、保留哪一部分数据的核心依据)
2)在三级缓存中,强引用 加载速度较快,加载优先级较高,而软引用加载优先级、效率更低,更容易被gc回收。
二、Java引用级别
这里写图片描述
Java引用级别可以分为强、软、弱、虚四级,其划分的依据是Java虚拟机对于对象回收的难易程度。强引用是我们使用得最普遍的,持有强应用的对象,其与生命周期和gc无关,就算是抛出了OOM异常,也不会被gc回收;而软应用、弱引用、虚引用都与内存有关,当内存不足时,持有月软引用的对象会被回收,而当gc扫描到持有弱引用的对象所在内存位置时,即被回收;虚引用则是无论什么情况下都有可能被回收,持有虚引用和不持有是一样的。
三、图片压缩原理分析

内存缓存

这里写图片描述
1)强引用缓存初始化:首先获取当前应用的最大可用内存,再设置强引用缓存的大小,然后在实例化LruCache集合的时候将强引用缓存的大小作为参数传入,这样就完成了强引用缓存大小的设置;
2)在初始化强引用缓存时,需要覆写两个方法:
sizeOf()————>计算BitMap的大小,获取新增图片的内存占有量
entryRemoved()—————>若强引用缓存满的时候,根据Lru算法将最近没有使用的图片放到软引用
3)软引用缓存的初始化:在初始化软引用缓存时,有一个移除最早使用的节点的方法,即把最不常使用的图片是移除。
2.从内存中获取图片:首先会根据ur从强引用中获取,如果不存在再从软引用中获取而在软引用中获取的时候,是将软引用的图片放到强引用中,这样就能从软引用中获取到图片:
这里写图片描述
3.向内存中添加图片:根据键值的形式向集合中添加图片(优先向强引用缓存中添加)

4.移除缓存:
在BitMap不再使用的时候,要调用recycle()方法将BitMap对象回收,在移除缓存中,需要调用BitMap的recycle()方法将BitMap对象回收,同时,调用集合的clear()方法,还需要调用System.gc()方法,申请垃圾回收。
这里写图片描述

package com.example.sj.imageloderdemo;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

import java.lang.ref.SoftReference;
import java.util.LinkedHashMap;
import java.util.Set;

/*
    第一级 内存
 */
public class ImageMemoryCache {
    /**
     * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
     * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
     */
    private static final int SOFT_CACHE_SIZE = 15;  //软引用缓存容量

    private static LruCache<String, Bitmap> mLruCache;  //强引用缓存

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

    public ImageMemoryCache(Context context) {
        //获取当前应用最大可用内存
        int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        //硬引用缓存容量,为系统可用内存的1/4
        int cacheSize = 1024 * 1024 * memClass / 4; 
        //实例化硬应用内存LruCache对象,构造器中传入该对象可以占用的最大内存数
        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            //计算Bitmap的大小,获取新增图片的内存占有量
            @Override
            protected int sizeOf(String key, Bitmap value) {
                if (value != null)
                    return value.getRowBytes() * value.getHeight();
                else
                    return 0;
            } 
            // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                if (oldValue != null)
                    mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
            }
        };      
        //实例化软引用LinkedHashMap对象
        mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>> (SOFT_CACHE_SIZE, 0.75f, true) {
            private static final long serialVersionUID = 6040103833179403725L;    
            //移除最老使用的节点---LRU
            @Override
            protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
                if (size() > SOFT_CACHE_SIZE) {//如果软引用缓存的内存大于设置的内存大小,则把数据也从软引用缓存中删除
                    eldest.getValue().get().recycle();
                    return true;
                }
                return false;
            }
        };
    }

    /**
     * 从内存中获取图片
     */
    public Bitmap getBitmapFromCache(String url) {
        Bitmap bitmap;
        //先从硬引用缓存中获取
        bitmap = mLruCache.get(url);
        if (bitmap != null) {
            //如果找到的话,把元素移到LruCache的最前面,从而保证在LRU算法中是最后被删除
            mLruCache.remove(url);
            mLruCache.put(url, bitmap);
            return bitmap;
        }
        //如果硬引用缓存中找不到,到软引用缓存中找
        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);
            }
            System.gc();
        }
        return null;
    }

    /**
     * 添加图片到缓存
     */
    public void addBitmapToCache(String url, Bitmap bitmap) {
        if (bitmap != null) {
            synchronized (mLruCache) {
                mLruCache.put(url, bitmap);
            }
        }
    }
    /*
        清除全部内存缓存
     */
    public void clearCache() {
        //移除强引用缓存中的数据
        mLruCache.evictAll();
        Set<String> keys = mSoftCache.keySet();
        //移除软引用缓存中的数据
        for (String key:keys){
            SoftReference<Bitmap> bitmapSoftReference = mSoftCache.get(key);
            bitmapSoftReference.get().recycle();
        }

        mSoftCache.clear();
        System.gc();
    }
}

本地缓存

1.从本地缓存中获取图片:根据路径,定位到图片的url,然后就能获取到图片:

/** 从缓存中获取图片 **/
    public Bitmap getImage(final String url) {
        final String path = getDirectory() + "/" + convertUrlToFileName(url);

        System.out.println(path);
        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;
    }

2.将图片存入本地缓存:在存入图片之前,需要判断SD卡的空间是否充足,若不足,则释放一些空间,若释放了还是不足,则不再缓存

 /** 将图片存入文件缓存 **/
    public void saveBitmap(Bitmap bm, String url) {
        if (bm == null) {
            return;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足,释放一些空间出来
            removeCache(getDirectory());
//            return;
        }
        //释放之后发现还不够,就不存了
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            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();
            removeCache(getDirectory());
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }
    }


    /** 将图片存入文件缓存 **/
    public String saveBitmap(InputStream inputStream, String url) {
        if (inputStream == null) {
            return null;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足,释放一些空间出来
            removeCache(getDirectory());
//            return;
        }
        //释放之后发现还不够,就不存了
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            return null;
        }

        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();
            FileOutputStream fos=new FileOutputStream(file);
            try {

                byte[] bs=new byte[1024*100];
                int total=0;
                while ((total=inputStream.read(bs))!=-1){
                    fos.write(bs,0,total);
                }
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            fos.flush();
            fos.close();
            removeCache(getDirectory());
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }

        return file.getAbsolutePath();
    }

3.移除缓存:计算缓存的大小,若缓存的大小大于规定或者设定的大小,则移除

/**
     * 计算存储目录下的文件大小,
     * 当文件总大小大于规定的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 (!Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            return false;
        }
        if (files == null) {
            return true;
        }   
        //计算缓存目录现在的使用量
        int dirSize = 0;
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
        //判读是否已经超限
        if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //删多少 ---removeFactor
            int removeFactor = (int) ((0.4 * files.length) + 1);          
            //把数组中的文件---即全部缓存文件,根据最后修改时间来排序
            Arrays.sort(files, new FileLastModifSort());
            for (int i = 0; i < removeFactor; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }      
        if (freeSpaceOnSd() <= CACHE_SIZE) {
            return false;
        }

        return true;
    }

完整代码:

package com.example.sj.imageloderdemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;

public class ImageFileCache {
    //SD卡缓存目录
    private static final String CACHDIR = "ImgCach";
    //缓存文件后缀
    private static final String WHOLESALE_CONV = ".cach";

    private static final int MB = 1024*1024;
    //缓存最大容量
    private static final int CACHE_SIZE = 100;
    //当新增一个图片时,我们需要的SD卡最少容量
    private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 100;


    /** 从缓存中获取图片 **/
    public Bitmap getImage(final String url) {
        final String path = getDirectory() + "/" + convertUrlToFileName(url);

        System.out.println(path);
        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;
    }

    /** 将图片存入文件缓存 **/
    public void saveBitmap(Bitmap bm, String url) {
        if (bm == null) {
            return;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足,释放一些空间出来
            removeCache(getDirectory());
//            return;
        }
        //释放之后发现还不够,就不存了
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            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();
            removeCache(getDirectory());
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }
    }


    /** 将图片存入文件缓存 **/
    public String saveBitmap(InputStream inputStream, String url) {
        if (inputStream == null) {
            return null;
        }
        //判断sdcard上的空间
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足,释放一些空间出来
            removeCache(getDirectory());
//            return;
        }
        //释放之后发现还不够,就不存了
        if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            return null;
        }

        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();
            FileOutputStream fos=new FileOutputStream(file);
            try {

                byte[] bs=new byte[1024*100];
                int total=0;
                while ((total=inputStream.read(bs))!=-1){
                    fos.write(bs,0,total);
                }
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            fos.flush();
            fos.close();
            removeCache(getDirectory());
        } catch (FileNotFoundException e) {
            Log.w("ImageFileCache", "FileNotFoundException");
        } catch (IOException e) {
            Log.w("ImageFileCache", "IOException");
        }

        return file.getAbsolutePath();
    }
    /**
     * 计算存储目录下的文件大小,
     * 当文件总大小大于规定的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 (!Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            return false;
        }
        if (files == null) {
            return true;
        }   
        //计算缓存目录现在的使用量
        int dirSize = 0;
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
        //判读是否已经超限
        if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //删多少 ---removeFactor
            int removeFactor = (int) ((0.4 * files.length) + 1);          
            //把数组中的文件---即全部缓存文件,根据最后修改时间来排序
            Arrays.sort(files, new FileLastModifSort());
            for (int i = 0; i < removeFactor; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }      
        if (freeSpaceOnSd() <= CACHE_SIZE) {
            return false;
        }

        return true;
    }

    /** 修改文件的最后修改时间   LRU **/
    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());
        double sdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
        return (int) sdFreeMB;
    }

    /** 将url转成文件名 **/
    private String convertUrlToFileName(String url) {

        return url.hashCode()+"" + WHOLESALE_CONV;
    }

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

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

    /**
     * 根据文件的最后修改时间进行排序
     */
    private class FileLastModifSort implements Comparator<File> {
        public int compare(File arg0, File arg1) {
            if (arg0.lastModified() > arg1.lastModified()) {
                return 1;
            } else if (arg0.lastModified() == arg1.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }

}

网络缓存

网络缓存实质就是直接从网络上获取图片,即:执行网络访问,获取数据:
这里写图片描述
总结:到这里,我们就根据缓存的原理去分析Imageloader的display()方法:
首先从内存中获取,若内存中存在则直接返回图片
若内存中不存在,则从本地缓存中获取,同样的,若不存在,则只能从网络上获取

猜你喜欢

转载自blog.csdn.net/ygz111111/article/details/80948947