Android中实现图片的三级缓存

2018-07-31  本文已影响0人  lxbnjupt

一、简介

从网络加载图片.png

如图所示,我们的Android App经常需要通过网络获取图片。但是,如果每次启动App都需要从网络获取图片,或者是想重复浏览一些图片的时候,每次浏览都需要通过网络获取,那么势必就会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的App,其用户数量级肯定会受到影响。其次,每次启动App都需要从网络获取图片,在网络情况不佳的情况下会加载很慢,非常影响用户体验。另外,从开发角度来说,Bitmap的创建非常消耗时间和内存,可能导致频繁GC,进而可能导致卡顿。
因此,提出了三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的流量耗费,加快图片的加载速度,减少卡顿,从而增强用户体验,进而有助于提高App的用户数量级。

二、什么是三级缓存

三、三级缓存原理

四、图片三级缓存代码实现

1、网络缓存

这里,我们使用了AsyncTask来进行网络图片的异步加载。简单来说,AsyncTask可以看作是对线程池和Handler的封装。其中,线程池用于线程调度、复用,以及执行任务,Handler则用于异步通信以及消息传递。可以看到,从网络获取图片后,将其分别保存至本地缓存和内存缓存。

public class NetworkCacheUtils {

    private LocalCacheUtils mLocalCacheUtils;
    private RamCacheUtils mRamCacheUtils;

    public NetworkCacheUtils(LocalCacheUtils localCacheUtils, RamCacheUtils ramCacheUtils) {
        this.mLocalCacheUtils = localCacheUtils;
        this.mRamCacheUtils = ramCacheUtils;
    }

    /**
     * 从网络加载图片
     * @param url
     * @return
     */
    public Bitmap loadBitmapFromNetwork(String url) {
        try {
            return new LoadBitmapTask().execute(url).get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private class LoadBitmapTask extends AsyncTask<String, Void, Bitmap> {

        private String loadUrl;

        @Override
        protected Bitmap doInBackground(String... strings) {
            loadUrl = strings[0];
            try {
                URL url = new URL(loadUrl);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(10000);
                httpURLConnection.setReadTimeout(10000);
                httpURLConnection.setRequestMethod("GET");
                if (httpURLConnection.getResponseCode() == 200) {
                    return BitmapFactory.decodeStream(httpURLConnection.getInputStream());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (bitmap != null) {
                //从网络获取图片后,保存至本地缓存
                mLocalCacheUtils.setBitmapToLocal(loadUrl, bitmap);

                //从网络获取图片后,保存至内存
                mRamCacheUtils.setBitmapToMemory(loadUrl, bitmap);
            }
        }
    }
}

2、本地缓存

初次从网络获取图片后,将其保存至本地缓存。在保存的时候,我们一般会对图片的url进行MD5加密处理,以此处理后的字符串作为图片名称来进行保存。另外,在创建缓存地址的时候,我们需要考虑如果这个手机没有SD卡,或者SD正好被移除了的情况。

public class LocalCacheUtils {

    private static final String LOCAL_CACHE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/ImageCache";
    private File cacheDir;

    public LocalCacheUtils(Context context) {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
                || !Environment.isExternalStorageRemovable()) {
            cacheDir = new File(LOCAL_CACHE_PATH);
        } else {
            cacheDir = context.getCacheDir();
        }
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
    }

    public Bitmap loadBitmapFromLocal(String url) {
        try {
            //把图片的url当做文件名,并进行MD5加密
            String fileName = MD5(url);
            File file = new File(cacheDir, fileName);
            return BitmapFactory.decodeStream(new FileInputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从本地读取图片
     *
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
        try {
            //把图片的url当做文件名,并进行MD5加密
            String fileName = MD5(url);
            File file = new File(cacheDir, fileName);

            //把图片保存至本地
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        /**
         * 往本地中保存图片
         * @param content
         * @return
         */

    private String MD5(String content) {
        byte[] hash;
        try {
            hash = MessageDigest.getInstance("MD5").digest(content.getBytes("UTF-8"));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("NoSuchAlgorithmException", e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UnsupportedEncodingException", e);
        }

        StringBuilder hex = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xFF) < 0x10) {
                hex.append("0");
            }
            hex.append(Integer.toHexString(b & 0xFF));
        }
        return hex.toString();
    }
}

3、内存缓存

(1)LRU (Least Recently Used),最近最少使用算法

public class LRUCache {

  private int capacity;
  private Map<Integer, Integer> cache;

  public LRUCache(int capacity) {
    this.capacity = capacity;
    this.cache = new LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
      @Override
      protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity;
      }
    };
  }

  public int getCapacity() {
    return capacity;
  }

  public int getCache(int key) {
    if (cache.containsKey(key)) {
      return cache.get(key);
    } else {
      return -1;
    }
  }

  public void setCache(int key, int value) {
    cache.put(key, value);
  }

  public void printLinkedHashMap() {
    cache.forEach((k, v) -> System.out.println(String.format("key: %d, value: %d", k, v)));
    System.out.println("-------");
  }

  public static void main(String[] args) {
    LRUCache lruCache = new LRUCache(5);
    lruCache.setCache(1, 100);
    lruCache.setCache(2, 200);
    lruCache.setCache(3, 300);
    lruCache.setCache(4, 400);
    lruCache.setCache(5, 500);
    lruCache.printLinkedHashMap();

    lruCache.getCache(1);
    lruCache.printLinkedHashMap();

    lruCache.getCache(3);
    lruCache.printLinkedHashMap();

    lruCache.setCache(6, 600);
    lruCache.printLinkedHashMap();
  }
}

(2)通过LruCache,实现内存缓存

public class RamCacheUtils {

    private LruCache<String, Bitmap> mRamCache;

    public RamCacheUtils() {
        //得到最大允许内存的1/8,即超过指定内存,则开始回收
        long maxMemory = Runtime.getRuntime().maxMemory();

        //指定mRamCache最大允许内存的1/8,即超过指定内存,则开始回收
        mRamCache = new LruCache<String, Bitmap>((int) (maxMemory / 8)) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    /**
     * 从内存中读图片
     * @param url
     * @return
     */
    public Bitmap loadBitmapFromRam(String url) {
        return mRamCache.get(url);
    }

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

五、自定义图片缓存工具类

public class ImageLoaderUtils {

    private static ImageLoaderUtils INSTANCE;
    private RamCacheUtils mRamCacheUtils;
    private LocalCacheUtils mLocalCacheUtils;
    private NetworkCacheUtils mNetworkCacheUtils;
    private Context mContext;

    private ImageLoaderUtils(Context context) {
        mContext = context.getApplicationContext();
        mRamCacheUtils = new RamCacheUtils();
        mLocalCacheUtils = new LocalCacheUtils(mContext);
        mNetworkCacheUtils = new NetworkCacheUtils(mLocalCacheUtils, mRamCacheUtils);
    }

    public static ImageLoaderUtils getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new ImageLoaderUtils(context);
        }
        return INSTANCE;
    }

    public Bitmap load(String url) {
        //内存缓存
        if(mRamCacheUtils.loadBitmapFromRam(url) != null){
            Log.e("ImageLoaderUtils", "**** 从内存获取图片 ****");
            return mRamCacheUtils.loadBitmapFromRam(url);
        }

        //本地缓存
        if(mLocalCacheUtils.loadBitmapFromLocal(url) != null){
            Log.e("ImageLoaderUtils", "**** 从本地获取图片 ****");
            return mLocalCacheUtils.loadBitmapFromLocal(url);
        }

        //网络缓存
        if(mNetworkCacheUtils.loadBitmapFromNetwork(url) != null){
            Log.e("ImageLoaderUtils", "**** 从网络获取图片 ****");
            return mNetworkCacheUtils.loadBitmapFromNetwork(url);
        }
        return null;
    }
}

我们在加载图片的时候,直接调用工具类ImageLoaderUtils的load(String url)方法即可,大概可以直接像这样调用:

private void loadImage() {
        String url = "http://up.boohee.cn/house/u/shop/cm25_yynd_200g/banner500_v1.jpg";
        imageView.setImageBitmap(ImageLoaderUtils.getInstance(this).load(url));
    }
上一篇下一篇

猜你喜欢

热点阅读