1. 程式人生 > >ImageLoader開源專案學習

ImageLoader開源專案學習

2015-6-29

自打畢業入行就開始了android開發之旅,經歷了兩三個比較大型的專案。雖然說是大型專案,其實沒有多大技術含量,只不過是專案的週期比較長久罷了。在做專案的過程中遇到過幾個比較棘手的問題,記憶體管理就是其中之一。為此,也有牛人專門分享了幾個開源框架供大家使用。其中比較流行的就有ImageLoader專案,上次還了解到facebook也開源了一個android圖片庫的專案叫Fresco。感嘆牛人無私奉獻的同時,也自省我為什麼做不出這樣大眾化的產品或者框架呢?小說裡面常講成為武林高手,必先有武林祕笈,再通過勤學苦練方能做到。在我們軟體行業呢,我認為這“武林祕笈”就是一個個優秀的開源專案。所以,要想成為軟體大師,必先深入的研究學習這些專案的原始碼。以上只是個人的一些經歷和看法,此際。

-------------------------我是分割線------------------------

通過ImageLoader專案的學習,希望能搞清楚一件事。那就是ImageLoader的快取機制。

面試過程中,我問過很多人關於快取機制的描述,沒有一個是滿意的。很多人會講,bitmap不用的時候就回收。我覺得這個描述太淺顯了,不足以形容一個機制!所以,通過此文希望能把這個快取機制描述清楚。

第一個點:記憶體分配數量

我們都知道java虛擬機器分配給每個程序的堆記憶體(heap limit)是有限的,android也是同樣的原理。android當中,每個app的最大記憶體數跟裝置記憶體是直接聯絡的。那麼,我們實際裝置的每個app的最大記憶體數量能是多少呢?可以通過以下方式獲取檢視(學習自com.nostra13.universalimageloader.core包下,DefaultConfigurationFactory類的createMemoryCache方法)

			//獲取每個app的最大記憶體數
			ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
			int memoryClass = am.getMemoryClass();

並且在android3.0之後,manifest,application增加了一個屬性largeHeap。這個設定可以幫助我們app獲取更多的記憶體數量,可以通過以下方式檢視

am.getLargeMemoryClass();

是不是又學了一招?實際效果還沒有對比,如果你的程式經常記憶體溢位,不妨加這個設定試試看。

2015-6-30

補充上一點:經過測試largeMemoryClass最大可達到手機理論記憶體的1/4(測試型號s3、m8),大大提升了單個app的記憶體上限。

2015-7-2

第二個點:快取是如何被及時回收的?

ImageLoader的快取機制使用的雙層快取,即磁碟、記憶體快取。這應該是成熟快取機制的標配。那麼,就先來看看磁碟快取的檔案回收機制吧!

首先,ImageLoader會約定兩個數量上限(檔案總大小、檔案數)只要磁碟檔案超過其中之一個上限,就會回收不常用的檔案。

那麼問題又來了,如何判定一個檔案不常用呢?

答案是ImageLoader採用LRU策略(演算法)來理本地磁碟檔案。LRULeast Recently Used的縮寫,即最近最少使用頁面置換演算法。更詳細的LRU策略解釋這裡不再展開。下面來看看具體實現細節:


LruDiskCache包含一個DiskLruCache的屬性,LruDiskCache的介面實現都是依靠DiskLruCache來做的。所以DiskLruCache類裡面才是磁碟快取處理的核心類。DiskLruCache這個類裡面有一個File屬性journalFile是用來記錄檔案最近訪問先後順序的。lruEntries屬性存放所有磁碟快取檔案的路徑和檔案大小。lruEntries的定義如下:

	//這是lru演算法的關鍵所在,以元素最近訪問的先後順序來排序。最近訪問的時間距離現在越久則越靠前,否則越靠後
	private final LinkedHashMap<String, Entry> lruEntries =
			new LinkedHashMap<String, Entry>(0, 0.75f, true);

第三個引數true就是指按著最經訪問順序來排序。

磁碟快取就記錄這麼多,接下來是記憶體快取。相信其原理也是類似的。

2015-7-3

本來想記憶體快取可能會用到非常巧妙的方法來處理記憶體的回收問題。檢視到結果,有些詫異呢!記憶體快取原理:

1、根據url從記憶體快取找Bitmap物件,找到返回,否則執行下一步

2、從網路或者磁碟檔案讀取到Bitmap物件,直接放在一個map容器(使用LRU策略管理)快取起來並返回。

3、如果容器內的所有Bitmap物件大小超過記憶體快取上限maxSize,則remove掉距離當前最久訪問的Bitmap物件。(這裡就是我說的詫異點,竟然不做recycle操作)

下面是記憶體快取管理的類圖:


trimToSize方法就是根據記憶體快取上限管理的實現。可以看到這裡被remove掉的Bitmap物件並沒有做recycle操作,而僅僅是被拋棄為垃圾物件等待系統回收,這樣還是會留下OOM的隱患。實際上在這裡直接做recycle操作是有些魯莽的,因為這個Bitmap物件很有可能正在被某個view使用中。如果強行recycle,就會報這樣一個錯:java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap。

以上是ImageLoader專案採用的預設快取策略,該專案還支援FIFO(先進先出)、LimitedAge(最大儲存週期)==快取機制,這裡不再做詳細記錄。快取的內容就記錄這些。