美文网首页Android 开发必备Android基础知识面试专栏
图片三级缓存的原理(项目经验)

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

作者: 总会颠沛流离 | 来源:发表于2019-04-28 19:26 被阅读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);

    相关文章

      网友评论

        本文标题:图片三级缓存的原理(项目经验)

        本文链接:https://www.haomeiwen.com/subject/loijnqtx.html