Android 开发必备Android基础知识面试专栏

图片三级缓存的原理(项目经验)

2019-04-28  本文已影响44人  总会颠沛流离

补充的知识:
强引用:不会被回收
弱引用:垃圾回收器触发会被回收
软引用:系统检测内存不足时会被回收
虚引用:等于null;

为什么需要图片缓存

android默认给每个应用只分配16M的内存,所以如果加载过多的图片,为了防止内存溢出,应该将图片缓存起来。图片的三级缓存分别是:
内存缓存
本地缓存
网络缓存
其中,内存缓存应优先加载,它速度最快;本地缓存次优先加载,它速度也快;网络缓存不应该优先加载,它走网络,速度慢且耗流量。

三级缓存的概念

内存 > 硬盘 > 网络

由内存、硬盘、网络缓存形成。
原理


20161129202321287.png

Android原生为我们提供了一个LruCache,其中维护着一个LinkedHashMap。LruCache可以用来存储各种类型的数据,但最常见的是存储图片(Bitmap)。LruCache创建LruCache时,我们需要设置它的大小,一般是系统最大存储空间的八分之一。LruCache的机制是存储最近、最后使用的图片,如果LruCache中的图片大小超过了其默认大小,则会将最老、最远使用的图片移除出去。

当图片被LruCache移除的时候,我们需要手动将这张图片添加到软引用(SoftReference)中。我们需要在项目中维护一个由SoftReference组成的集合,其中存储被LruCache移除出来的图片。软引用的一个好处是当系统空间紧张的时候,软引用可以随时销毁,因此软引用是不会影响系统运行的,换句话说,如果系统因为某个原因OOM了,那么这个原因肯定不是软引用引起的。

关于三级缓存用到的技术

Android高效加载大图、多图解决方案、有效避免程序OOM使用的核心技术就是LruCache。
LruCache只是管理了内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时。对此,
Google又提供了一套硬盘缓存的解决方案:DiskLruCache(非Google官方编写,但获得官方认证。)

用法和流程

自己理解:当每次加载图片的时候都优先去内存加载图片、当内存中获取不到图片的时候则去硬盘缓存读取、如果硬盘缓存读取不到数据就去网络获取数据。不
管是从硬盘缓存还是从网络获取,读取到了数据之后都应该添加到内存缓存当中,这样的话我们下次再去读取图片的时候就能迅速从内存当中读取到
,而如果该图片从内存中被移除了的话,那就重复再执行一遍上述流程。

官方的原理:
 当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找,如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去文件系统中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到文件系统中,然后放到LruCache中。

三级缓存的具体实现

网络缓存

根据图片的url去加载图片
在本地和内存中缓存
public class NetCacheUtils {

    private LocalCacheUtils mLocalCacheUtils;
    private MemoryCacheUtils mMemoryCacheUtils;

    public NetCacheUtils(LocalCacheUtils localCacheUtils,
            MemoryCacheUtils memoryCacheUtils) {
        mLocalCacheUtils = localCacheUtils;
        mMemoryCacheUtils = memoryCacheUtils;
    }

    /**
     * 从网络下载图片
     * 
     * @param ivPic
     * @param url
     */
    public void getBitmapFromNet(ImageView ivPic, String url) {
        new BitmapTask().execute(ivPic, url);// 启动AsyncTask,
                                                // 参数会在doInbackground中获取
    }

    /**
     * Handler和线程池的封装
     * 
     * 第一个泛型: 参数类型 第二个泛型: 更新进度的泛型, 第三个泛型是onPostExecute的返回结果
     * 
     * 
     */
    class BitmapTask extends AsyncTask<Object, Void, Bitmap> {

        private ImageView ivPic;
        private String url;

        /**
         * 后台耗时方法在此执行, 子线程
         */
        @Override
        protected Bitmap doInBackground(Object... params) {
            ivPic = (ImageView) params[0];
            url = (String) params[1];

            ivPic.setTag(url);// 将url和imageview绑定

            return downloadBitmap(url);
        }

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

        /**
         * 耗时方法结束后,执行该方法, 主线程
         */
        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                String bindUrl = (String) ivPic.getTag();

                if (url.equals(bindUrl)) {// 确保图片设定给了正确的imageview
                    ivPic.setImageBitmap(result);
                    mLocalCacheUtils.setBitmapToLocal(url, result);// 将图片保存在本地
                    mMemoryCacheUtils.setBitmapToMemory(url, result);// 将图片保存在内存
                    System.out.println("从网络缓存读取图片啦...");
                }
            }
        }
    }

    /**
     * 下载图片
     * 
     * @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");
            conn.connect();

            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                InputStream inputStream = conn.getInputStream();

                //图片压缩处理
                BitmapFactory.Options option = new BitmapFactory.Options();
                option.inSampleSize = 2;//宽高都压缩为原来的二分之一, 此参数需要根据图片要展示的大小来确定
                option.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片格式

                Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option);
                return bitmap;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
        }

        return null;
    }
}
本地缓存

两个方法:设置本地缓存,获取本地缓存

public class LocalCacheUtils {

    public static final String CACHE_PATH = Environment
            .getExternalStorageDirectory().getAbsolutePath() + "/local_cache";

    /**
     * 从本地sdcard读图片
     */
    public Bitmap getBitmapFromLocal(String url) {
        try {
            String fileName = MD5Encoder.encode(url);
            File file = new File(CACHE_PATH, fileName);

            if (file.exists()) {
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
                        file));
                return bitmap;
            }

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

        return null;
    }

    /**
     * 向sdcard写图片
     * 
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
        try {
            String fileName = MD5Encoder.encode(url);

            File file = new File(CACHE_PATH, fileName);

            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {// 如果文件夹不存在, 创建文件夹
                parentFile.mkdirs();
            }

            // 将图片保存在本地
            bitmap.compress(CompressFormat.JPEG, 100,
                    new FileOutputStream(file));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
内存缓存

两个方法:设置内存缓存,获取内存缓存
问题:
1: 如果使用HashMap存储图片时,当图片越来越多时,会导致内存溢出,因为它是强引用,java的垃圾回收器不会回收。
2: 如若改成软引用SoftReference(内存不够时,垃圾回收器会考虑回收),仍有一个问题:在android2.3+, 系统会优先将SoftReference的对象提前回收掉, 即使内存够用。
解决办法:可以用LruCache来解决上述内存不回收或提前回收的问题。least recentlly use 最少最近使用算法 它会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定

    public class MemoryCacheUtils {
   // private HashMap<String, SoftReference<Bitmap>> mMemoryCache = new
        // HashMap<String, SoftReference<Bitmap>>();
        private LruCache<String, Bitmap> mMemoryCache;

        public MemoryCacheUtils() {
            long maxMemory = Runtime.getRuntime().maxMemory() / 8;// 模拟器默认是16M
            mMemoryCache = new LruCache<String, Bitmap>((int) maxMemory) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    int byteCount = value.getRowBytes() * value.getHeight();// 获取图片占用内存大小
                    return byteCount;
                }
            };
        }

        /**
         * 从内存读
         * 
         * @param url
         */
        public Bitmap getBitmapFromMemory(String url) {
            // SoftReference<Bitmap> softReference = mMemoryCache.get(url);
            // if (softReference != null) {
            // Bitmap bitmap = softReference.get();
            // return bitmap;
            // }
            return mMemoryCache.get(url);
        }

        /**
         * 写内存
         * 
         * @param url
         * @param bitmap
         */
        public void setBitmapToMemory(String url, Bitmap bitmap) {
            // SoftReference<Bitmap> softReference = new
            // SoftReference<Bitmap>(bitmap);
            // mMemoryCache.put(url, softReference);
            mMemoryCache.put(url, bitmap);
        }
    }
图片压缩
//图片压缩处理(在从网络获取图片的时候就进行压缩)
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 2;//宽高都压缩为原来的二分之一, 此参数需要根据图片要展示的大小来确定
option.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片格式
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option);
上一篇 下一篇

猜你喜欢

热点阅读