美文网首页优秀案例Android基础知识
自定义图片框架实现图片三级缓存

自定义图片框架实现图片三级缓存

作者: MengkZhang | 来源:发表于2019-05-28 20:54 被阅读33次

什么是三级缓存

网络缓存, 不优先加载, 速度慢,浪费流量
本地缓存, 次优先加载, 速度快
内存缓存, 优先加载, 速度最快

具体实现及代码

  • 网络缓存, 不优先加载, 速度慢,浪费流量
  • 本地缓存, 次优先加载, 速度快
  • 内存缓存, 优先加载, 速度最快

三级缓存原理

  • 首次加载 Android App 时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中
  • 之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片
  • 总之,只在初次访问新内容时,才通过网络获取图片资源

1. 自定义的图片缓存工具类(MyBitmapUtils)###

  • 通过new MyBitmapUtils().display(ImageView ivPic, String url) 提供给外部方法进行图片缓存的接口
  • 参数含义:ivPic 用于显示图片的ImageView,url 获取图片的网络地址
        MyBitmapUtils bitmapUtils = new MyBitmapUtils();
        bitmapUtils.display(mImageView,url);
/**
 * 自定义图片加载工具类,实现三级缓存
 * 加载图片的原理:imageView.setImageBitmap(bitmap)
 */
public class MyBitmapUtils {
    private NetCacheUtils mNetCacheUtils;
    private LocalCacheUtils mLocalCacheUtils;
    private MemoryCacheUtils mMemoryCacheUtils;

    public MyBitmapUtils() {
        mLocalCacheUtils = new LocalCacheUtils();
        mMemoryCacheUtils = new MemoryCacheUtils();
        mNetCacheUtils = new NetCacheUtils(mLocalCacheUtils,mMemoryCacheUtils);
    }

    public void display(ImageView ivPic,String url) {
        ivPic.setImageResource(R.mipmap.ic_launcher);
        Bitmap bitmap;

        //读取内存缓存
        bitmap = mMemoryCacheUtils.getBitmapFromMemory(url);
        if (bitmap != null) {
            ivPic.setImageBitmap(bitmap);
            System.out.println("从内存获取图片啦.....");
            Log.e("===z","从内存获取图片啦.....");
            return;
        }
        //读取磁盘缓存
        bitmap = mLocalCacheUtils.getBitmapFromLocal(url);
        if (bitmap != null) {
            ivPic.setImageBitmap(bitmap);
            System.out.println("从本地获取图片啦.....");
            Log.e("===z","从本地获取图片啦.....");
            return;
        }

        //读取网络缓存
        mNetCacheUtils.getBitmapFromNet(ivPic,url);
    }

}

2. 网络缓存(NetCacheUtils)###

  • 网络缓存中主要用到了AsyncTask来进行异步数据的加载
  • 简单来说,AsyncTask可以看作是一个对handler和线程池的封装,通常,AsyncTask主要用于数据简单时,handler+thread主要用于数据量多且复杂时,当然这也不是必须的,仁者见仁智者见智
  • 同时,为了避免内存溢出的问题,我们可以在获取网络图片后。对其进行图片压缩
/**
 * 三级缓存之网络缓存
 * 网络缓存中主要用到了AsyncTask来进行异步数据的加载
 * 简单来说,AsyncTask可以看作是一个对handler和线程池的封装,通常,AsyncTask主要用于数据简单时,handler+thread主要用于数据量多且复杂时,当然这也不是必须的,仁者见仁智者见智
 * 同时,为了避免内存溢出的问题,我们可以在获取网络图片后。对其进行图片压缩
 *
 */
class NetCacheUtils {

    private LocalCacheUtils mLocalCacheUtils;
    private MemoryCacheUtils mMemoryCacheUtils;

    public NetCacheUtils(LocalCacheUtils mLocalCacheUtils,MemoryCacheUtils mMemoryCacheUtils) {
        this.mLocalCacheUtils = mLocalCacheUtils;
        this.mMemoryCacheUtils = mMemoryCacheUtils;
    }

    public void getBitmapFromNet(ImageView ivPic, String url) {
        new BitmapTask().execute(ivPic,url);
    }

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

        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 url
         * @return
         */
        private Bitmap downloadBitmap(String url) {
            HttpURLConnection conn = null;
            try {
                conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setReadTimeout(5000);
                conn.setConnectTimeout(5000);
                conn.setRequestMethod("GET");

                int code = conn.getResponseCode();
                if (code == 200) {
                    InputStream inputStream = conn.getInputStream();
                    //压缩图片
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inSampleSize = 1;//宽高为原来的1/2
                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream,null,options);

                    Log.e("===z",bitmap + "");
                    return bitmap;
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
            }
            return null;
        }

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

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

                //缓存图片到本地
                mLocalCacheUtils.setBitmapToLocal(url,result);
                // 缓存图片到内存
                mMemoryCacheUtils.setBitmapToMemory(url,result);


            }
        }
    }


}

3. 本地缓存(LocalCacheUtils)###

  • 在初次通过网络获取图片后,我们可以在本地SD卡中将图片保存起来
  • 可以使用Base64.decode(url)图片的网络地址,来作为图片的名称保存
/**
 * 三级缓存之本地缓存
 */
class LocalCacheUtils {
    private static final String CACHE_PATH= Environment.getExternalStorageDirectory().getAbsolutePath()+"/WerbNews";

    /**
     * 从网络中获取图片后 保存到本地
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
//        String fileName = url;//把图片的url当做文件名,并进行MD5加密
        String fileName = Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
        try {
            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();
        }
    }

    public Bitmap getBitmapFromLocal(String url) {
//        String fileName = url;//把图片的url当做文件名,并进行MD5加密
        String fileName = Base64.encodeToString(url.getBytes(), Base64.DEFAULT);
        try {
            File file = new File(CACHE_PATH, fileName);
            Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

4. 内存缓存(MemoryCacheUtils)###

  • 这是本文中最重要且需要重点介绍的部分
  • 进行内存缓存,就一定要注意一个问题,那就是内存溢出(OutOfMemory)
  • 为什么会造成内存溢出?
  1. Android 虚拟机默认分配给每个App 16M的内存空间,真机会比16M大,但任会出现内存溢出的情况
  2. Android 系统在加载图片时是解析每一个像素的信息,再把每一个像素全部保存至内存中
  3. 图片大小 = 图片的总像素 * 每个像素占用的大小

单色图:每个像素占用1/8个字节,16色图:每个像素占用1/2个字节,256色图:每个像素占用1个字节,24位图:每个像素占用3个字节(常见的rgb构成的图片)

  1. 例如一张1920x1080的JPG图片,在Android 系统中是以ARGB格式解析的,即一个像素需占用4个字节,图片的大小=1920x1080x4=7M

实现方法:

通过 HashMap<String,Bitmap>键值对的方式保存图片,key为地址,value为图片对象,但因是强引用对象,很容易造成内存溢出,可以尝试SoftReference软引用对象

通过 HashMap<String, SoftReference<Bitmap>>SoftReference 为软引用对象(GC垃圾回收会自动回收软引用对象),但在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache

通过 LruCache<String,Bitmap> least recentlly use 最少最近使用算法
会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定

/**
 * 三级缓存之内存缓存
 */
class MemoryCacheUtils {

    private LruCache<String,Bitmap> mMemoryCache;

    public MemoryCacheUtils() {
        //得到手机最大允许内存的1/8,即超过指定内存,则开始回收
        long maxMemory = Runtime.getRuntime().maxMemory()/8;
        //需要传入允许的内存最大值,虚拟机默认内存16M,真机不一定相同
        mMemoryCache = new LruCache<String,Bitmap>((int) maxMemory) {
            /**
             * 用于计算每个条目的大小
             * @param key
             * @param value
             * @return
             */
            @Override
            protected int sizeOf(String key, Bitmap value) {
                int byteCount = value.getByteCount();
                return byteCount;
            }
        };

    }

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

    /**
     * 从内存读取图片
     * @param url
     * @return
     */
    public Bitmap getBitmapFromMemory(String url) {
        Bitmap bitmap = mMemoryCache.get(url);
        return bitmap;
    }
}

Demo地址:三级缓存

相关文章

  • 自定义图片框架实现图片三级缓存

    什么是三级缓存 网络缓存, 不优先加载, 速度慢,浪费流量本地缓存, 次优先加载, 速度快内存缓存, 优先加载, ...

  • 浅谈图片加载的三级缓存

    浅谈图片加载的三级缓存 之前被人问及过,图片的三级缓存是什么啊,来给我讲讲,图片三级缓存,好高大尚的名字,听着挺厉...

  • 自定义图片缓存框架(三级缓存)

    一、(三级缓存) GitHub: RxImageLoader

  • Android 图片三级缓存 _内存缓存LruCache

    本文目标 理解Android图片三级缓存机制,并重点理解内存缓存 (LruCache算法) 图片的缓存技术分为三级...

  • SDWebImage缓存原理

    SDWebImage是一个功能很强大的缓存网络图片的框架。框架实现了异步加载网络图片、自动缓存图片数据等功能。以U...

  • Android基础(41)图片加载框架

    1)图片库对比2)Glide源码解析3)图片框架缓存实现4)LRUCache原理。LruCache默认缓存大小5)...

  • Fresco的缓存机制

    Fresco的图片获取及缓存由ImagePipeline模块实现,具体见下图: 三级缓存 1.Bitmap缓存 B...

  • 解读SDWebImage

    SDWebImage是一个功能强大的图片缓存框架,可以实现网络图片加载和缓存。那么SDWebImage的缓存机制是...

  • Glide图片加载框架

    1、Glide图片加载框架三级缓存活动缓存、内存缓存、磁盘缓存第一次:首先去查找顺序活动缓存——》内存缓村———》...

  • 多线程03

    SDWebImage框架详解 下载图片并显示: 下载图片/显示图片/内存缓存/磁盘缓存 下载图片\内存缓存\磁盘缓...

网友评论

    本文标题:自定义图片框架实现图片三级缓存

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