大家好!本人是即將畢業學生一枚,閑暇時間經常看大神們寫的部落格學到很多東西。最近在做畢業設計的時候遇到一些問題,然後把自己的問題和解決方案總結一下,有不對的地方希望大家多多包涵,提出批評與指導。
這篇博文主要介紹使用AndroidStudio對記憶體進行分析和跟蹤,還有就是從源碼角度解決ImageLoader引起的OOM問題。
我正在做的項目使用到了ImageLoader來載入圖片,我也是第一次使用,就拿來直接用了。寫完的代碼運行很正常的載入圖片,並沒有發現什麼問題。但是在測試的時候發現了問題。當多次開啟軟體中一個瀏覽大圖片的Activity(每次的圖片都不一樣)後,這時在這個Activity中點擊返回後,螢幕會突然變黑,然後回到了MainActivity,並沒有回到上一級Activity,甚至連之間開啟的多個Activity都沒有返回。想了一下問題懷疑可能是重啟了這個應用。但那個大圖片的Activity開始開啟的幾次都可以正常的回到上一級Activity,再多開啟幾次就不能正常回退了,這時候我就開始看LogCat打出的日誌,看看有沒有發生什麼異常。發現ImageLoader產生了OOM的Bug。如:
然後上網上查了一下ImageLoader引起記憶體泄露的問題,發現有很多人遇到,但說到解決的辦法的,並沒有很好的答案。大部分都是說ImageLoaderConfiguration.Builder(context)和DisplayImageOptions.Builder()裡面的配置問題,但並不能解決這個問題。還有人說換一個圖片載入的架構,像Universal-Image-Loader(UIL)和Google的Volley架構,我嘗試換了Universal-Image-Loader(UIL)這個,但發現還是有這個問題。而且項目換一個架構改動比較大,就沒有嘗試Volley架構。還有人說是應為使用了cacheInMemory(true)和cacheOnDisc(true)這兩個屬性導致的,但是我將他們設為false還是會引發OOM,所以就自己用工具分析。
既然產生了OOM,我們就要分析記憶體,看看具體是什麼原因導致的。使用AndroidStudio工具(發現很多地方都比eclipse強大),在使用AndroidStudio的Memory工具觀察的時候發現了問題,在我多次開啟這個Activity的時候,發現Memory在一直的增長,每次Activity退出後Memory也沒有下降。
就這樣當我多次瀏覽不同的圖片,多次開啟這個Activity後,這個Memory就一直的增長,當增長到120+M的時候應用突然掛掉,之後又自動重啟。發現了這個現象我們就可以藉助AndroidStudio的強大工具來分析導致這個問題的原因。在Memory視窗的左邊有四個按鈕,分別是:
Enabled(藍色的開關):就是一個正常的開關功能
Initiate GC(橙色小卡車):就是手動調用GC,我們在抓記憶體前,一定要手動點擊 Initiate GC按鈕手動觸發GC,這樣抓到的記憶體使用量情況就是不包括Unreachable對象的(Unreachable指的是可以被記憶體回收行程回收的對象,但是由於沒有GC發生,所以沒有釋放,這時抓的記憶體使用量中的Unreachable就是這些對象)
Dump Java Heap(紫色帶向下的箭頭):擷取hprof檔案(hprof檔案是我們使用MAT工具分析記憶體時使用的檔案),但這裡直接產生的檔案MAT還不能直接使用,需用轉換成標準的hprof檔案。可以使用AndroidStudio轉換或者用hprof-conv命令轉化,網上可以查到。
Start Allocation Tracking(紫色帶圓圈):開始分配追蹤,第一次點擊可以指定追蹤記憶體的開始位置,第二次點擊可以結束追蹤的位置。這樣我們截取了一段要分析的記憶體,等待幾秒鐘AndroidStudio會給我們開啟一個Allocation視圖(感覺和MAT工具差不多,不過MAT工具更加強大,我們也可以擷取hprof檔案,使用MAT來分析)例如:
我截取了這段記憶體,接下來我們就看Allocation視圖來分析。
通過視圖中的內容我們首先看到1號線程它佔用了百分之92的記憶體,那麼我們就點開它看一下,每次都只要點開其中記憶體佔用最大的就可以。
到劃紅線的那個位置就可以了,這裡已經到了安卓的graphics源碼的層次,所以我們不需要再向下看了。我們看一下紅線上面的ImageLoader裡的那個decodeStream方法:
<img alt="decodeStream" src="http://www.2cto.com/uploadfile/Collfiles/20160506/20160506090822425.png" title="" kf="" ware="" vc="" "="" target="_blank" class="keylink" style="border-width: 0px; padding: 0px; margin: 0px auto; list-style: none; display: block; width: 630px; height: 118.462px;">vcHLRGlzcGxheUJpdG1hcFRhc2vV4rj2wODW0KOsxMfDtM7Sw8fU2b34yOu1vdXiuPbA4NbQv7S3os/WwcvSu7j2vqrPsqO6PC9wPg0KPHA+PGltZyBhbHQ9"DisplayBitmapTask" src="/uploadfile/Collfiles/20160506/20160506090822426.png" title="\" />
這個對象被傳到了DisplayBitmapTask的成員變數中,DisplayBitmapTask本身也實現了Runnable介面,那麼我就想是不是應為DisplayBitmapTask這個類沒有及時回收導致的Bitmap成員變數佔用記憶體的問題,所以我就在run()方法跑完之後,讓Bitmap = null。然後又將程式運行了一遍,但是發現根本沒有解決這個問題。這時候又重頭想了一下整個過程,發現我的操作是不對的,雖然我成功將DisplayBitmapTask中的Bitmap成員變數置為了空,但是他只是真正的Bitmap記憶體的一個引用,我並沒有將真正的Bitmap記憶體空間所釋放,只是將他的一個引用給釋放了。通過網上查詢發現,Bitmap會儲存在C層的記憶體中,如果我們想要釋放他在C層中的記憶體,可以調用Bitmap的recycle()方法,從代碼中看,這個方法本身會調用Bitmap.cpp中的Bitmap_recycle方法,這個是native方法。這樣就可以通知底層將C層中的Bitmap記憶體釋放掉,而且還會將一些相關的引用計數置0。但是並不會立即釋放掉,這要看系統。 一下子完美解決。既然查到了原因,那就好辦了,我將Bitmap = null這句話改為Bitmap.recycle()。好了,接下來我再次運行,發現這回應用直接就崩了,只要能在Logcat中抓到Log,一切問題都好解決。看了一下log發現下面這個Error:
通過字面理解的意思就是,我們的View去顯示了已經recycle的Bitmap。仔細想一想確實是,我們在ImageLoader中的一個線程的run方法中直接將Bitmap釋放掉了,那我們的應用豈不是直接顯示一個空的Bitmap,這當然會引起錯誤。既然不能在這裡釋放掉Bitmap,那我們應該在什麼地方釋放呢?那一定是在我們的View不再使用這個Bitmap的時候調用,想一想,那我們只要在Activity或者fragment的onDestory()方法中釋放了不就可以了嗎?但這時又有一個問題產生,我們該如何在上層應用擷取到這個View用到的Bitmap的對象呢?想了半天發現好像只能通過改寫ImageLoader的源碼來想辦法將這個對象會傳到上層應用。正當我在ImageLoader中尋找在哪裡寫這個回調方法時,突然我看到了這段代碼:
發現其實ImageLoader的源碼中已經為我們寫好了這個回調監聽器介面,而且在ImageLoader的displayImage方法中,也重載了一個提供了傳遞ImageLoaderListener實現對象的方法。這樣我們只需要實現ImageLoaderListener這個介面,並實現他的onLoadingComplete方法,在這個方法中對Bitmap做處理就可以了。
我們只需要在Activity或Fragment的onDestory()方法中再調用cleanBitmapList()方法就可以了。通過上面的更改我再次運行應用,發現真的解決了這個問題,我的應用程式記憶體不再會是一直的上升,而是上升之後又會下降。
註:這個方式比較適合cacheInMemory(false)和cacheOnDisc(false)的情況寫,應為如果你將這兩個設定為true時,那麼下次再次載入圖片時他會從記憶體和硬碟中去載入圖片,這兩個地方也持有Bitmap的引用,這時就會再次出現trying to use a recycled bitmap 這個錯誤。當然你也是可以設定為true的,只要在清除Bitmap(也就是我們這裡調用cleanBitmapList)的地方再加上mImageLoader.clearDiscCache()和mImageLoader.clearMemoryCache()這兩句話就可以。
好了,整個ImageLoader引起的OOM問題額分析與解決的過程就是這些,希望能對需要的人有協助,通過這個問題我也學會到,遇到問題最好的解決辦法就是我們要從最根本源碼的角度去分析,這樣既能學到很多東西,又可以解決困難問題
。