关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android ImageLoader 框架之图片缓存

    Android ImageLoader 框架之图片缓存

    2015-04-05 00:00:00 出处:gao_chun的专栏
    分享

    在Android ImageLoader框架系列博文中,我们从基本架构到具体实现已经更新了大部分的内容。今天,我们来讲最后一个关键点,即图片的缓存。为了用户体验,通常情况下我们都会将已经下载的图片缓存起来,一般来说内存和本地都会有图片缓存。那既然是框架,必然需要有很好的定制性,这让我们又自然而然的想到了抽象。下面我们就一起来看看缓存的实现吧。

    缓存接口

    在Android ImageLoader框架之图片加载与加载策略我们聊到了Loader,然后阐述了AbsLoader的基本逻辑,其中就有图片缓存。因此AbsLoader中必然含有缓存对象的引用。我们看看相关代码:

    /**
     * @author mrsimple
     */
    public abstract class AbsLoader implements Loader {
    
        /**
         * 图片缓存
         */
        private static BitmapCache mCache = SimpleImageLoader.getInstance().getConfig().bitmapCache;
    
        // 代码省略
    }

    AbsLoader中定义了一个static的BitmapCache对象,这个就是图片缓存对象。那为什么是static呢?因为不管Loader有多少个,缓存对象都应该是共享的,也就是缓存只有一份。说了那么多,那我们先来了解一下BitmapCache吧。

    public interface BitmapCache {
    
        public Bitmap get(BitmapRequest key);
    
        public void put(BitmapRequest key, Bitmap value);
    
        public void remove(BitmapRequest key);
    
    }

    BitmapCache很简单,只声明了获取、添加、移除三个方法来操作图片缓存。这里有依赖了一个BitmapRequest类,这个类代表了一个图片加载请求,该类中有该请求对应的Imageview、图片uri、显示Config等属性。在缓存这块我们主要要使用图片的uri来检索缓存中是否含有该图片,缓存以图片的uri为key,Bitmap为value来关联存储。另外需要BitmapRequest的ImageView宽度和高度,以此来按尺寸加载图片。

    定义BitmapCache接口还是为了可扩展性,面向接口的编程的理念又再一次的浮现在你面前。假如是你,你会作何设计呢?自己写代码来练习一下吧,看看自己作何考虑,假如实现,这样你才会从中有更深的领悟。

    内存缓存

    既然是框架,那就需要接受用户各种各样的需求。但通常来说框架会有一些默认的实现,对于图片缓存来说内存缓存就其中的一个默认实现,它会将已经加载的图片缓存到内存中,大大地提升图片重复加载的速度。内存缓存我们的策略是使用LRU算法,直接使用了support.v4中的LruCache类,相关代码如下。

    /**
     * 图片的内存缓存,key为图片的uri,值为图片本身
     * 
     * @author mrsimple
     */
    public class MemoryCache implements BitmapCache {
    
        private LruCache<String, Bitmap> mMemeryCache;
    
        public MemoryCache() {
    
            // 计算可使用的最大内存
            final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    
            // 取4分之一的可用内存作为缓存
            final int cacheSize = maxMemory / 4;
            mMemeryCache = new LruCache<String, Bitmap>(cacheSize) {
    
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
                }
            };
    
        }
    
        @Override
        public Bitmap get(BitmapRequest key) {
            return mMemeryCache.get(key.imageUri);
        }
    
        @Override
        public void put(BitmapRequest key, Bitmap value) {
            mMemeryCache.put(key.imageUri, value);
        }
    
        @Override
        public void remove(BitmapRequest key) {
            mMemeryCache.remove(key.imageUri);
        }
    
    }

    就是简单的实现了BitmapCache接口,然后内部使用LruCache类实现内存缓存。比较简单,就不做说明了。

    sd卡缓存

    对于图片缓存,内存缓存是不够的,更多的需要是将图片缓存到sd卡中,这样用户在下次进入app时可以直接从本地加载图片,避免重复地从网络上读取图片数据,即耗流量,用户体验又不好。sd卡缓存我们使用了Jake Wharton的DiskLruCache类,我们的sd卡缓存类为DiskCache,代码如下 :

    public class DiskCache implements BitmapCache {
    
        /**
         * 1MB
         */
        private static final int MB = 1024 * 1024;
    
        /**
         * cache dir
         */
        private static final String IMAGE_DISK_CACHE = "bitmap";
        /**
         * Disk LRU Cache
         */
        private DiskLruCache mDiskLruCache;
        /**
         * Disk Cache Instance
         */
        private static DiskCache mDiskCache;
    
        /**
         * @param context
         */
        private DiskCache(Context context) {
            initDiskCache(context);
        }
    
        public static DiskCache getDiskCache(Context context) {
            if (mDiskCache == null) {
                synchronized (DiskCache.class) {
                    if (mDiskCache == null) {
                        mDiskCache = new DiskCache(context);
                    }
                }
    
            }
            return mDiskCache;
        }
    
        /**
         * 初始化sdcard缓存
         */
        private void initDiskCache(Context context) {
            try {
                File cacheDir = getDiskCacheDir(context, IMAGE_DISK_CACHE);
                if (!cacheDir.exists()) {
                    cacheDir.mkdirs();
                }
                mDiskLruCache = DiskLruCache
                        .open(cacheDir, getAppVersion(context), 1, 50 * MB);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取sd缓存的目录,假如挂载了sd卡则使用sd卡缓存,否则使用应用的缓存目录。
         * @param context Context
         * @param uniqueName 缓存目录名,比如bitmap
         * @return
         */
        public File getDiskCacheDir(Context context, String uniqueName) {
            String cachePath;
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                Log.d("", "### context : " + context + ", dir = " + context.getExternalCacheDir());
                cachePath = context.getExternalCacheDir().getPath();
            } else {
                cachePath = context.getCacheDir().getPath();
            }
            return new File(cachePath + File.separator + uniqueName);
        }
    
            @Override
        public synchronized Bitmap get(final BitmapRequest bean) {
            // 图片解析器
            BitmapDecoder decoder = new BitmapDecoder() {
    
                @Override
                public Bitmap decodeBitmapWithOption(Options options) {
                    final InputStream inputStream = getInputStream(bean.imageUriMd5);
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null,
                            options);
                    IOUtil.closeQuietly(inputStream);
                    return bitmap;
                }
            };
    
            return decoder.decodeBitmap(bean.getImageViewWidth(),
                    bean.getImageViewHeight());
        }
    
        private InputStream getInputStream(String md5) {
            Snapshot snapshot;
            try {
                snapshot = mDiskLruCache.get(md5);
                if (snapshot != null) {
                    return snapshot.getInputStream(0);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public void put(BitmapRequest key, Bitmap value) {
            // 代码省略 
        }
    
        public void remove(BitmapRequest key) {
            // 代码省略
        }
    
    }

    代码比较简单,也就是实现BitmapCache,然后包装一下DiskLruCache类的方法实现图片文件的增加、删除、获取方法。这里给大家介绍一个类,是我为了简化图片按ImageView尺寸加载的辅助类,即BitmapDecoder。

    BitmapDecoder

    BitmapDecoder是一个按ImageView尺寸加载图片的辅助类,一般我加载图片的过程是这样的:
    1. 创建BitmapFactory.Options options,设置options.inJustDecodeBounds = true,使得只解析图片尺寸等信息;
    2. 根据ImageView的尺寸来检查是否需要缩小要加载的图片以及计算缩放比例;
    3. 设置options.inJustDecodeBounds = false,然后按照options设置的缩小比例来加载图片.

    BitmapDecoder类使用decodeBitmap方法封装了这个过程 ( 模板方法噢 ),用户只需要实现一个子类,并且覆写BitmapDecoder的decodeBitmapWithOption实现图片加载即可完成这个过程(参考DiskCache中的get方法)。代码如下 :

    /**
     * 封装先加载图片bound,计算出inSmallSize之后再加载图片的逻辑操作
     * 
     * @author mrsimple
     */
    public abstract class BitmapDecoder {
    
        /**
         * @param options
         * @return
         */
        public abstract Bitmap decodeBitmapWithOption(Options options);
    
        /**
         * @param width 图片的目标宽度
         * @param height 图片的目标高度
         * @return
         */
        public Bitmap decodeBitmap(int width, int height) {
            // 假如请求原图,则直接加载原图
            if (width <= 0 || height <= 0) {
                return decodeBitmapWithOption(null);
            }
    
            // 1、获取只加载Bitmap宽高等数据的Option, 即设置options.inJustDecodeBounds = true;
            BitmapFactory.Options options = getJustDecodeBoundsOptions();
            // 2、通过options加载bitmap,此时返回的bitmap为空,数据将存储在options中
            decodeBitmapWithOption(options);
            // 3、计算缩放比例, 并且将options.inJustDecodeBounds设置为false;
            calculateInSmall(options, width, height);
            // 4、通过options设置的缩放比例加载图片
            return decodeBitmapWithOption(options);
        }
    
        /**
         * 获取BitmapFactory.Options,设置为只解析图片边界信息
         */
        private Options getJustDecodeBoundsOptions() {
            //
            BitmapFactory.Options options = new BitmapFactory.Options();
            // 设置为true,表示解析Bitmap对象,该对象不占内存
            options.inJustDecodeBounds = true;
            return options;
        }
    
        protected void calculateInSmall(Options options, int width, int height) {
            // 设置缩放比例
            options.inSampleSize = computeInSmallSize(options, width, height);
            // 图片质量
            options.inPreferredConfig = Config.RGB_565;
            // 设置为false,解析Bitmap对象加入到内存中
            options.inJustDecodeBounds = false;
            options.inPurgeable = true;
            options.inInputShareable = true;
        }
    
        private int computeInSmallSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
            // Raw height and width of image
            final int height = options.outHeight;
            final int width = options.outWidth;
            int inSampleSize = 1;
    
            if (height > reqHeight || width > reqWidth) {
                // Calculate ratios of height and width to requested height and
                // width
                final int heightRatio = Math.round((float) height / (float) reqHeight);
                final int widthRatio = Math.round((float) width / (float) reqWidth);
    
                inSampleSize = heightRatio < widthRatio   heightRatio : widthRatio;
                final float totalPixels = width * height;
    
                // Anything more than 2x the requested pixels we'll sample down
                // further
                final float totalReqPixelsCap = reqWidth * reqHeight * 2;
    
                while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                    inSampleSize++;
                }
            }
            return inSampleSize;
        }
    
    }

    在decodeBitmap中,我们首先创建BitmapFactory.Options对象,并且设置options.inJustDecodeBounds = true,然后第一次调用decodeBitmapWithOption(options),使得只解析图片尺寸等信息;然后调用calculateInSmall方法,该方法会调用computeInSmallSize来根据ImageView的尺寸来检查是否需要缩小要加载的图片以及计算缩放比例,在calculateInSmall方法的最后将 options.inJustDecodeBounds = false,使得下次再次decodeBitmapWithOption(options)时会加载图片;那最后一步必然就是调用decodeBitmapWithOption(options)啦,这样图片就会按照按照options设置的缩小比例来加载图片了。

    我们使用这个辅助类封装了这个麻烦、重复的过程,在一定程度上简化了代码,也使得代码的可复用性更高,也是模板方法模式的一个较好的示例。

    二级缓存

    有了内存和sd卡缓存,其实这还不够。我们的需求很可能就是这个缓存会同时有内存和sd卡缓存,这样上述两种缓存的优点我们就会具备,这里我们把它称为二级缓存。看看代码吧,也很简单。

    /**
     * 综合缓存,内存和sd卡双缓存
     * 
     * @author mrsimple
     */
    public class DoubleCache implements BitmapCache {
        DiskCache mDiskCache;
        MemoryCache mMemoryCache = new MemoryCache();
    
        public DoubleCache(Context context) {
            mDiskCache = DiskCache.getDiskCache(context);
        }
    
        @Override
        public Bitmap get(BitmapRequest key) {
            Bitmap value = mMemoryCache.get(key);
            if (value == null) {
                value = mDiskCache.get(key);
                saveBitmapIntoMemory(key, value);
            }
            return value;
        }
    
        private void saveBitmapIntoMemory(BitmapRequest key, Bitmap bitmap) {
            // 假如Value从disk中读取,那么存入内存缓存
            if (bitmap != null) {
                mMemoryCache.put(key, bitmap);
            }
        }
    
        @Override
        public void put(BitmapRequest key, Bitmap value) {
            mDiskCache.put(key, value);
            mMemoryCache.put(key, value);
        }
    
        @Override
        public void remove(BitmapRequest key) {
            mDiskCache.remove(key);
            mMemoryCache.remove(key);
        }
    
    }

    其实就是封装了内存缓存和sd卡缓存的相关操作嘛~ 那我就不要再费口舌了

    自定义缓存

    缓存是有很多实现策略的,既然大家要可扩展性,那就要允许用户注入自己的缓存实现。只要你实现BitmapCache,就可以将它通过ImageLoaderConfig注入到ImageLoader内部。

        private void initImageLoader() {
            ImageLoaderConfig config = new ImageLoaderConfig()
                    .setLoadingPlaceholder(R.drawable.loading)
                    .setNotFoundPlaceholder(R.drawable.not_found)
                    .setCache(new MyCache())
            // 初始化
            SimpleImageLoader.getInstance().init(config);
        }

    MyCache.java

    // 自定义缓存实现类
    public class MyCache implements BitmapCache {
    
        // 代码
    
        @Override
        public Bitmap get(BitmapRequest key) {
            // 你的代码
        }
    
        @Override
        public void put(BitmapRequest key, Bitmap value) {
            // 你的代码  
        }
    
        @Override
        public void remove(BitmapRequest key) {
            // 你的代码
        }
    
    }

    Github地址

    SimpleImageLoader。

    总结

    ImageLoader系列到这里就算结束了,我们从基本架构、具体实现、设计上面详细的阐述了一个简单、可扩展性较好的ImageLoader实现过程,希望大家看完这个系列之后能够自己去实现一遍,这样你会发现一些具体的问题,领悟能够更加的深刻。假如你在看这系列博客的过程中,真的能够从中体会到面向对象的基本原则、设计思考等东西,而不是说”我擦,我又找到了一个可以copy来用的ImageLoader”,那我就觉得我做的这些分享到达目的了。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    微软已修复windows 7/windows 8.1媒体中心严重漏洞 用户可下载安

    卸载MySQL数据库,用rpm如何实现

    windows 7中使用网上银行或支付宝支付时总是打不开支付页面

    一致性哈希算法原理设计

    MySQL数字类型中的三种常用种类

    如何解决SQL Server中传入select语句in范围参数

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。