1. 程式人生 > >關於Android記憶體優化你應該知道的一切

關於Android記憶體優化你應該知道的一切

介紹

在Android系統中,記憶體分配與釋放分配在一定程度上會影響App效能的—鑑於其使用的是類似於Java的GC回收機制,因此係統會以消耗一定的效率為代價,進行垃圾回收。
在中國有句老話:”由儉入奢易,由奢返儉難”。而此諺語也似乎正適應於Android的記憶體使用。GC回收機制給程式設計師省去了像C語言程式設計師那樣手動釋放記憶體的工作,但是也帶來了一系列的”雷”—動輒記憶體洩漏,再甚者稍微不慎就會OOM。
這篇文章將會介紹Android的記憶體管理機制並解釋幾種在此機制下對記憶體有影響的幾個比較關鍵的因素。另外,還會介紹如何提高記憶體管理、檢測並避免記憶體洩漏,以及如何分析記憶體分配情況

Here we go!!

Android記憶體管理機制

Android記憶體模型並沒有交換空間(swap space)的概念,而是使用分頁(paging)和記憶體對映(memory-mapping)管理記憶體,這意味著不管是分配新的物件還是使用已有的對映頁這些記憶體仍然被佔據在RAM裡而不能被扇出。因此完全釋放你app記憶體的唯一方式是釋放物件引用以便於能被垃圾回收器回收。
Dalvik虛擬機器為每一個App分配相應大小的可用記憶體空間,從2M開始到32M(此最大值根據不同的廠商一般會有不同),不可否認,在當前國內各大手機廠商瘋狂的拼硬體的時代,這個每個App的可用記憶體甚至被提高到了256M,這有效的避免了很多OOM的情況,但是如果程式設計師因此就不管記憶體管理任意而為,會為此付出嚴重代價的(App高解除安裝率).
Android系統會將在後臺執行的App程序儲存在一個LRU cache中(不懂的自行百度)。當系統記憶體緊張時,它會根據LRU的策略kill掉一些優先順序比較低的程序。當然,究竟哪一個App是當前佔用記憶體最大的程式也是它kill程序時所考慮的一個因素。如果你希望自己的App在後臺執行時能儘可能長的”活著”,不被系統kill掉,就要好好的思考如何避免被kill。比如在App轉到後臺執行之前,儘可能的將沒有用的記憶體給釋放掉,這樣會減少Android系統列印錯誤日誌甚至終止App的可能性。

如何提高Android記憶體使用

Android系統是世界上使用率最高的手機系統。每年都有成千上萬的年輕人轉入到開發Android系統的行列中,但是這些人中,能真正寫出穩定、可擴充套件性強的程式碼的還是少數。

以下是提高記憶體使用的幾條建議:
  1. 慎用橋接模式,雖然從程式的設計角度來看,抽象能夠幫助我們建立更加靈活的軟體架構。但是在手機系統中,這種設計模式有可能會造成很多副作用。除非大有必要,否則儘量不要用橋接模式
  2. 避免使用列舉Enum,一個Enum分配的空間是一個普通常量的兩倍,因此儘量少使用列舉
  3. 試著使用Android框架優化後的資料容器,譬如:SparseArray, SparseBooleanArray, 以及 LongSparseArray containers. 使用這些類來替代HashMap的使用。原因是傳統的 HashMap 在記憶體上的實現十分的低效,因為它需要為 HashMap 中每一項在記憶體中建立對映關係. 另外, SparseArray類非常高效因為它避免了對key和value的自動封箱. 萬事都有兩面性,這些個被優化過的容器也不例外,千萬記住SparseArray等容器並不適應於內部元素很多的集合,當集合的長度超過1000條時,使用SparseArray進行增刪改查的效率遠比HashMap低
  4. 避免建立不需要的物件。對於生命週期較短的臨時變數,儘量想辦法規避掉每次都要去建立它,這樣GC回收被強制呼叫機會就會更少,留給Android系統進行UI渲染或者音訊載入的時間就會更多,從而避免了卡頓現象
  5. 檢測App記憶體中的可用堆的大小,在程式碼中可以通過動態的呼叫ActivityManager::getMemoryClass()方法來查詢你的App中的可用記憶體堆大小。如果系統檢測到需要分配的記憶體大小超過了此值,則會丟擲OOM錯誤
  6. **可以適當適應onTrimMemory回撥方法。OnTrimMemory 回撥是 Android 4.0 之後提供的一個API,這個 API 是提供給開發者的,它的主要作用是提示開發者在系統記憶體不足的時候,通過處理部分資源來釋放記憶體,從而避免被 Android 系統殺死。這樣應用在下一次啟動的時候,速度就會比較快。—詳情請參閱Android記憶體優化—OnTrimMemory
  7. 當使用Service應當小心小心再小心!當你需要啟動一個服務在後臺執行一項任務時,應當在其完成工作之後儘快的停止此服務。可以考慮使用IntentService—當在子執行緒完成耗時操作之後,IntentService會自動停止並結束自身。然而在實際開發中經常會碰到需要服務去執行一項耗時比較長的任務,比如:音樂播放器,下載APP等等。像這樣的應用可以分隔為兩個程序:一個程序負責 UI 工作, 另外一個則在後臺服務中執行其它的工作. 在AndroidManifest 檔案中為各個元件申明 android:process 屬性就可以分隔為不同的程序。注意一點:在後臺執行的Service絕對不能處理或者持有任何UI,否則系統可能會分配雙倍甚至三倍的空間來維護UI資源!!
  8. 當你載入 bitmap 時, 需要根據當前裝置的解析度載入相應解析度的bitmap進入記憶體,如果下載下來的原圖解析度比裝置解析度高則要壓縮它. 要小心bitmap的解析度增加後所佔用的記憶體也要進行相應的增加(平方級increase2的增長), 因為它是根據x和y的大小來增加記憶體佔用的
  9. 使用程式碼混淆工具 ProGuard 通過去除沒有用的程式碼和通過語義模糊來重新命名類, 欄位和方法來縮小, 優化和混淆你的程式碼. 使用它能使你的程式碼更簡潔, 更少量的RAM對映頁.如果構建apk後你沒有做後續的任何處理(包括根據你的證書進行簽名), 你必須執行 zipalign 工具為你的apk進行優化, 如果不這樣做會導致你的應用使用更多的記憶體,zipalign之後像資源這樣的東西不會再從apk中對映(mmap)入記憶體.注意:goole play store 不接受沒有進行zipalign的apk

針對以上幾條,後續會單獨再post幾篇blog單獨講解。

如何避免記憶體洩漏

程式設計師在分配記憶體時如果考慮到了上述9條建議,或許會給App在效率上帶來不小的收益,並且可以在後臺時依然堅挺(更持久!)。 但是這一切的努力都會因為一個叫做記憶體洩漏的東東而萎了! 這玩意就如同可樂的存在一樣,少喝一點還能扛得住,但是多了的話。。你懂得! 以下是幾個常見的造成記憶體洩漏的情況:

  • 當查詢完資料庫之後,及時關閉Cursor物件。
  • 記得在Activity的onPause方法中呼叫unregisterReceiver()方法,解註冊廣播
  • 避免Content記憶體洩漏,比如在4.0.1之前的版本上不要講Drawer物件置為static。當一個Drawable繫結到了View上,實際上這個View物件就會成為這個Drawable的一個callback成員變數,上面的例子中靜態的sBackground持有TextView物件lable的引用,而lable只有Activity的引用,而Activity會持有其他更多物件的引用。sBackground生命週期要長於Activity。當螢幕旋轉時,Activity無法被銷燬,這樣就產生了記憶體洩露問題。
  • 儘量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類例項的引用,當非靜態內部類的引用的宣告週期長於Activity的宣告週期時,會導致Activity無法被GC正常回收掉。
  • 謹慎使用執行緒Thread!!這條是很多人會犯的錯誤: Java中的Thread有一個特點就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機器對所有被啟用狀態的執行緒都是持有強引用,導致GC永遠都無法回收掉這些執行緒物件,除非執行緒被手動停止並置為null或者使用者直接kill程序操作。所以當使用執行緒時,一定要考慮在Activity退出時,及時將執行緒也停止並釋放掉
  • 使用Handler時,要麼是放在單獨的類檔案中,要麼就是使用靜態內部類。因為靜態的內部類不會持有外部類的引用,所以不會導致外部類例項的記憶體洩露–詳情請參閱Android中Handler引起的記憶體洩露

如何分析記憶體的使用情況

在Mac終端(windows的cmd)中,可以使用adb logcat命令來檢視或者統計記憶體的具體使用情況,另外還可以指定包名來檢視相應App的記憶體使用情況。除此之外,還可以使用三方的工具來分析Android記憶體的使用情況,比如:DDMS、MAT(Memory Analyzer tool).

在adb logcat中,通常能看到GC相關的log如下圖所示

Dalvik GC log

GC_Reason 觸發GC回收的原因,可能包含以下幾種情況:

  • GC_FOR_ALLOC, 這個是說我們的應用嘗試去分配記憶體而這時候和heap已經快滿了(不夠用了),這個時候系統會把我們的應用停下來然後進行記憶體回收,通常heap size會增大
  • GC_CONCURRENT,這個應該的當我們的Heap size 快要被填滿的時候觸發的一個併發的記憶體回收
  • GC_EXPLICIT,這個是主動呼叫系統gc方法觸發的GC(在DDMS 點選GC就可以看到)
  • GC_HPROF_DUMP_HEAP 我們在做記憶體分析建立HPROF(MAT可以分析該檔案)的時候會列印

Amount feed 表示本次垃圾收集釋放了多少記憶體

Heap_stats 當前空閒記憶體佔總記憶體的百分比

External memory stats 表示API 10及以下的外部分配記憶體,已分配記憶體/導致垃圾回收的閾值

Pause_time 應用暫停的時間

通常情況下,生成的GC log越大,表示記憶體的分配與釋放發生的頻率越高,這種情況下往往會非常影響使用者體驗!

使用DDMS檢視並追蹤堆記憶體的分配情況

通過DDMS,程式設計師可以很輕鬆的檢測指定程序的記憶體分配情況。你可以通過“Heap”標籤檢視最新的實時的堆記憶體資訊,這樣可以幫助你辨別出究竟是哪一個操作最有可能造成大量的記憶體分配。 “Allocation Tracker” 標籤顯示的是最近所有的記憶體分配—包含分配物件的型別,是在哪個執行緒中分配等資訊。一下圖片演示的是使用DDMS展示程序資訊—包含了當前程序、對記憶體分配統計資訊。

heap_stat

相關推薦

關於Android記憶體優化應該知道一切

介紹 在Android系統中,記憶體分配與釋放分配在一定程度上會影響App效能的—鑑於其使用的是類似於Java的GC回收機制,因此係統會以消耗一定的效率為代價,進行垃圾回收。 在中國有句老話:”由儉入奢易,由奢返儉難”。而此諺語也似乎正適應於Android的

第一次使用Android Studio時應該知道一切配置

出現 jpg rcu true 導入 職位 文章 加載 什麽 【聲明】 歡迎轉載,但請保留文章原始出處→_→ 生命壹號:http://www.cnblogs.com/smyhvae/ 文章來源:http://www.cnblogs.com/smyhvae/p/43909

第一次使用Android Studio時應該知道一切配置(二):新建一個屬於自己的工程並安裝Genymotion模擬器

人性 pro net 參考 json irb 一個地方 vid 調試 【聲明】 歡迎轉載,但請保留文章原始出處→_→ 生命壹號:http://www.cnblogs.com/smyhvae/ 文章來源:http://www.cnblogs.com/smyhvae/p/439

第一次使用Android Studio時應該知道一切配置(三):gradle項目構建

gen 官方 配置文件 conf 什麽 學習 package ack 處的 ?【聲明】 歡迎轉載,但請保留文章原始出處→_→ 生命壹號:http://www.cnblogs.com/smyhvae/ 文章來源:http://www.cnblogs.com/smyhvae

Android注入框架應該知道一切------打造自己的注入框架

前言 Java的所有框架基本都是基於反射的,所以有句話是這麼說的,無反射,無框架。所以Android的注入框架也是基於反射的,接下來就簡單的介紹一下Android的注入框架你應該知道的一切。 註解簡介 註解(Annotation)在Java裡面是比較重要的

[Android]Android記憶體洩漏所要知道一切(翻譯)

以下內容為原創,歡迎轉載,轉載請註明 來自天天部落格:http://www.cnblogs.com/tiantianbyconan/p/7235616.html Android記憶體洩漏你所要知道的一切 原文:https://blog.aritraroy.in/everything-

Android Fragment 應該知道一切

很久以前寫過兩篇Fragment的介紹,主要就是介紹其功能:Android Fragment 真正的完全解析(上)和Android Fragment 真正的完全解析(下) 有興趣的可以湊合看下。之前的部落格屬於怎麼使用Fragment,本文目標教你如何用好Fragment,即

Android材料設計庫之摺疊式佈局應該知道一切

github原始碼地址:https://github.com/geduo83/AndroidMaterialDesign/tree/master/module_drawerlayout_coordinatorlayout 在Android5.0之後,Android給我們提供了非常豐富關於UI

Android 應該知道的的應用冷啟動過程分析和優化方案

你有沒有發現,點選安卓手機桌面上的App圖示時,有時候應用馬上進入主介面,有時候要經歷好幾秒甚至更久的白屏(也可能是黑屏)時間才能進入主介面呢?這其實是安卓應用常見的冷熱啟動問題。本文就和大家一起聊聊冷熱啟動方式和啟動頁的體驗優化方案。 啟動方式

Android單例模式應該知道一切

前言 單例模式想必大家都使用過,但是也許你並不完全瞭解它,在這裡我就來詳細介紹一下所有的單例模式,及單例模式中涉及的一些細節問題。 一、單例模式的作用 保證了單利類的物件只有一個例項存在,利於協調系統整體的行為。 二、單利的模式的使用場景

Android中關於View滑動的實現應該知道

nan ida gif 當前位置 距離 保存 改變 post 控件 滑動作為Android中最基礎的特效之一,使用場景非常廣泛。實現的方式也有多種,理解各種滑動的實現方式。清楚在開發中根據自己的實際需求,選擇合理的實現方案。這篇文章從:scrollTo()/scrollBy

做網站SEO優化,這些網絡引流方法,應該知道

尋求 可能 垃圾郵件 百度搜 如果 什麽 網站鏈接 很快 建立 對於網站SEO優化來說,網站流量的重要性不言而喻!國內的站長平臺工具通過用網站流量來衡量一個網站的權重,當你的網站流量很高的時候,同時會影響你網站的權重,進而影響你網站SEO優化排名。所以說流量對於一個網站的意

關於HandlerThread應該知道一切

什麼是HandlerThread 先來看看官方給的說明 Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes

Android關於Canvas知道的和不知道一切

在一年的Android自學中,Canvas一直是我能避且避的類,甚至不惜封裝自己的繪相簿來替代它。 如今回首,虐我千萬次的Canvas也不過如此,靜下心看看,其實也沒有想象中的那麼糟糕。 就像曾經等級30的我去打點等級40的副本(Canvas)非常吃力,現在等級50的我回來吊打它一樣。 所以朋友,遇到承

Android關於Path知道的和不知道一切

零、前言 1.canvas本身提供了很多繪製基本圖形的方法,普通繪製基本滿足 2.但是更高階的繪製canvas便束手無策,但它的一個方法卻將圖形的繪製連線到了另一個次元 3.下面進入Path的世界,[注]:本文只說Path,關於繪製只要使用Canvas.drawPath(Path,Paint)即可 4

Android關於Paint知道的和不知道一切

零、前言: 1.曾經也算半個藝術家,深知筆的重要性與複雜性 2.Android裡的Paint設定項好多基本上都是setXXX,getXXX,很多文字相關的內容都在Paint裡 3.主要由畫筆常規配置,畫筆型別、畫筆特效(線效,著色,濾色)、畫筆文字 4.本文暫時還無法覆蓋Paint的所有API,能用的

Android關於Color知道的和不知道一切

零、前言 1.做安卓的大多應該對顏色不太敏感,畢竟咱是敲程式碼的,顏色有設計師呢。 2.不過作為一名在大學被顏色薰(陶)過四年的人,對顏色多少還是挺親切的(雖然當時挺討厭的) 3.紀念也好,記錄也罷,為它寫篇總結也理所應當 4.如果你覺得並不需要了解關於顏色的知識,那你可以將本文當做一篇科普文(出去跟

Android Context 上下文 必須知道一切

                1、Context概念其實一直想寫一篇關於Context的文章,但是又怕技術不如而誤人子弟,於是參考了些資料,今天準備整理下寫出來,如有不足,請指出,參考資料會在醒目地方標明。Context,相信不管是第一天開發Android,還是開發Android的各種老鳥,對於Contex

關於Java序列化應該知道一切

什麼是序列化 我們的物件並不只是存在記憶體中,還需要傳輸網路,或者儲存起來下次再加載出來用,所以需要Java序列化技術。 Java序列化技術正是將物件轉變成一串由二進位制位元組組成的陣列,可以通過將二進位制資料儲存到磁碟或者傳輸網路,磁碟或者網路接收者可以在

Activity生命週期的回撥,應該知道得更多!--Android原始碼剖析(上)

private class ApplicationThread extends ApplicationThreadNative { //... public final void schedulePauseActivity(IBinder token, boolean finished,