最近,总算有时间去做些平时喜欢而没空去做的事情。一直觉得项目中使用的Image Loader适用性不强,昨晚在github随便逛逛,发现一个开源项目Android-Universal-Image-Loader十分火热。代码并不十分复杂,却写的不错,决定记录和分享一下。

  Android-Universal-Image-Loader是一个针对图片加载、缓存的开源项目。github: https://github.com/nostra13/Android-Universal-Image-Loader

  @author nostra13频繁的更新记录来看,Android-Universal-Image-Loader的后期维护比较好。从Applications using Universal Image Loader来看,还是相当强大的,如:EyeEm Camera, UPnP/DLNA Browser, Facebook Photo/Album,淘宝天猫,京东商城等。到底有何种魅力让大家纷至沓来?

  

01. 项目包结构

图 1

  去掉无实际意义的包名:com.nostra13.universalimageloader,一切变显得比较清楚了。

  (1)cache主要是磁盘缓存及内存缓存预定的接口和常规实现类,包含的算法较多(并不复杂),如FIFO算法、LRU算法等。

  (2)core明显是整个Image Loader的核心包,图片下载、适配显示,并向上层应用提供各种接口,默认模板,还包括很多关键枚举类、工具类。

  (3)utils比较简单些,常规工具类,如ImageSizeUtils、StorageUtils等。


02. 文件存储策略

  针对手机,流量、电量及体验的要求,不断向网络发起重复的请求,都是不恰当的。常规存储方式包括:文件存储、数据库存储、SharedPreference、云存储(网络存储)及保存至内存等几种手段。显然,需要在本地进行持久化,就图片而已,以文件的方式保存无疑是最好的选择。至此,完成了云端-->本地的持久化,但是如果简单的每次都去”本地存储“请求,请求不到再到云端请求,无疑无法解决频繁请求图片所需要的速度和体验。请记住,用户对图片的渴望程序是相当高,试想一下,ListView上下滚动时,每次直接到”本地存储“请求时,整个界面多处在Loading,显然体验效果比较一般。因此,图片一般都会存储至内存中。从内存中读取图片的速度比本地加载的快得多,体验也会更好。这就是平时通常说的三步:内存-->本地文件-->云端。

  在Android-Universal-Image-Loader,单纯从包结构上就可以看出,cache.disc是负责硬盘存储,而cache.memory是负责内存存储的(ps.具体存储实现方式还是在core包内)。而网络请求应该在core.download中,core包在Part3.2会深入分析。

  事实上,我们似乎忽略了一些东西,如果我发起请求的是本地图片呢?如果是R.drawable.*的图片呢?优点1:Android-Universal-Image-Loader设计得比较细致,在ImageDownloader接口枚举类Scheme,明确指出所支持的请求类型:

  1. HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");

  021. 存储空间、时间、数量策略

    在cache.disc下,有着比较完整的存储策略,根据预先指定的空间大小,使用频率(生命周期),文件个数的约束条件,都有着对应的实现策略。最基础的接口DiscCacheAware和抽象类BaseDiscCache。简单类图如下:

图2

  从类图中,明显可以看出,整个disc存储的实现方式有四种:文件总数,文件总大小(或目录),生命周期,无限制(即空间、时间维度的限制)。分析重点,是接口/抽象类。

  1. public interface DiscCacheAware {
  2. /**
  3. * This method must not to save file on file system in fact. It is called after image was cached in cache directory
  4. * and it was decoded to bitmap in memory. Such order is required to prevent possible deletion of file after it was
  5. * cached on disc and before it was tried to decode to bitmap.
  6. */
  7. void put(String key, File file);
  8.  
  9. /**
  10. * Returns {@linkplain File file object} appropriate incoming key.<br />
  11. * <b>NOTE:</b> Must <b>not to return</b> a null. Method must return specific {@linkplain File file object} for
  12. * incoming key whether file exists or not.
  13. */
  14. File get(String key);
  15.  
  16. /** Clears cache directory */
  17. void clear();
  18. }

  具体看英文注释,比较好理解,此处就不翻译了。

  

  022. 图片文件的命名

  如果你是心思细腻的用户,在图片浏览器中是否会经常看到一些小图片甚至于广告图片。在一定程序上,本人挺鄙视这些图片来源的应用。为什么把一些临时下载的图片,并无实际意义的图片,占据如此重要的位置。对此,我只想说:我会直接删掉这个目录,如果知道是哪个应用的话,估计我也会卸载。我不禁猜想:如果所有应用缓存的图片,都是可以被媒体库扫描出来的话,这是什么境况呢?慢,肯定的。

  因此,图片保存时,以什么样的文件名进行存储是相当重要的。这个必须要根据需求进行定制生成的策略。举三个例子:

  (1)如果要做一个“云相册”,保存图片后,当然希望能够使用“美图秀秀”、“相机360”等进行编辑。

  (2)如果要做一个“淘宝客户端”,所有浏览过的商品图片都存储,并可被其它图片浏览器感知,这似乎不恰当吧。

  (3)如果要做一个“私密相册”,保存本地的图片,自然不希望别人去感知。

  结论:用户有意识去保存的图片,应该可被感知;用户无意识缓存的图片,不应该被“外界”感知,但也有例外如第3种。当然,第三种权限的控制也是可行的。

  Android-Universal-Image-Loader在保存图片时,需要一个FileNameGenerator(译:文件名生产者)。抽象类BaseDiscCache中存在两个构造器:

  1. public BaseDiscCache(File cacheDir) {
  2. this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator());
  3. }
  4.  
  5. public BaseDiscCache(File cacheDir, FileNameGenerator fileNameGenerator) {
  6. if (cacheDir == null) {
  7. throw new IllegalArgumentException(String.format(ERROR_ARG_NULL, "cacheDir"));
  8. }
  9. if (fileNameGenerator == null) {
  10. throw new IllegalArgumentException(String.format(ERROR_ARG_NULL, "fileNameGenerator"));
  11. }
  12.  
  13. this.cacheDir = cacheDir;
  14. this.fileNameGenerator = fileNameGenerator;
  15. }

  DefaultConfigurationFactory提供默认文件名生产者为HashCodeFileNameGenerator,默认实现比较简单:返回字符串的hashCode。

  核心类图:

图3

  Md5FileNameGenerator相关原理可查询MD5加密算法,JDK自带类java.security.MessageDigest。


03. 内存存储

  这部分涉及的知识面比较多,算法、链表、堆栈、队列、泛型等。Android-Universal-Image-Loader在“开闭原则”上做得很好,接口简洁、实用,基于接口,都可以自行扩展功能。

  直接看下接口MemoryCacheAware的定义:

  1. public interface MemoryCacheAware<K, V> {
  2. /**
  3. * Puts value into cache by key
  4. *
  5. * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into
  6. * cache
  7. */
  8. boolean put(K key, V value);
  9.  
  10. /** Returns value by key. If there is no value for key then null will be returned. */
  11. V get(K key);
  12.  
  13. /** Removes item by key */
  14. void remove(K key);
  15.  
  16. /** Returns all keys of cache */
  17. Collection<K> keys();
  18.  
  19. /** Remove all items from cache */
  20. void clear();
  21. }

  如果不熟悉泛型的话,建议复习一下,或者直接把MemoryCacheAware<K, V>当MemoryCacheAware<String, Bitmap>看,会方便些。

  核心类图:

图4

  大体结构上以文件存储比较相近,关键性的区别在于BaseMemoryCache子类使用的都是WeakReference(弱引用),FuzzyKeyMemoryCache、LimitedAgeMemoryCache、LruMemoryCache使用的是强引用。本节对内存存储方式,比较关键的概念:强引用、软引用、弱引用及虚引用。

  回顾:

  (1)StrongReference(强引用)

    强引用就是平时经常使用的,如常规new Object()。如果一个对象具有强引用,那垃圾回收器绝不会回收。内存不足,甚至出现OOM时,也不会随意回收强引用的对象。

  (2)SoftReference(软引用)

    在内存空间足够,垃圾回收器不会回收它;如果内存空间不足,垃圾回收器就会回收软引用的对象。

  (3)WeakReference(弱引用)

    弱引用相对软引用,具有更短暂的生命周期。常规的GC,只要被扫描到,都会直接被回收。

  (4)PhantomReference(虚引用)

    虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。没用过。。。

  选其一FIFOLimitedMemoryCache进行分析,构造时,必须要指定大小(单位:字节)。当设置的大小超过16MB(Android默认分配的大小好像也是这个)时,会有警告。

  1. public LimitedMemoryCache(int sizeLimit) {
  2. this.sizeLimit = sizeLimit;
  3. cacheSize = new AtomicInteger();
  4. if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
  5. L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB);
  6. }
  7. }

而FIFOLimitedMemoryCache的实现也相对简单,只要你清楚FIFO的基本概念:先进先出。

  1. public class FIFOLimitedMemoryCache extends LimitedMemoryCache<String, Bitmap> {
  2.  
  3. private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>());
  4.  
  5. public FIFOLimitedMemoryCache(int sizeLimit) {
  6. super(sizeLimit);
  7. }
  8.  
  9. @Override
  10. public boolean put(String key, Bitmap value) {
  11. if (super.put(key, value)) {
  12. queue.add(value);
  13. return true;
  14. } else {
  15. return false;
  16. }
  17. }
  18.  
  19. @Override
  20. public void remove(String key) {
  21. Bitmap value = super.get(key);
  22. if (value != null) {
  23. queue.remove(value);
  24. }
  25. super.remove(key);
  26. }
  27.  
  28. @Override
  29. public void clear() {
  30. queue.clear();
  31. super.clear();
  32. }
  33.  
  34. @Override
  35. protected int getSize(Bitmap value) {
  36. return value.getRowBytes() * value.getHeight();
  37. }
  38.  
  39. @Override
  40. protected Bitmap removeNext() {
  41. return queue.remove(0);
  42. }
  43.  
  44. @Override
  45. protected Reference<Bitmap> createReference(Bitmap value) {
  46. return new WeakReference<Bitmap>(value);
  47. }
  48. }

  在removeNext()方法中,可以看到每次都是移除队列中的第一个对象。

  


  Part3.1就此结束。

  原来,用心写一篇文章,还是相当的困难。前前后后,走读,分析,类图,补充部分遗漏的知识点,足足用了半天的时间。不过,收获良多。

  Part3.2主要是对core包中的下载设计技巧进行分析、研究,core.assist、core.display只会简单过一下。

  Part3.3重在如何在实际项目中运用Android-Universal-Image-Loader,如何自行扩展部分功能。

  估计,Part3.2/3.3会晚些日子才会更新,后面会对自己做的一些项目进行一定的总结。希望大家多多支持。

  有任何问题,请邮件至:Osmondy@sina.com

[原创] 浅谈开源项目Android-Universal-Image-Loader(Part 3.1)的更多相关文章

  1. 浅谈开源项目Android-Universal-Image-Loader(Part 3.1)

    本文转载于:http://www.cnblogs.com/osmondy/p/3266023.html 浅谈开源项目Android-Universal-Image-Loader(Part 3.1) 最 ...

  2. [原创]浅谈Web UI自动化测试

    [原创]浅谈Web UI自动化测试 Web UI自动化测试相信大家都不陌生,今天来谈谈这个,我最早接触自动化测试时大约是在2004年,2006年当时在腾讯财付通算是开始正式接触自动化测试,之所以是正式 ...

  3. [原创]浅谈移动互联网App兼容性测试

    [原创]浅谈移动互联网App兼容性测试 今天要谈的话题,估计各位测试都有感受,移动互联网App兼容性测试,我们到底测试覆盖如何去挑选机型?具体移动App兼容性测试如何开展?是不是应引进像testin这 ...

  4. [原创]浅谈移动App安全测试

    [原创]浅谈移动App安全测试 移动互联网很火,就像当年互联网兴起一样,这几天和朋友在沟通交流,谈到一个话题,你们做金融App钱放在你们哪边安全不?会不会你们做的移动App不安全,让人盗了里面的资金, ...

  5. [原创]浅谈H5页面性能测试

    [原创]浅谈H5页面性能测试 H5页面我想各位都不陌生,随着移动互联网兴起,不管是App,还是H5都火起来了,最突出的2个表现是ios/android/前端等工程师薪水大涨,尤其是资深前端工程师40W ...

  6. [原创]浅谈H5页面测试介绍

    [原创]浅谈H5页面测试介绍 目前移动互联网非常火热,除了各种App,H5也是非常热,由于H5跨平台,且版本更新容易,做为引流或获客是非常好的一种简单低成本平台:今天来谈谈H5页面测试都要测试什么? ...

  7. 浅谈 MVP in Android(转)

    我自己写的demo:https://pan.baidu.com/s/1dFImVYD 一.概述 对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让 ...

  8. [原创]浅谈如何使用gcc开发NT核心驱动程序

    原文链接:[原创]浅谈如何使用gcc开发NT核心驱动程序 一谈到在 Win NT 下开发核心驱动程序,可能不少人首先都会想到微软“正统”的VC来.诚然,用VC 配合 WINDDK 的确工作的不错,但或 ...

  9. [原创]浅谈IT人如何做理财规划

    [原创]浅谈IT人如何做理财规划 鱼哥博客上多数写的是技术和管理相关,但很少有理财等话题,今天抽空来谈谈IT人如何做理财规划,如果要想学习理财,我想很有名的“标准普尔家庭资产象限图”上值得每个学习和理 ...

随机推荐

  1. 剑指offer42:翻转单词顺序 VS 左旋转字符串(更高效、简便的解法)

    题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变.为简单起见,标点符号和普通字母一样处理.例如输入字符串"I am a student." ,则输出" ...

  2. 【08】node 之 fs文件

    var fs = require("fs");//fs 系统文件模块,对文件进行操作.Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有 ...

  3. EF4.2预览版出来了

    原文发布时间为:2011-09-21 -- 来源于本人的百度文章 [由搬家工具导入] http://blogs.msdn.com/b/adonet/archive/2011/08/22/ef-4-2- ...

  4. Servlet乱码解决

    后端收前端 1.post乱码 可以通过 request.setCharacterEncoding("utf-8");  这样在后端可以接收中文 2.get乱码(手动解决) 可以通过 ...

  5. Python学习杂记_7_文件操作

    文件操作 Python3用open()方法打开文件并返回文件句柄,有了文件句柄就可以对文件进行各种操作了. 打开文件: open(“文件名” , 打开方式)            如: f=open( ...

  6. python笔记5:函数式编程

    5 函数式编程(即高阶函数,将函数作为参数传入) map(): map()函数接收两个参数,一个是传入函数,一个是Iterable,map将传入函数依次作用到序列的每个元素,并把结果作为新的Itera ...

  7. Unix进程和线程管理及其异同

    Unix进程和线程管理及其异同 一,进程 1,什么是进程 在最初的单处理器系统中,系统中的多道程序按照一定规则切换而实现多任务处理,后来发现多个程序并发导致系统资源被共享,为了描述和管理程序对共享资源 ...

  8. 语义分割丨PSPNet源码解析「训练阶段」

    引言 之前一段时间在参与语义分割的项目,最近有时间了,正好把这段时间的所学总结一下. 在代码上,语义分割的框架会比目标检测简单很多,但其中也涉及了很多细节.在这篇文章中,我以PSPNet为例,解读一下 ...

  9. jvm 简单描述

    java零基础入门-面向对象篇(一) 基础类型和引用类型 友情提示:本章开始可能会有部分较深入的内容,不说又不行,说了又很难解释清楚,因为里面的技术细节实在太多太复杂,所以我会屏蔽部分技术细节,只展示 ...

  10. luogu P1579 哥德巴赫猜想(升级版)

    题目描述 一个等差数列是一个能表示成a, a+b, a+2b,..., a+nb (n=0,1,2,3,...)的数列. 在这个问题中a是一个非负的整数,b是正整数.写一个程序来找出在双平方数集合(双 ...