標籤:
記憶體泄露可以引發很多的問題:
1.程式卡頓,響應速度慢(記憶體佔用高時JVM虛擬機器會頻繁觸發GC)
2.莫名消失(當你的程式所佔記憶體越大,它在背景時候就越可能被幹掉。反之記憶體佔用越小,在後台存在的時間就越長)
3.直接崩潰(OutOfMemoryError)
ANDROID記憶體面臨的問題:
1.有限的堆記憶體,原始只有16M
2.記憶體大小消耗等根據裝置,作業系統等級,螢幕尺寸的不同而不同
3.程式不能直接控制
4.支援後台多任務處理(multitasking)
5.運行在虛擬機器之上
我主要通過以下的5個方面(5R)來對ANDROID記憶體進行最佳化:
1.Reckon(計算)首先需要知道你的app所消耗記憶體的情況,知己知彼才能百戰不殆
2.Reduce(減少)消耗更少的資源
3.Reuse(重用)當第一次使用完以後,盡量給其他的使用
4.Recycle(回收)返回資源給生產流
5.Review(檢查)回顧檢查你的程式,看看設計或代碼有什麼不合理的地方。
下面就從這幾個方面詳細說一下:
一、Reckon(計算)
瞭解自己應用的記憶體使用量情況是很有必要的。如果當記憶體使用量過高的話就需要對其進行最佳化,因為更少的使用記憶體可以減少ANDROID系統終止我們的進程的幾率,也可以提高多任務執行效率和體驗效果。下面從系統記憶體(system ram)和堆記憶體(heap)兩個方面介紹一些查看和計算記憶體使用量情況的方法:
1.System Ram(系統記憶體):
觀察和計算系統記憶體使用量情況,可以使用Android提供給我們的兩個工具procstats,meminfo。他們一個側重於背景記憶體使用量,另一個是運行時的記憶體使用量。
(1)Process Stats:
Android 4.4 KitKat 提出了一個新系統服務,叫做procstats。它將協助你更好的理解你app在後台(background)時的記憶體使用量情況。
Procstats可以去監視你app在一段時間的行為,包括在後台運行了多久,並在此段時間使用了多少記憶體。從而協助你快速的找到應用中不效率和不規範的地方去避免影響其performs,尤其是在低記憶體的裝置上運行時。
你可以通過adb shell命令去使用procstats(adb shell dumpsys procstats –hours 3),或者更方便的方式是運行Process Stats開發人員工具(在4.4版本的手機中點擊Settings > Developer options > Process Stats)
點擊單個條目還可以查看詳細資料
(2)meminfo:
Android還提供了一個工具叫做meminfo。它是根據PSS標準 (Proportional Set Size——實際實體記憶體)計算每個進程的記憶體使用量並且按照重要程度排序。
你可以通過命令列去執行它:(adb shell dumpsys meminfo)或者使用在裝置上點擊Settings > Apps > Running(與Procstats不用,它也可以在老版本上運行)
2.Heap(堆記憶體):
在程式中可以使用如下的方法去查詢記憶體使用量情況
(1)ActivityManager#getMemoryClass()
查詢可用堆記憶體的限制,3.0(HoneyComb)以上的版本可以通過largeHeap=“true”來申請更多的堆記憶體(不過這算作“作弊”)
(2)ActivityManager#getMemoryInfo(ActivityManager.MemoryInfo)
得到的MemoryInfo中可以查看如下Field的屬性:
availMem:表示系統剩餘記憶體
lowMemory:它是boolean值,表示系統是否處於低記憶體運行
hreshold:它表示當系統剩餘記憶體低於好多時就看成低記憶體運行
(3)android.os.Debug#getMemoryInfo(Debug.MemoryInfo memoryInfo)
得到的MemoryInfo中可以查看如下Field的屬性:
dalvikPrivateDirty: The private dirty pages used by dalvik。
dalvikPss :The proportional set size for dalvik.
dalvikSharedDirty :The shared dirty pages used by dalvik.
nativePrivateDirty :The private dirty pages used by the native heap.
nativePss :The proportional set size for the native heap.
nativeSharedDirty :The shared dirty pages used by the native heap.
otherPrivateDirty :The private dirty pages used by everything else.
otherPss :The proportional set size for everything else.
otherSharedDirty :The shared dirty pages used by everything else.
dalvik:是指dalvik所使用的記憶體。
native:是被native堆使用的記憶體。應該指使用C\C++在堆上分配的記憶體。
other:是指除dalvik和native使用的記憶體。但是具體是指什麼呢?至少包括在C\C++分配的非堆記憶體,比如分配在棧上的記憶體。
private:是指私人的。非共用的。
share:是指共用的記憶體。
PSS:實際使用的實體記憶體(比例分配共用庫佔用的記憶體)
PrivateDirty:它是指非共用的,又不能換頁出去(can not be paged to disk )的記憶體的大小。比如Linux為了提高分配記憶體速度而緩衝的小對象,即使你的進程結束,該記憶體也不會釋放掉,它只是又重新回到緩衝中而已。
SharedDirty:參照PrivateDirty我認為它應該是指共用的,又不能換頁出去(can not be paged to disk )的記憶體的大小。比如Linux為了提高分配記憶體速度而緩衝的小對象,即使所有共用它的進程結束,該記憶體也不會釋放掉,它只是又重新回到緩衝中而已。
(4)android.os.Debug#getNativeHeapSize()
返回的是當前進程navtive堆本身總的記憶體大小
(5)android.os.Debug#getNativeHeapAllocatedSize()
返回的是當前進程navtive堆中已使用的記憶體大小
(6)android.os.Debug#getNativeHeapFreeSize()
返回的是當前進程navtive堆中已經剩餘的記憶體大小
(7)Memory Analysis Tool(MAT):
通常記憶體泄露分析被認為是一件很有難度的工作,一般由團隊中的資深人士進行。不過,今天我們要介紹的 MAT(Eclipse Memory Analyzer)被認為是一個“傻瓜式“的堆轉儲檔案分析工具,你只需要輕輕點擊一下滑鼠就可以產生一個專業的分析報告。
如:
二、Reduce(減少)主要通過以下方法減少記憶體使用量:
(一)Bitmap:
Bitmap是記憶體消耗大戶,絕大多數的OOM崩潰都是在操作Bitmap時產生的,下面來看看如何幾個處理圖片的方法:
(1)圖片顯示:根據需求去載入圖片的大小。例如在列表中僅用於預覽時載入縮圖(thumbnails ),只有當使用者點擊具體條目想看詳細資料的時候,才顯示整個圖片
(2)圖片大小:直接使用ImageView顯示bitmap會佔用較多資源,特別是圖片較大的時候,可能導致崩潰。使用BitmapFactory.Options設定inSampleSize, 這樣做可以減少對系統資源的要求。屬性值inSampleSize表示縮圖大小為原始圖片大小的幾分之一,即如果這個值為2,則取出的縮圖的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4。
(3)圖片像素:Android中圖片有四種屬性,分別是:
ALPHA_8:每個像素佔用1byte記憶體
ARGB_4444:每個像素佔用2byte記憶體
ARGB_8888:每個像素佔用4byte記憶體 (預設)
RGB_565:每個像素佔用2byte記憶體
Android預設的顏色模式為ARGB_8888,這個顏色模式色彩最細膩,顯示品質最高。但同樣的,佔用的記憶體也最大。 所以在對圖片效果不是特別高的情況下使用RGB_565(無透明度),或者ARGB_4444(有透明度)
(4)圖片回收:使用Bitmap過後,就需要及時的調用Bitmap.recycle()方法來釋放Bitmap佔用的記憶體空間,而不要等Android系統來進行釋放。
(5)捕獲異常:經過上面這些最佳化後還會存在報OOM的風險,所以下面需要一道最後的關卡——捕獲OOM異常
(二)修改對象參考型別:
引用分為四種層級,這四種層級由高到低依次為:強引用>軟引用>弱引用>虛引用。
如果只是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對於應用的效能更在意,想儘快回收一些佔用記憶體比較大的對象,則可以使用弱引用。
自Android2.3版本(API Level 9)開始,記憶體回收行程更著重於對軟/弱引用的回收,對於軟引用或者弱引用的Bitmap緩衝方案,現在已經不推薦使用了。
(三)其他減少記憶體使用量的方法
(1)對常量使用static final修飾符
(2)靜態方法代替虛擬方法
(3)減少不必要的全域變數
(4)避免建立不必要的對象
(5)避免內部Getters/Setters
(6)避免使用浮點數
(7)使用實體類比介面好
(8)免使用枚舉
(9)for迴圈時,訪問成員變數比訪問本地變數慢得多,永遠不要在for的第二個條件中調用任何方法,在java1.5中引入的for-each文法(例如for (Foo a : mArray))編譯器還會在每次迴圈中產生一個額外的對本地變數的儲存操作(如上面例子中的變數a),這樣會比普通迴圈多出4個位元組,速度要稍微慢一些
(10)瞭解並使用類庫
三、Reuse(重用)
重用是減少記憶體消耗的重要手段之一,核心思路就是將已經存在的記憶體資源重新使用而避免去建立新的,最典型的使用就是緩衝(Cache)和池(Pool)。
(一)Bitmap緩衝:Bitmap緩衝分為兩種:一種是記憶體緩衝,一種是硬碟緩衝。
記憶體緩衝(LruCache):
以犧牲寶貴的應用記憶體為代價,記憶體緩衝提供了快速的Bitmap訪問方式。系統提供的LruCache類是非常適合用作緩衝Bitmap任務的,它將最近被引用到的Object Storage Service在一個強引用的LinkedHashMap中,並且在緩衝超過了指定大小之後將最近不常使用的對象釋放掉。
注意:以前有一個非常流行的記憶體緩衝實現是SoftReference(軟引用)或者WeakReference(弱引用)的Bitmap緩衝方案,然而現在已經不推薦使用了。自Android2.3版本(API Level 9)開始,記憶體回收行程更著重於對軟/弱引用的回收,這使得上述的方案相當無效。
硬碟緩衝(DiskLruCache):
一個記憶體緩衝對加速訪問最近瀏覽過的Bitmap非常有協助,但是你不能局限於記憶體中的可用圖片。GridView這樣有著更大的資料集的組件可以很輕易消耗掉記憶體緩衝。你的應用有可能在執行其他任務(如打電話)的時候被打斷,並且在背景任務有可能被殺死或者緩衝被釋放。一旦使用者重新聚焦(resume)到你的應用,你得再次處理每一張圖片。
在這種情況下,硬碟緩衝可以用來儲存Bitmap並在圖片被記憶體緩衝釋放後減小圖片載入的時間(次數)。當然,從硬碟載入圖片比記憶體要慢,並且應該在後台線程進行,因為硬碟讀取的時間是不可預知的。
注意:如果訪問圖片的次數非常頻繁,那麼ContentProvider可能更適合用來儲存緩衝圖片,例如Image Gallery這樣的應用程式。更多關於記憶體緩衝和硬碟緩衝的內容請看Google官方教程https://developer.android.com/develop/index.html
(二)圖片緩衝的開源項目:
對於圖片的緩衝現在都傾向於使用開源項目,這裡我列出幾個我搜到的:
1. Android-Universal-Image-Loader 圖片緩衝
目前使用最廣泛的圖片緩衝,支援主流圖片緩衝的絕大多數特性。
項目地址:https://github.com/nostra13/Android-Universal-Image-Loader
2. picasso square開源的圖片緩衝
項目地址:https://github.com/square/picasso
特點:
(1)可以自動檢測adapter的重用並取消之前的下載
(2)圖片變換
(3)可以載入本地資源
(4)可以設定佔位資源
(5)支援debug模式
3. ImageCache 圖片緩衝,包含記憶體和Sdcard緩衝
項目地址:https://github.com/Trinea/AndroidCommon
特點:
(1)支援預取新圖片,支援等待隊列
(2)包含二級緩衝,可自訂檔案名稱儲存規則
(3)可選擇多種緩衝演算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13種)或自訂緩衝演算法
(4)可方便的儲存及初始化恢複資料
(5)支援不同類型網路處理
(6)可根據系統配置初始化緩衝等
4. Android 網路通訊架構Volley
項目地址:https://android.googlesource.com/platform/frameworks/volley
我們在程式中需要和網路通訊的時候,大體使用的東西莫過於AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,在2013年的Google I/O發布了Volley。Volley是Android平台上的網路通訊庫,能使網路通訊更快,更簡單,更健壯。
特點:
(1)JSON,映像等的非同步下載;
(2)網路請求的排序(scheduling)
(3)網路請求的優先順序處理
(4)緩衝
(5)多層級取消請求
(6)和Activity和生命週期的聯動(Activity結束時同時取消所有網路請求)
(三)Adapter適配器
在Android中Adapter使用十分廣泛,特別是在list中。所以adapter是資料的 “集散地” ,所以對其進行記憶體最佳化是很有必要的。主要使用convertView和ViewHolder來進行緩衝處理
(四)池(PooL)
1.對象池:
對象池使用的基本思路是:將用過的對象儲存起來,等下一次需要這種對象的時候,再拿出來重複使用,從而在一定程度上減少頻繁建立對象所造成的開銷。 並非所有對象都適合拿來池化――因為維護對象池也要造成一定開銷。對產生時開銷不大的對象進行池化,反而可能會出現“維護對象池的開銷”大於“產生新對象的開銷”,從而使效能降低的情況。但是對於產生時開銷可觀的對象,池化技術就是提高效能的有效原則了。
2.線程池:
線程池的基本思想還是一種對象池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反覆建立線程對象所帶來的效能開銷,節省了系統的資源。
比如:一個應用要和網路打交道,有很多步驟需要訪問網路,為了不阻塞主線程,每個步驟都建立個線程,線上程中和網路互動,用線程池就變的簡單,線程池是對線程的一種封裝,讓線程用起來更加簡便,只需要創一個線程池,把這些步驟像任務一樣放進線程池,在程式銷毀時只要調用線程池的銷毀函數即可。java提供了ExecutorService和Executors類,我們可以應用它去建立線程池。
(五)注意:要根據情況適度使用緩衝,因為記憶體有限。能儲存路徑地址的就不要存放圖片資料,不經常使用的盡量不要緩衝,不用時就清空。
四、Recycle(回收)
回收可以說是在記憶體使用量中最重要的部分。因為記憶體空間有限,無論你如何最佳化,如何節省記憶體總有用完的時候。而回收的意義就在於去清理和釋放那些已經閑置,廢棄不再使用的記憶體資源和記憶體空間。
(一)記憶體回收(GC):
1、Java記憶體回收行程:
Java技術提供了一個系統級的線程,即垃圾收集器線程(Garbage Collection Thread),來跟蹤每一塊分配出去的記憶體空間,當JAVA 虛擬機器(Java Virtual Machine)處於空閑迴圈時,垃圾收集器線程會自動檢查每一快分配出去的記憶體空間,然後自動回收每一快可以回收的無用的記憶體塊。
2、作用:
1.清除不用的對象來釋放記憶體:
2.消除堆記憶體空間的片段:
3、記憶體回收行程優點:
1.減輕編程的負擔,提高效率:
2.它保護程式的完整性:
4、記憶體回收行程缺點:
1.佔用資源時間:
2.不可預知:
3.不確定性:
4.不可操作
5、finalize():
每一個對象都有一個finalize方法,這個方法是從Object類繼承來的。
當記憶體回收確定不存在對該對象的更多引用時,由對象的記憶體回收行程調用此方法。
Java 技術允許使用finalize方法在垃圾收集器將對象從記憶體中清除出去之前做必要的清理工作。一旦記憶體回收行程準備好釋放對象佔用的空間,將首先調用其finalize()方法,並且在下一次記憶體回收動作發生時,才會真正回收對象佔用的記憶體。
簡單的說finalize方法是在垃圾收集器刪除對象之前對這個對象調用的
6、System.gc():
我們可以調用System.gc方法,建議虛擬機器進行記憶體回收工作(注意,是建議,但虛擬機器會不會這樣幹,我們也無法預知!)
總之,在Java語言中,判斷一塊記憶體空間是否符合垃圾收集器收集的標準只有兩個:
1.給對象賦予了空值null,以下再沒有調用過。
2.給對象賦予了新值,既重新分配了記憶體空間。
最後再次提醒一下,一塊記憶體空間符合了垃圾收集器的收集標準,並不意味著這塊記憶體空間就一定會被垃圾收集器收集。
(二)資源的回收:
1、Thread(線程)回收:
線程中涉及的任何東西GC都不能回收(Anything reachable by a thread cannot be GC’d ),所以線程很容易造成記憶體泄露。因為運行中的線程是稱之為記憶體回收根(GC Roots)對象的一種,不會被記憶體回收。當記憶體回收行程判斷一個對象是否可達,總是使用記憶體回收根對象作為參考點。
2、Cursor(遊標)回收:
Cursor是Android查詢資料後得到的一個管理資料集合的類,在使用結束以後。應該保證Cursor佔用的記憶體被及時的釋放掉,而不是等待GC來處理。並且Android明顯是傾向於編程者手動的將Cursor close掉,因為在原始碼中我們發現,如果等到記憶體回收行程來回收時,會給使用者以錯誤提示。
有一種情況下,我們不能直接將Cursor關閉掉,這就是在CursorAdapter中應用的情況,但是注意,CursorAdapter在Acivity結束時並沒有自動的將Cursor關閉掉,因此,你需要在onDestroy函數中,手動關閉。
(三)Receiver(接收器)回收
調用registerReceiver()後未調用unregisterReceiver().
當我們Activity中使用了registerReceiver()方法註冊了BroadcastReceiver,一定要在Activity的生命週期內調用unregisterReceiver()方法取消註冊
也就是說registerReceiver()和unregisterReceiver()方法一定要成對出現,通常我們可以重寫Activity的onDestory()方法取消註冊
(四)Stream/File(流/檔案)回收:
主要針對各種流,檔案資源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,檔案,I/O,Bitmap圖片等操作等都應該記得顯示關閉。
五、Review(檢查)
主要目的就是檢查代碼中存在的不合理和可以改進的地方
(一)Code Review(代碼檢查):
Code Review主要檢查代碼中存在的一些不合理或可以改進最佳化的地方。
(二)UI Review(視圖檢查):
Android對於視圖中控制項的布局渲染等會消耗很多的資源和記憶體,所以這部分也是我們需要注意的。
1、減少視圖層級:
減少視圖層級可以有效減少記憶體消耗,因為視圖是一個樹形結構,每次重新整理和渲染都會遍曆一次。
1.hierarchyviewer:想要減少視圖層級首先就需要知道視圖層級,所以下面介紹一個SDK中內建的一個非常好用的工具hierarchyviewer。
你可以在下面的地址找到它:\sdk path\sdk\tools
如大家可以看到,hierarchyviewer可以非常清楚的看到當前視圖的層級結構,並且可以查看視圖的執行效率(視圖上的小圓點,綠色表示流暢,黃色和紅色次之),所以我們可以很方便的查看哪些view可能會影響我們的效能從而去進一步最佳化它。
2.ViewStub標籤
此標籤可以使UI在特殊情況下,直觀效果類似於設定View的不可見度,但是其更大的意義在於被這個標籤所包裹的Views在預設狀態下不會佔用任何記憶體空間。
3.include標籤
可以通過這個標籤直接載入外部的xml到當前結構中,是複用UI資源的常用標籤。
4.merge標籤
它在最佳化UI結構時起到很重要的作用。目的是通過刪減多餘或者額外的層級,從而最佳化整個Android Layout的結構。
5.布局用Java代碼比寫在XML中快
一般情況下對於Android程式布局往往使用XML檔案來編寫,這樣可以提高開發效率,但是考慮到代碼的安全性以及執行效率,可以通過Java代碼執行建立,雖然Android編譯過的XML是二進位的,但是載入XML解析器的效率對於資源佔用還是比較大的,Java處理效率比XML快得多,但是對於一個複雜介面的編寫,可能需要一些套嵌考慮,如果你思維靈活的話,使用Java代碼來布局你的Android應用程式是一個更好的方法。
3、重用系統資源:
1. 利用系統定義的id
在xml檔案中引用系統的id,只需要加上“@android:”首碼即可。如果是在Java代碼中使用系統資源,和使用自己的資源基本上是一樣的。不同的是,需要使用android.R類來使用系統的資源,而不是使用應用程式指定的R類。
2. 利用系統的圖片資源
這樣做的好處,一個是美工不需要重複的做一份已有的圖片了,可以節約不少工時;另一個是能保證我們的應用程式的風格與系統一致。
3. 利用系統的字串資源
如果使用系統的字串,預設就已經支援多語言環境了。比如直接使用了@android:string/yes和@android:string/no,在簡體中文環境下會顯示“確定”和“取消”,在英文環境下會顯示“OK”和“Cancel”。
4. 利用系統的Style
假設布局檔案中有一個TextView,用來顯示視窗的標題,使用中等大小字型。可以使用下面的程式碼片段來定義TextView的Style。
android中的記憶體最佳化