Android性能優化以內存泄漏

摘要:
綜述 內存泄漏(memory leak)是指因爲疏忽或錯誤形成程序未能釋放已經再也不使用的內存。那麼在Android中,當一個對象持有Activity的引用,若是該對象不能被系統回收,那麼當這個Activity再也不使用時,這個Activity也不會被系統回收,那這麼以來便出現了內存泄漏的狀況。

綜述php

內存泄漏(memory leak)是指因爲疏忽或錯誤形成程序未能釋放已經再也不使用的內存。那麼在Android中,當一個對象持有Activity的引用,若是該對象不能被系統回收,那麼當這個Activity再也不使用時,這個Activity也不會被系統回收,那這麼以來便出現了內存泄漏的狀況。在應用中內出現一次兩次的內存泄漏獲取不會出現什麼影響,可是在應用長時間使用之後,如果存在大量的Activity沒法被GC回收的話,最終會致使OOM的出現。那麼咱們在這就來分析一下致使內存泄漏的常見因素而且如何去檢測內存泄漏。java

致使內存泄漏的常見因素android

情景一:靜態Activity和View面試

靜態變量Activity和View會致使內存泄漏,在下面這段代碼中對Activity的Context和TextView設置爲靜態對象,從而產生內存泄漏。性能優化

import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView;   public class MainActivity extends AppCompatActivity {       private static Context context;     private static TextView textView;       @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         context = this;         textView = new TextView(this);     } } 複製代碼

情景二:Thread,匿名類,內部類bash

在下面這段代碼中存在一個非靜態的匿名類對象Thread,會隱式持有一個外部類的引用LeakActivity,從而致使內存泄漏。同理,如果這個Thread做爲LeakActivity的內部類而不是匿名內部類,他一樣會持有外部類的引用而致使內存泄漏。在這裏只須要將爲Thread匿名類定義成靜態的內部類便可(靜態的內部類不會持有外部類的一個隱式引用)。架構

public class LeakActivity extends AppCompatActivity {       @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_leak);         leakFun();     }       private void leakFun(){         new Thread(new Runnable() {             @Override             public void run() {                 try {                     Thread.sleep(Integer.MAX_VALUE);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         });     } }  複製代碼

情景三:動畫app

在屬性動畫中有一類無限循環動畫,若是在Activity中播放這類動畫而且在onDestroy中去中止動畫,那麼這個動畫將會一直播放下去,這時候Activity會被View所持有,從而致使Activity沒法被釋放。解決此類問題則是須要早Activity中onDestroy去去調用objectAnimator.cancel()來中止動畫。框架

public class LeakActivity extends AppCompatActivity {       private TextView textView;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_leak);         textView = (TextView)findViewById(R.id.text_view);         ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);         objectAnimator.setRepeatCount(ValueAnimator.INFINITE);         objectAnimator.start();     } }  複製代碼

情景四:Handlereclipse

對於Handler的內存泄漏在(Android的消息機制——Handler的工做過程)[http://blog.csdn.net/ljd2038/article/details/50889754]這篇文章中已經詳細介紹,就不在贅述。

情景五:第三方庫使用不當

對於EventBus,RxJava等一些第三開源框架的使用,如果在Activity銷燬以前沒有進行解除訂閱將會致使內存泄漏。

使用MAT檢測內存泄漏

對於常見的內存泄露進行介紹完之後,在這裏再看一下使用MAT(Memory Analysis Tool)來檢測內存泄露。MAT的下載地址爲:http://www.eclipse.org/mat/downloads.php。

下面來看一段會致使內存泄露的錯誤代碼。

public class LeakActivity extends AppCompatActivity {       @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_leak);         EventBus.getDefault().register(this);     }       @Subscribe     public void subscriber(String s){       } }  複製代碼

在上面這段代碼中有會致使內存泄漏,緣由是EventBus沒有解除註冊。下面就以這段代碼爲例來看一下如何分析內存泄漏。

打開AndroidStudio中的Monitors能夠看到以下界面。  

打開AndroidStudio中的Monitors

在這裏能夠看到在應用剛啓動的時候,所佔用的內存爲15M,而後咱們如今開始操做APP,反覆進入退出LeakActicity。點擊上如中的GC按鈕。這時候咱們在看一下內存使用狀況。

內存使用狀況

在這裏咱們能夠看到,內存一直在持續增長,已經達到33M,而且沒法被GC所回收。因此咱們能夠判斷,這時候必然出現內存泄漏的情形。那麼如今再點擊Dump Java Heap按鈕,在captures窗口看到生成得hprof文件。但這時候所生成的hprof文件不是標準格式的,咱們須要經過SDK所提供的工具hprof-conv進行轉化,該工具在SDK的platform-tools目錄下。執行命令以下:

複製代碼
  1. hprof-conv XXX.hprof converted-dump.hprof

固然在AndroidStudio中能夠省去這一步,能夠直接導出標準格式的hprof文件。

直接導出標準格式的hprof文件

這時候能夠經過MAT工具來打開導出的hprof文件。打開界面以下圖所示:

能夠經過MAT工具來打開導出的hprof文件

在MAT中咱們最經常使用的就是Histogram和Dominator Tree,他們分別對應上圖中的A和B按鈕。Histogram能夠看出內存中不一樣類型的buffer的數量和佔用內存的大小,而Dominator Tree則是把內存中的對象按照從大到小的順序進行排序,而且能夠分析對象之間的引用關係。在這裏再來介紹一下MAT中兩個符號的含義。

  • ShallowHeap:對象自身佔用的內存大小,不包括他引用的對象
  • RetainedHeap:對象自身佔用的內存大小而且加上它直接或者間接引用對象的大小

Histogram

因爲在Android中通常內存泄漏大多出如今Acivity中,這時候能夠點擊Histogram按鈕,並搜索Activity。

點擊Histogram按鈕,並搜索Activity

在這裏能夠看出LeakActivity存在69個對象,基本上能夠判定存在內存泄漏的情形,這時候即可以經過查看GC對象的引用鏈來進行分析。點擊鼠標右鍵選擇Merge Shortest paths to GC Roots並選擇exclude weak/soft references來排除弱引用和軟引用。

點擊鼠標右鍵選擇Merge Shortest paths to GC Roots並選擇exclude weak/soft references來排除弱引用和軟引用。

在排除軟引用和弱引用之後以下圖所示:

在排除軟引用和弱引用之後

在這裏能夠看出因爲EventBus致使的LeakActivity內存泄漏。

在Histogram中還能夠查看一個對象包含了那些對象的引用。例如,如今要查看LeakActivity所包含的引用,能夠點擊鼠標右鍵,選擇list objects中的with incoming reference。而with outcoming reference表示選中對象持有那些對象的引用。

點擊鼠標右鍵,選擇list objects中的with incoming reference

Dominator Tree

如今咱們點擊這時候能夠點擊Dominator Tree按鈕,並搜索Activity。能夠看到以下圖所示:

點擊Dominator Tree按鈕,並搜索Activity

在這裏能夠看到存在大量的LeakActivity。而後點擊鼠標右鍵選擇Path To GC Roots->exclude weak/soft references來排除弱引用和軟引用。

點擊鼠標右鍵選擇Path To GC Roots->exclude weak/soft references來排除弱引用和軟引用

以後能夠看到以下結果,依然是EventBus致使的內存泄漏:

EventBus致使的內存泄漏

總結

內存泄漏每每被咱們所忽略,可是當大量的內存泄漏之後致使OOM。它所形成的影響也是不容小覷的。固然除了上述內存泄漏的分析覺得咱們還能夠經過LeakCanary來分析內存泄漏。對於LeakCanary的使用在這裏就不在進行詳細介紹。

加羣免費領取安卓進階學習視頻,源碼,面試資料,羣內有大牛一塊兒交流討論技術;點擊連接加入羣聊【Android技術交流】:https://jq.qq.com/?_wv=1027&k=55YgqsK。 (包括java基礎與原理,自定義控件、NDK、架構設計、混合式開發(Flutter,Weex)、性能優化、完整商業項目開發等)

相關文章
相關標籤/搜索