Android 內存泄漏案例和解析

Android 編程所使用的 Java 是一門使用垃圾收集器(GC, garbage collection)來自動管理內存的語言,它使得咱們再也不須要手動調用代碼來進行內存回收。那麼它是如何判斷的呢?簡單說,若是一個對象,從它的根節點開始不可達的話,那麼這個對象就是沒有引用的了,是會被垃圾收集器回收的,其中,所謂的 「根節點」 每每是一個線程,好比主線程。所以, **若是一個對象從它的根節點開始是可達的有引用的,但實際上它已經沒有再使用了,是無用的,這樣的對象就是內存泄漏的對象 **,它會在內存中佔據咱們應用程序本來就不是不少的內存,致使程序變慢,甚至內存溢出(OOM)程序崩潰。git

內存泄漏的緣由並不難理解,但僅管知道它的存在,每每咱們仍是會不知覺中寫出導致內存泄漏的代碼。在 Android 編程中,也是有許多情景容易致使內存泄漏,如下將一一列舉一些我所知道的內存泄漏案例,從這些例子中應該能更加直觀瞭解怎麼致使了內存泄漏,從而在編程過程當中去避免。github

靜態變量形成內存泄漏編程

首先,比較簡單的一種狀況是,靜態變量導致內存泄漏,說到靜態變量,咱們至少得了解其生命週期才能完全明白。靜態變量的生命週期,起始於類的加載,終止於類的釋放。對於 Android 而言,程序也是從一個 main 方法進入,開始了主線程的工做,若是一個類在主線程或旁枝中被使用到,它就會被加載,反過來講,假如一個類存在於咱們的項目中,但它從未被咱們使用過,算是個孤島,這時它是沒有被加載的。一旦被加載,只有等到咱們的 Android 應用進程結束它纔會被卸載。異步

因而,當咱們在 Activity 中聲明一個靜態變量引用了 Activity 自身,就會形成內存泄漏:ide

public class LeakActivity extends AppCompatActivity {

    private static Context sContext;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        sContext = this;
    }
}

這樣的代碼會致使當這個 Activity 結束的時候,sContext 仍然持有它的引用,導致 Activity 沒法回收。解決辦法就是在這個 Activity 的 onDestroy 時將 sContext 的值置空,或者避免使用靜態變量這樣的寫法。oop

一樣的,若是一個 Activity 的靜態 field 變量 **內部 **得到了當前 Activity 的引用,好比咱們常常會把 this 傳給 View 之類的對象,這個對象如果靜態的,而且沒有在 Activity 生命週期結束以前置空的話,也會致使一樣的問題。post

非靜態內部類和匿名內部類形成內存泄漏動畫

也是一個很常見的情景,常常會遇到的 Handler 問題就是這樣一種狀況,若是咱們在 field 聲明一個 Handler 變量:this

private Handler mHandler = new Handler() {
    @Override public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

因爲在 Java 中, **非靜態內部類(包括匿名內部類,好比這個 Handler 匿名內部類)會引用外部類對象 this(好比 Activity),而靜態的內部類則不會引用外部類對象。 **因此這裏 Handler 會引用 Activity 對象,當它使用了 postDelayed 的時候,若是 Activity 已經 finish 了,而這個 handler 仍然引用着這個 Activity 就會導致內存泄漏,由於 **這個 handler 會在一段時間內繼續被 main Looper 持有,致使引用仍然存在 **,在這段時間內,若是內存吃緊至超出,就很危險了。線程

解決辦法就是你們都知道的使用 **靜態內部類 **加 WeakReference:

private StaticHandler mHandler = new StaticHandler(this);

public static class StaticHandler extends Handler {
    private final WeakReference<Activity> mActivity;

    public StaticHandler(Activity activity) {
        mActivity = new WeakReference<Activity>(activity);
    }

    @Override public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
}

另外,綜合上面兩種狀況,若是一個變量,既是靜態變量,並且是 **非靜態的內部類對象, **那麼也會形成內存泄漏:

public class LeakActivity extends AppCompatActivity {

    private static Hello sHello;

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

        sHello = new Hello();
    }

    public class Hello {}
}

注意,這裏咱們定義的 Hello 雖然是空的,但它是一個非靜態的內部類,因此它必然會持有外部類即 LeakActivity.this 引用,致使 sHello 這個靜態變量一直持有這個 Activity,因而結果就和第一個例子同樣,Activity 沒法被回收。

到這裏你們應該能夠看出,內存泄漏常常和靜態變量有關。和靜態變量有關的,還有一種常見情景,就是使用 **單例模式沒有解綁導致內存泄漏 **,單例模式的對象常常是和咱們的應用相同的生命週期,若是咱們使用 EventBus 或 Otto 並生成單例,註冊了一個 Activity 而沒有在頁面結束的時候進行解除註冊,那麼單例會一直持有咱們的 Activity,這個 Activity 雖然沒有使用了,但會一直佔用着內存。

屬性動畫形成內存泄漏

另外當咱們使用屬性動畫,咱們須要調用一些方法將動畫中止,特別是無限循環的動畫,不然也會形成內存泄漏,好在使用 View 動畫並不會出現內存泄漏,估計 View 內部有進行釋放和中止。

RxJava 使用不當形成內存泄漏

最後說一說 RxJava 使用不當形成的內存泄漏,RxJava 是一個很是易用且優雅的異步操做庫。對於異步的操做,若是沒有及時取消訂閱,就會形成內存泄漏:

Observable.interval(1, TimeUnit.SECONDS)
          .subscribe(new Action1<Long>() {
              @Override public void call(Long aLong) {
                  // pass
              }
          });

一樣是匿名內部類形成的引用無法被釋放,使得若是在 Activity 中使用就會致使它沒法被回收,即便咱們的 Action1 看起來什麼也沒有作。解決辦法就是接收 subscribe 返回的 Subscription 對象,在 Activity onDestroy 的時候將其取消訂閱便可:

public class LeakActivity extends AppCompatActivity {

    private Subscription mSubscription;

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

        mSubscription = Observable.interval(1, TimeUnit.SECONDS)
            .subscribe(new Action1<Long>() {
              @Override public void call(Long aLong) {
                  // pass
              }
            });
    }

    @Override protected void onDestroy() {
        super.onDestroy();
        mSubscription.unsubscribe();
    }
}

除了以上這種解決方式以外,還有一種解決方式就是經過 RxJava 的 compose 操做符和 Activity 的生命週期掛鉤,咱們可使用一個很方便的第三方庫叫作 RxLifecycle 來快捷作到這點,使用起來就像這樣:

public class MyActivity extends RxActivity {
    @Override
    public void onResume() {
        super.onResume();
        myObservable
            .compose(bindToLifecycle())
            .subscribe();
    }
}

另外,它還提供了和 View 的便捷綁定,詳情能夠點擊我提供的連接進行了解,這裏很少說了。

總結來講,仍然是前面說的內部類或匿名內部類引用了外部類形成了內存泄漏,因此在實際編程過程當中,若是涉及此類問題或者線程操做的,應該特別當心,極可能不知不覺中就寫出了帶內存泄漏的代碼了。

內存泄漏的檢測

前面說了很多內存泄漏的場景和對應的解決辦法,但若是咱們不知不覺中寫出了帶有內存泄漏隱患的代碼怎麼辦,面對這個問題,其實到如今,咱們是很幸運的,由於有不少相關的檢查方式或組件能夠選擇,好比最簡單的:觀察 Memory Monitor 內存走勢圖,能夠或多或少知道內存狀況,但若是要精確地追蹤到內存泄漏點,這裏特別推薦偉大的 Square 公司開源的 LeakCanary 方案,LeakCanary 能夠作到很是簡單方便、低侵入性地捕獲內存泄漏代碼,甚至不少時候你能夠捕捉到 Android 官方組件的內存泄漏代碼,具體使用你們能夠自行參看其說明,因爲本文主要想講的是內存泄漏的緣由和一些常見場景,對於檢測,這裏就很少說啦

相關文章
相關標籤/搜索