Android 內存泄漏分析與解決

該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!另外,本系列文章知識可能須要有必定Android開發基礎和項目經驗的同窗才能更好理解,也就是說該系列文章面向的是Android中高級開發工程師。android


前言

上一篇咱們主要上了一個實例來把讀者帶進自定義ViewGroup的大門,只是帶進大門,自定義View的內容還有不少,我以後碰到一些好的自定義View的話必定還來這裏分享。本篇內容咱們來分析App運行過程當中出現的內存泄漏及如何解決。git


內存泄漏概念及其影響

內存泄漏通俗的講是一個本該被回收的對象卻由於某些緣由致使其不能回收。咱們都知道對象是有生命週期的,從生到死,當對象的任務完成以後,由Android系統進行垃圾回收。咱們知道Android系統爲某個App分配的內存是有限的(這個可能根據機型的不一樣而不一樣),當一個應用中產生的內存泄漏比較多時,就不免會致使應用所須要的內存超過這個系統分配的內存限額,最終致使OOM(OutOfMemory)使程序崩潰。github

內存泄漏檢查工具介紹

早在使用Eclipse的時候咱們就知道了MAT性能分析工具,使用MAT固然能檢查內存泄漏,不過使用稍微有些麻煩,我這裏介紹另外一個工具,同時呢,咱們也拋棄了Eclipse,擁抱Android Studio。這個工具名叫LeakCanary。爲何要使用這個工具呢,固然由於其簡單,傻瓜式操做。這個工具是在Github開源的,是Square公司出品的,不是有一句話嘛,Square出品必屬精品,https://github.com/square/leakcanary咱們能夠方便的引用它編程

In your build.gradle:性能優化

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
 }

In your Application class:多線程

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

就是如此簡單,那麼下面咱們就來用一下把 結合下面的內存泄漏場景應用。app

常見的內存泄漏

在咱們平時的開發中可能已經形成了內存泄漏而不自知,下面就羅列其中幾種,看看你的程序裏是否是有這樣的代碼。ide

靜態變量形成的內存泄漏

public class MainActivity extends Activity{
    private static final String TAG = "MainActivity";

    private static Context sContext;
       
    private static View sView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //這裏直接把當前Activity賦值給了靜態變量sContext
        sContext = this;
        //這種寫法和上面的相似
        sView = new View(this);

    }
}

上面這種方法估計小學生都知道會形成內存泄漏,緣由是當MainActivity對象完成任務須要回收時,卻有一個靜態變量引用它(靜態變量的生命週期與Application相同),形成內存泄漏。咱們使用LeakCanary分析就是以下圖工具

當咱們的App發生內存泄漏時會在通知欄顯示通知,點擊該通知可獲得內存泄漏的詳細信息,或者點擊上圖中的Leaks圖標得到App運行過程當中全部的內存泄漏,上面例子中獲得的內存泄漏信息以下圖所示oop

單例模式形成的內存泄漏

上面的內存泄漏太明顯,估計你們都不會這樣寫,可是單例模式就不同了,咱們每每會忽略掉錯誤使用單例模式而形成的泄漏。好比說咱們常在開發中用到的dp轉px,px轉dp等每每會封裝成一個單例類。以下

public class DisplayUtils {
    private static volatile DisplayUtils instance = null;
    private Context mContext;
    private DisplayUtils(Context context){
        this.mContext = context;
    }
    public static DisplayUtils getInstance(Context context){
        if (instance != null){
            synchronized (DisplayUtils.class){
                if (instance !=null){
                    instance = new DisplayUtils(context);
                }
            }
        }

        return instance;
    }

    public int dip2px(float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

而後咱們去調用它

public class SingleActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single);
        //這裏咱們把當前SingleActivity傳入
        DisplayUtils.getInstance(this).dip2px(5);
    }
}

就這樣內存泄漏產生了,咱們能夠看圖。

這個圖和上面的內存泄漏的圖很相像。可是咱們經常忽略了這種內存泄漏,是由於咱們沒有直接使用靜態變量指向傳遞進來的參數,解決辦法要保證Context和AppLication的生命週期同樣,修改後代碼以下:

public class DisplayUtils {
    private static volatile DisplayUtils instance = null;
    private Context mContext;
    private DisplayUtils(Context context){
        //這裏變化了,把當前Context指向個應用程序的Context
        this.mContext = context.getApplicationContext();
    }
    public static DisplayUtils getInstance(Context context){
        if (instance != null){
            synchronized (DisplayUtils.class){
                if (instance !=null){
                    instance = new DisplayUtils(context);
                }
            }
        }

        return instance;
    }

    public int dip2px(float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

非靜態內部類建立靜態實例形成的內存泄漏

咱們在程序中基本上不能避免使用ListView或者RecyclerView,談到這些列表展現的類,那麼咱們的Adapter基本上也是不可缺乏,咱們在優化ListView的Adapter的時候會使用ViewHolder(RecyclerView自己已經作了優化),咱們在使用ViewHolder的使用建議使用靜態內部類。那麼爲何會由此建議呢?這就是咱們下面要談到的。非靜態內部類建立靜態實例可能形成的內存泄漏

public class NonStaticActivity extends AppCompatActivity {
    private static Config sConfig;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_non_static);
        //Config類並非靜態類,
        sConfig = new Config();
    
    }
    
    class Config {
    
    }

}

形成內存泄漏的緣由是內部類會隱式持有外部類的引用,這裏的外部類是NonStaticActivity,然而內部類sConfig又是static靜態變量其生命週期與Application生命週期同樣,因此在NonStaticActivity關閉的時候,內部類靜態實例依然持有對NonStaticActivity的引用,致使NonStaticActivity沒法被回收釋放,引起內存泄漏。
解決辦法就是把內部類生命爲靜態內部類,與外部類解耦。,這也是在使用ViewHolder的使用建議使用靜態內部類的緣由。

WebView形成的內存泄漏

對於使用Android的WebView形成的內存泄漏。我在此建議使用https://github.com/delight-im/Android-AdvancedWebView,使用這個優化後的WebView,按照提示進行操做。

Handler形成的內存泄漏

我在個人項目中使用了handler,此時mHandler會隱式地持有一個外部類對象引用這裏就是MainActivity,當執行postDelayed方法時,該方法會將你的Handler裝入一個Message,並把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理消息,那麼當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,因此致使該Activity的內存資源沒法及時回收,引起內存泄漏。

public class HandlerActivity extends AppCompatActivity {
    private Handler mHandler = new Handler();
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        mTextView = (TextView) findViewById(R.id.text);//模擬內存泄露
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("test");
            }
        }, 5 * 60 * 1000);

    }


}

用LeakCanary能夠看到相似下圖

解決辦法是 在HandlerActivity onDestroy裏面移除消息隊列中全部消息和全部的Runnable。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
    mHandler = null;
}

其餘緣由形成的內存泄漏

形成內存泄漏的緣由有不少,咱們這裏只是列舉了其中比較典型的幾種,固然還有好多緣由會形成內存泄漏,好比資源開啓可是未關閉、多線程等等等等。可是咱們有LeakCanary這個利器哈。

本篇總結

本篇只是稍微介紹了下LeakCanary以及幾種常見的內存泄漏,內存泄漏以及內存性能優化是個持久的過程。我這裏只是向大家介紹其中一種方法。編程無止境,性能優化也是。

下篇預告

好了,咱們下一篇介紹正篇Android的消息機制Looper、Handler、MessageQueue,Message


此致,敬禮

相關文章
相關標籤/搜索