Android中图片的三级缓存浅析

作者: 笑说余生 | 来源:发表于2016-09-29 21:23 被阅读4146次

    我的主页
    Demo下载地址


    图片的三级缓存机制一般是指应用加载图片的时候,分别去访问内存,文件和网络而获取图片数据的一种行为。以下内容只是简单的介绍了三级缓存的思想和大致流程,还有很多细节未进行处理,如果想深入研究可以在Github上找Picasso的源码进行研究,谢谢。

    一、三级缓存流程图

    三级缓存流程图

    二、代码框架搭建

    • 这里我仿造Picasso的加载图片代码,也做出了with,load,into等方法。

    2.1 with(context)

    • 这个方法传入上下文,返回ImageManager对象。

        /**
         * 初始化对象
         *
         * @param context
         * @return
         */
        public static ImageManager with(Context context) {
            mContext = context;
            return getInstance();
        }
      
    • 因为ImageManager会不断的调用,所以要做成单利。

        /**
         * 获取对象的单利
         *
         * @return
         */
        private static ImageManager instance;
        private static ImageManager getInstance() {
            if (instance == null) {
                instance = new ImageManager();
            }
      
            return instance;
        }
      

    2.2 load(url)

    • 这个方法返回一个自定义的RequestCreator内部类,对图片的操作都在这个内部类中进行。

        /**
         * 加载图片的url地址,返回RequestCreator对象
         *
         * @param url
         * @return
         */
        public RequestCreator load(String url) {
      
            return new RequestCreator(url);
        }
      
    • RequestCreator的构造方法中接收传入的url 。

        // 初始化图片的url地址
        public RequestCreator(String url) {
            this.url = url;
        }
      

    2.3 into(imageview)

    • 这是RequestCreator类的方法,也是工具类的核心方法,这个方法里进行图片的三个缓存处理。

    三、内存缓存

    3.1 Java中对象的四种引用类型介绍

    • 强引用

      • Java中所有new出来的对象都是强引用类型,回收的时候,GC宁愿抛出OOM异常,也不回收它。

          Map<String, Bitmap> mImageCache = new HashMap<>();
        
    • 软引用,SoftReference

      • 内存足够时,不回收。内存不够时,就回收。这里使用这种方式缓存对象。

          Map<String, SoftReference<Bitmap>> mImageCache = new HashMap<>();
        
    • 弱引用,WeakReference

      • GC一出来工作就回收它。
    • 虚引用,PhantomReference

      • 用完就消失。

    3.2 使用LruCache类来做缓存

    • LruCache其实是一个Hash表,内部使用的是LinkedHashMap存储数据。使用LruCache类可以规定缓存内存的大小,并且这个类内部使用到了最近最少使用算法来管理缓存内存。

        LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
      

    3.3 代码实现内存缓存

    • 创建缓存集合

        /**
         * 内存储存图片的集合
         * 使用lrucache缓存图片,这里不能申明在方法里,不然会被覆盖掉
         * 使用软引用类型对象
         * 4兆的大小作为缓存
         */
        private LruCache<String, SoftReference<Bitmap>> mImageCache = new LruCache<>(1024 * 1024 * 4);
      
    • 访问内存时先从集合中取出软引用,获取BitMap

        // 1 去内存之中找,有就显示,没有就往下走
        SoftReference<Bitmap> reference = mImageCache.get(url);
        Bitmap cacheBitmap;
        if(reference != null){
            cacheBitmap = reference.get();
            // 有就显示图片
            imageView.setImageBitmap(cacheBitmap);
      
            Log.d("RequestCreator:", "内存中有图片显示");
            // 不往下走了
            return;
        }
      
    • 如果缓存集合中的数据为空,就继续往下走。

    四、文件缓存

    4.1 缓存文件存储的路径设定

    • 存储的路径首先要考虑SD卡的缓存目录,当SD卡不存在时,就只能存到内部存储的缓存目录了。

        /**
         * 获取缓存路径目录
         */
        private File getCacheDir() {
      
            // 获取保存的文件夹路径
            File file;
            if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
                // 有SD卡就保存到sd卡
                file = mContext.getExternalCacheDir();
            } else {
                // 没有就保存到内部储存
                file = mContext.getCacheDir();
            }
            return file;
        }
      

    4.2 解析文件生成Bitmap对象

    • 存储的文件的名字截取URL中的名字。

    • 文件名使用Md5加密。

        /**
         * 从文件中获取bitmap
         *
         * @return
         */
        private Bitmap getBitmapFromFile() {
            // 从url中获取文件名字
            String fileName = url.substring(url.lastIndexOf("/") + 1);
      
            File file = new File(getCacheDir(),MD5Util.encodeMd5(fileName));
            // 确保路径没有问题
            if (file.exists() && file.length() > 0) {
                // 返回图片
                return BitmapFactory.decodeFile(file.getAbsolutePath());
            } else {
                return null;
            }
        }
      

    4.3 判断是否有缓存

    • 有缓存则读取出来显示,并且将缓存存入内存,没有就继续往下走。

        // 2 去本地硬盘中找,有就显示,没有就继续往下走
        // 将文件转换成bitmap对象
        Bitmap diskBitmap = getBitmapFromFile();
        if (diskBitmap != null) {
            // 本地磁盘有就显示图片
            imageView.setImageBitmap(diskBitmap);
      
            // 保存到内存中去
            mImageCache.put(url, new SoftReference<Bitmap>(diskBitmap));
      
            Log.d("RequestCreator:", "磁盘中有图片显示");
      
            // 不往下走了
            return;
        }
      

    五、联网加载

    5.1 简单线程池处理耗时的网络请求

    • 创建线程池对象

        /**
         * 构建出线程池,5条线程
         */
        private  ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
      
    • 提交任务,让RequestCreator实现Runnable接口,run方法中执行任务

        // 3 联网请求数据
        // 前面两步都没有的话就去联网加载数据
        // 将从网络上获取的数据放到线程池去执行
        mExecutorService.submit(this);
      

    5.2 联网加载数据

    • 使用HttpUrlConnection连接网络

        // 子线程
        // 处理网络请求
        try {
            URL loadUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) loadUrl.openConnection();
      
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(2000);
      
            if (conn.getResponseCode() == 200) {
                InputStream is = conn.getInputStream();
      
                // 获取到图片进行显示
                final Bitmap bm = BitmapFactory.decodeStream(is);
      
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        // 主线程
                        imageView.setImageBitmap(bm);
                    }
                });
      
                Log.d("RequestCreator:", "联网显示图片");
            } else {
                // 联网失败,显示失败图片
                showError();
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 发生异常显示失败图片
            showError();
        }
      

    5.3 保存数据到内存和文件

    • 使用缓存保存数据

        // 3.1 保存到内存
        mImageCache.put(url, new SoftReference<>(bm));
      
        // 3.2 保存到磁盘
        // 从url中获取文件名字
        String fileName = url.substring(url.lastIndexOf("/") + 1);
      
        // 获取存储路径
        File file = new File(getCacheDir(), MD5Util.encodeMd5(fileName));
        FileOutputStream os = new FileOutputStream(file);
        // 将图片转换为文件进行存储
        bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
      

    六、细节处理

    6.1 设置占位图

    • 界面一上来加载图片时肯定是空白的,所以需要一张占位图。

    • RequestCreator类提供一个方法,将占位图片资源ID传进来。

        /**
         * 设置默认图片,占位图片
         *
         * @param holderResId
         */
        public RequestCreator placeholder(int holderResId) {
            this.holderResId = holderResId;
      
            return this;
        }
      
    • 在into方法中,读取缓存之前,就让默认图显示。

         // 一进来先设置占位图片
         imageView.setImageResource(holderResId);
      

    6.2 设置错误图片

    • 加载数据出现错误和异常都显示错误图片。

        /**
         * 显示错误图片
         */
        private void showError() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageResource(errorResId);
                }
            });
        }
      

    七、使用自己封装的小框架加载图片

    • 使用很简单,和Picasso一样,一行代码就搞定。

         // 使用自己封装的图片缓存工具类加载图片
        ImageManager.with(mContext).load(imgUrl).placeholder(R.drawable.ic_default).error(R.drawable.ic_error).into(ivImage);
      
    • 效果图

    效果图

    我的主页
    Demo下载地址

    以上纯属于个人平时工作和学习的一些总结分享,如果有什么错误欢迎随时指出,大家可以讨论一起进步。

    相关文章

      网友评论

      • 残唐:请问,我如何清除已有的缓存?
      • 深圳某某人:大圣,请问下你的apikey是怎么申请的?我把你这个demo敲了一遍,在http://lbsyun.baidu.com/apiconsole/key/create 里面使用包名和sha1证书申请了apikey,但是没生效
        深圳某某人:@敲代码的大圣 已经搞好了,谢谢
        笑说余生:@深圳某某人 啥,我图片从http://apistore.baidu.com/apiworks/servicedetail/992.html拿的,你怎么跑去百度地图啦~~申请一下就好啦
      • 28337af108b1:三级缓存如果按照你这样的说法,面试一票否决,抛开技术不谈,啥叫 缓存?
        zzl93:哪里不对么
        笑说余生:@紫星325 缓存个人理解就是将数据暂时的保存下来,像图片的内存缓存就是将bitmap对象引用保存在内存,文件缓存就是直接将图片保存,网络请求并不算缓存,但是一般都是这三个步骤,所以把网络请求也叫缓存,这样理解没错吧?
      • 都有米:关于三极缓存这篇文章才是正解! http://www.jianshu.com/p/eadb0ef271b0
        笑说余生: @都有米 学习了。

      本文标题:Android中图片的三级缓存浅析

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