最近,總算有時間去作些平時喜歡而沒空去作的事情。一直以爲項目中使用的Image Loader適用性不強,昨晚在github隨便逛逛,發現一個開源項目Android-Universal-Image-Loader十分火熱。代碼並不十分複雜,卻寫的不錯,決定記錄和分享一下。 java
Android-Universal-Image-Loader是一個針對圖片加載、緩存的開源項目。github: https://github.com/nostra13/Android-Universal-Image-Loader git
@author nostra13頻繁的更新記錄來看,Android-Universal-Image-Loader的後期維護比較好。從Applications using Universal Image Loader來看,仍是至關強大的,如:EyeEm Camera, UPnP/DLNA Browser, Facebook Photo/Album,淘寶天貓,京東商城等。到底有何種魅力讓你們紛至沓來? github
01. 項目包結構 算法
圖 1 數據庫
去掉無實際意義的包名:com.nostra13.universalimageloader,一切變顯得比較清楚了。 瀏覽器
(1)cache主要是磁盤緩存及內存緩存預約的接口和常規實現類,包含的算法較多(並不複雜),如FIFO算法、LRU算法等。 緩存
(2)core明顯是整個Image Loader的核心包,圖片下載、適配顯示,並向上層應用提供各類接口,默認模板,還包括不少關鍵枚舉類、工具類。 網絡
(3)utils比較簡單些,常規工具類,如ImageSizeUtils、StorageUtils等。 app
02. 文件存儲策略 ide
針對手機,流量、電量及體驗的要求,不斷向網絡發起重複的請求,都是不恰當的。常規存儲方式包括:文件存儲、數據庫存儲、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,明確指出所支持的請求類型:
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"), ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");
021. 存儲空間、時間、數量策略
在cache.disc下,有着比較完整的存儲策略,根據預先指定的空間大小,使用頻率(生命週期),文件個數的約束條件,都有着對應的實現策略。最基礎的接口DiscCacheAware和抽象類BaseDiscCache。簡單類圖以下:
圖2
從類圖中,明顯能夠看出,整個disc存儲的實現方式有四種:文件總數,文件總大小(或目錄),生命週期,無限制(即空間、時間維度的限制)。分析重點,是接口/抽象類。
public interface DiscCacheAware { /** * This method must not to save file on file system in fact. It is called after image was cached in cache directory * and it was decoded to bitmap in memory. Such order is required to prevent possible deletion of file after it was * cached on disc and before it was tried to decode to bitmap. */ void put(String key, File file); /** * Returns {@linkplain File file object} appropriate incoming key.<br /> * <b>NOTE:</b> Must <b>not to return</b> a null. Method must return specific {@linkplain File file object} for * incoming key whether file exists or not. */ File get(String key); /** Clears cache directory */ void clear(); }
具體看英文註釋,比較好理解,此處就不翻譯了。
022. 圖片文件的命名
若是你是心思細膩的用戶,在圖片瀏覽器中是否會常常看到一些小圖片甚至於廣告圖片。在必定程序上,本人挺鄙視這些圖片來源的應用。爲何把一些臨時下載的圖片,並沒有實際意義的圖片,佔據如此重要的位置。對此,我只想說:我會直接刪掉這個目錄,若是知道是哪一個應用的話,估計我也會卸載。我不由猜測:若是全部應用緩存的圖片,都是能夠被媒體庫掃描出來的話,這是什麼境況呢?慢,確定的。
所以,圖片保存時,以什麼樣的文件名進行存儲是至關重要的。這個必需要根據需求進行定製生成的策略。舉三個例子:
(1)若是要作一個「雲相冊」,保存圖片後,固然但願可以使用「美圖秀秀」、「相機360」等進行編輯。
(2)若是要作一個「淘寶客戶端」,全部瀏覽過的商品圖片都存儲,並可被其它圖片瀏覽器感知,這彷佛不恰當吧。
(3)若是要作一個「私密相冊」,保存本地的圖片,天然不但願別人去感知。
結論:用戶有意識去保存的圖片,應該可被感知;用戶無心識緩存的圖片,不該該被「外界」感知,但也有例外如第3種。固然,第三種權限的控制也是可行的。
Android-Universal-Image-Loader在保存圖片時,須要一個FileNameGenerator(譯:文件名生產者)。抽象類BaseDiscCache中存在兩個構造器:
public BaseDiscCache(File cacheDir) { this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator()); } public BaseDiscCache(File cacheDir, FileNameGenerator fileNameGenerator) { if (cacheDir == null) { throw new IllegalArgumentException(String.format(ERROR_ARG_NULL, "cacheDir")); } if (fileNameGenerator == null) { throw new IllegalArgumentException(String.format(ERROR_ARG_NULL, "fileNameGenerator")); } this.cacheDir = cacheDir; this.fileNameGenerator = fileNameGenerator; }
DefaultConfigurationFactory提供默認文件名生產者爲HashCodeFileNameGenerator,默認實現比較簡單:返回字符串的hashCode。
核心類圖:
圖3
Md5FileNameGenerator相關原理可查詢MD5加密算法,JDK自帶類java.security.MessageDigest。
03. 內存存儲
這部分涉及的知識面比較多,算法、鏈表、堆棧、隊列、泛型等。Android-Universal-Image-Loader在「開閉原則」上作得很好,接口簡潔、實用,基於接口,均可以自行擴展功能。
直接看下接口MemoryCacheAware的定義:
public interface MemoryCacheAware<K, V> { /** * Puts value into cache by key * * @return <b>true</b> - if value was put into cache successfully, <b>false</b> - if value was <b>not</b> put into * cache */ boolean put(K key, V value); /** Returns value by key. If there is no value for key then null will be returned. */ V get(K key); /** Removes item by key */ void remove(K key); /** Returns all keys of cache */ Collection<K> keys(); /** Remove all items from cache */ void clear(); }
若是不熟悉泛型的話,建議複習一下,或者直接把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默認分配的大小好像也是這個)時,會有警告。
public LimitedMemoryCache(int sizeLimit) { this.sizeLimit = sizeLimit; cacheSize = new AtomicInteger(); if (sizeLimit > MAX_NORMAL_CACHE_SIZE) { L.w("You set too large memory cache size (more than %1$d Mb)", MAX_NORMAL_CACHE_SIZE_IN_MB); } }
而FIFOLimitedMemoryCache的實現也相對簡單,只要你清楚FIFO的基本概念:先進先出。
public class FIFOLimitedMemoryCache extends LimitedMemoryCache<String, Bitmap> { private final List<Bitmap> queue = Collections.synchronizedList(new LinkedList<Bitmap>()); public FIFOLimitedMemoryCache(int sizeLimit) { super(sizeLimit); } @Override public boolean put(String key, Bitmap value) { if (super.put(key, value)) { queue.add(value); return true; } else { return false; } } @Override public void remove(String key) { Bitmap value = super.get(key); if (value != null) { queue.remove(value); } super.remove(key); } @Override public void clear() { queue.clear(); super.clear(); } @Override protected int getSize(Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override protected Bitmap removeNext() { return queue.remove(0); } @Override protected Reference<Bitmap> createReference(Bitmap value) { return new WeakReference<Bitmap>(value); } }
在removeNext()方法中,能夠看到每次都是移除隊列中的第一個對象。
Part3.1就此結束。
原來,用心寫一篇文章,仍是至關的困難。前先後後,走讀,分析,類圖,補充部分遺漏的知識點,足足用了半天的時間。不過,收穫良多。
Part3.2主要是對core包中的下載設計技巧進行分析、研究,core.assist、core.display只會簡單過一下。
Part3.3重在如何在實際項目中運用Android-Universal-Image-Loader,如何自行擴展部分功能。
估計,Part3.2/3.3會晚些日子纔會更新,後面會對本身作的一些項目進行必定的總結。但願你們多多支持。
有任何問題,請郵件至:Osmondy@sina.com