Android图片缓存策略

前言

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

一直想写一个自己的图片缓存框架,之前一直用Glide、Fresco等一些主流的图片框架。这些框架对处理的处理都做的非常好,也查阅了这些框架的一些源码,整体思路是使用三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量。

什么是三级缓存

  • 网络缓存, 不优先加载, 速度慢,浪费流量
  • 本地缓存, 次优先加载, 速度快
  • 内存缓存, 优先加载, 速度最快(包括强引用(LruCache)和软引用(SoftReference))

三级缓存原理

加载图片时,判断内存缓存中是否有该图片,没有则到本地SD卡中查找,SD卡中没有则通过网络请求获取图片。

因为Android内存空间是很珍贵的,我们不能发过多的图片在缓存中,容易导致OOM,这个时候我们就要使用软引用(SoftReference)。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁,因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。

LruCache创建LruCache时,我们需要设置它的大小,一般是系统最大存储空间的八分之一。LruCache的机制是存储最近、最后使用的图片,如果LruCache中的图片大小超过了其默认大小,则会将最老、最远使用的图片移除出去。当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用(SoftReference)中。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片

流程

实现

1、自定义图片加载工具类(VImageUtils)

通过 VImageUtils.disPlay(ImageView ivPic, String url)提供给外部方法进行图片加载。

/**
 * 自定义图片缓存工具类
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class VImageUtils {
    private static final String TAG = "VImageUtils";
    public static void disPlay(ImageView ivPic, String url) {
        Bitmap bitmap;
        //内存缓存
        bitmap=MemoryCacheUtils.getInstance().getBitmapFromMemory(url);
        if (bitmap!=null){
            ivPic.setImageBitmap(bitmap);
            Log.d(TAG,"从内存获取图片啦.....");
            return;
        }

        //本地缓存
        bitmap = LocalCacheUtils.getInstance().getBitmapFromLocal(url);
        if(bitmap !=null){
            ivPic.setImageBitmap(bitmap);
            Log.d(TAG,"从本地获取图片啦.....");
            //从本地获取图片后,保存至内存中
            MemoryCacheUtils.getInstance().setBitmapToMemory(url,bitmap);
            return;
        }
        //网络缓存
        NetCacheUtils.getInstance().getBitmapFromNet(ivPic,url);
    }

}

2、网络缓存(NetCacheUtils)

  • 网络缓存中主要用到了AsyncTask来进行异步数据的加载
  • 简单来说,AsyncTask可以看作是一个对handler和线程池的封装,通常,AsyncTask主要用于数据简单时,handler+thread主要用于数据量多且复杂时,当然这也不是必须的,仁者见仁智者见智。
  • 同时,为了避免内存溢出的问题,我们可以在获取网络图片后。对其进行图片压缩。
/**
 * 网络缓存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class NetCacheUtils {
    private static  NetCacheUtils mInstance;
    private static final String TAG = "NetCacheUtils";
    private LocalCacheUtils mLocalCacheUtils;
    private MemoryCacheUtils mMemoryCacheUtils;

    private NetCacheUtils() {
        mMemoryCacheUtils = MemoryCacheUtils.getInstance();
        mLocalCacheUtils = LocalCacheUtils.getInstance();
    }
    public static NetCacheUtils getInstance(){
        if(mInstance == null){
            synchronized (NetCacheUtils.class) {
                if (mInstance == null) {
                    mInstance = new NetCacheUtils();
                }
            }
        }
        return mInstance;
    }
    /**
     * 从网络下载图片
     * @param ivPic 显示图片的imageview
     * @param url   下载图片的网络地址
     */
    public void getBitmapFromNet(ImageView ivPic, String url) {
        new BitmapTask().execute(ivPic, url);//启动AsyncTask

    }

    /**
     * AsyncTask就是对handler和线程池的封装
     * 第一个泛型:参数类型
     * 第二个泛型:更新进度的泛型
     * 第三个泛型:onPostExecute的返回结果
     */
    class BitmapTask extends AsyncTask {

        private ImageView ivPic;
        private String url;

        /**
         * 后台耗时操作,存在于子线程中
         * @param params
         * @return
         */
        @Override
        protected Bitmap doInBackground(Object[] params) {
            ivPic = (ImageView) params[0];
            url = (String) params[1];

            return downLoadBitmap(url);
        }

        /**
         * 更新进度,在主线程中
         * @param values
         */
        @Override
        protected void onProgressUpdate(Void[] values) {
            super.onProgressUpdate(values);
        }

        /**
         * 耗时方法结束后执行该方法,主线程中
         * @param result
         */
        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                ivPic.setImageBitmap(result);
                Log.d(TAG,"从网络缓存图片啦.....");

                //从网络获取图片后,保存至本地缓存
                mLocalCacheUtils.setBitmapToLocal(url, result);
                //保存至内存中
                mMemoryCacheUtils.setBitmapToMemory(url, result);

            }
        }
    }

    /**
     * 网络下载图片
     * @param url
     * @return
     */
    private Bitmap downLoadBitmap(String url) {
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.setRequestMethod("GET");

            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                //图片压缩
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize=2;//宽高压缩为原来的1/2
                options.inPreferredConfig=Bitmap.Config.ARGB_4444;
                Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream(),null,options);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
        }

        return null;
    }
}



3、本地缓存(LocalCacheUtils)

  • 在初次通过网络获取图片后,我们可以在本地SD卡中将图片保存起来
  • 可以使用MD5加密图片的网络地址,来作为图片的名称保存
/**
 * 本地缓存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class LocalCacheUtils {
    private static final String CACHE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/VImageUtils";
    private static  LocalCacheUtils mInstance;
    private LocalCacheUtils(){

    }
    public static LocalCacheUtils getInstance(){
        if(mInstance == null){
            synchronized (LocalCacheUtils.class) {
                if (mInstance == null) {
                    mInstance = new LocalCacheUtils();
                }
            }
        }
        return mInstance;
    }
    /**
     * 从本地读取图片
     * @param url
     * @return Bitmap
     */
    public Bitmap getBitmapFromLocal(String url) {
        String fileName = null;//把图片的url当做文件名,并进行MD5加密
        try {
            fileName = MD5Encoder.encode(url);
            File file = new File(CACHE_PATH, fileName);

            Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));

            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    /**
     * 从网络获取图片后,保存至本地缓存
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
        try {
            String fileName = MD5Encoder.encode(url);//把图片的url当做文件名,并进行MD5加密
            File file = new File(CACHE_PATH, fileName);

            //通过得到文件的父文件,判断父文件是否存在
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            //把图片保存至本地
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

4、内存缓存(MemoryCacheUtils)

  • 这块东西比较重要,也是图片缓存的关键一步
  • 通过LruCache 来缓存我们的图片,由于我们LruCache的空间有限,我们把剩余的图片放入
    Map> 软引用中,这样不会造成内存泄露。
  • 在向缓存中取图片时,通过最少最近使用算法,先从LruCache中查找,在LruCache中没有再从SoftReference软引用中查找。在SoftReference查到然后重新放入LruCache中。
/**
 * 内存缓存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */
public class MemoryCacheUtils {
    private static  MemoryCacheUtils mInstance;
    private ImageCache mImageCache;

    private MemoryCacheUtils(){
        Map> cacheMap = new HashMap<>();
        mImageCache = new ImageCache(cacheMap);

    }
    public static MemoryCacheUtils getInstance(){
        if(mInstance == null){
            synchronized (MemoryCacheUtils.class) {
                if (mInstance == null) {
                    mInstance = new MemoryCacheUtils();
                }
            }
        }
        return mInstance;
    }

    /**
     * 从内存中读图片
     * @param url
     */
    public Bitmap getBitmapFromMemory(String url) {
        Bitmap bitmap = mImageCache.get(url);
        // 如果图片不存在强引用中,则去软引用(SoftReference)中查找
        if(bitmap == null){
            Map> cacheMap = mImageCache.getCacheMap();
            SoftReference softReference = cacheMap.get(url);
            if(softReference!=null){
                bitmap = softReference.get();
                //重新放入强引用缓存中
                mImageCache.put(url,bitmap);
            }
        }
        return bitmap;

    }

    /**
     * 往内存中写图片
     * @param url
     * @param bitmap
     */
    public void setBitmapToMemory(String url, Bitmap bitmap) {
        mImageCache.put(url,bitmap);
    }

}
ImageCache代码:
/**
 * 图片缓存
 *
 * @author Veer
 * @email [email protected]
 * @date 18/12/11
 */

public class ImageCache extends LruCache {

    private Map> cacheMap;

    public ImageCache(Map> cacheMap) {
        super((int) (Runtime.getRuntime().maxMemory() / 8));
        this.cacheMap = cacheMap;
    }

    @Override // 获取图片大小
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
    }

    // 当有图片从LruCache中移除时,将其放进软引用集合中
    @Override
    protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
        if (oldValue != null) {
            SoftReference softReference = new SoftReference(oldValue);
            cacheMap.put(key, softReference);
        }
    }

    public Map> getCacheMap() {
        return cacheMap;
    }
}

总结

以上就是图片缓存的策略,后期我会完善我的这个图片框架,争取做到向那些主流框架一样。

会一直在我的github上更新,希望多多关注

 

 

 

 

 

 

你可能感兴趣的:(Android疑难功能)