1. 程式人生 > >[譯]Android防止記憶體洩漏的八種方法(下)

[譯]Android防止記憶體洩漏的八種方法(下)

原文網址:http://www.jianshu.com/p/c5ac51d804fa?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

原文地址。

在上一篇Android記憶體洩漏的八種可能(上)中,我們討論了八種容易發生記憶體洩漏的程式碼。其中,尤其嚴重的是洩漏Activity物件,因為它佔用了大量系統記憶體。不管記憶體洩漏的程式碼表現形式如何,其核心問題在於:

在Activity生命週期之外仍持有其引用。

幸運的是,一旦洩漏發生且被定位到了,修復方法是相當簡單的。

Static Actitivities

這種洩漏

private static MainActivity activity;

void setStaticActivity() {
    activity = this;
}

構造靜態變數持有Activity物件很容易造成記憶體洩漏,因為靜態變數是全域性存在的,所以當MainActivity生命週期結束時,引用仍被持有。這種寫法開發者是有理由來使用的,所以我們需要正確的釋放引用讓垃圾回收機制在它被銷燬的同時將其回收。

弱引用不會阻止物件的記憶體釋放,所以即使有弱引用的存在,該物件也可以被回收。

 private static WeakReference<MainActivity> activityReference;

    void
setStaticActivity()
{ activityReference = new WeakReference<MainActivity>(this); }

Static Views

靜態變數持有View

private static View view;

void setStaticView() {
    view = findViewById(R.id.sv_button);
}

由於View持有其宿主Activity的引用,導致的問題與Activity一樣嚴重。弱引用是個有效的解決方法,然而還有另一種方法是在生命週期結束時清除引用,Activity#onDestory()

方法就很適合把引用置空。

 private static View view;

@Override
public void onDestroy() {
    super.onDestroy();
    if (view != null) {
        unsetStaticView();
    }
}

void unsetStaticView() {
    view = null;
}

Inner Class

這種洩漏

private static Object inner;

void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

於上述兩種情況相似,開發者必須注意用非靜態內部類,因為靜態內部類持有外部類的隱式引用,容易導致意料之外的洩漏。然而內部類可以訪問外部類的私有變數,只要我們注意引用的生命週期,就可以避免意外的發生。

避免靜態變數

這樣持有內部類的成員變數是可以的。

private Object inner;

void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

Anonymous Classes

前面我們看到的都是持有全域性生命週期的靜態成員變數引起的,直接或間接通過鏈式引用Activity導致的洩漏。這次我們用AsyncTask

void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}

Handler

void createHandler() {
    new Handler() {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }.postDelayed(new Runnable() {
        @Override public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

Thread

void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

全部都是因為匿名類導致的。匿名類是特殊的內部類——寫法更為簡潔。當需要一次性的特殊子類時,Java提供的語法糖能讓表示式最少化。這種很贊很偷懶的寫法容易導致洩漏。正如使用內部類一樣,只要不跨越生命週期,內部類是完全沒問題的。但是,這些類是用於產生後臺執行緒的,這些Java執行緒是全域性的,而且持有建立者的引用(即匿名類的引用),而匿名類又持有外部類的引用。執行緒是可能長時間執行的,所以一直持有Activity的引用導致當銷燬時無法回收。
這次我們不能通過移除靜態成員變數解決,因為執行緒是於應用生命週期相關的。為了避免洩漏,我們必須捨棄簡潔偷懶的寫法,把子類宣告為靜態內部類。

靜態內部類不持有外部類的引用,打破了鏈式引用。

所以對於AsyncTask

private static class NimbleTask extends AsyncTask<Void, Void, Void> {
    @Override protected Void doInBackground(Void... params) {
        while(true);
    }
}

void startAsyncTask() {
    new NimbleTask().execute();
}

Handler

private static class NimbleHandler extends Handler {
    @Override public void handleMessage(Message message) {
        super.handleMessage(message);
    }
}

private static class NimbleRunnable implements Runnable {
    @Override public void run() {
        while(true);
    }
}

void createHandler() {
    new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1);
}

TimerTask

private static class NimbleTimerTask extends TimerTask {
    @Override public void run() {
        while(true);
    }
}

void scheduleTimer() {
    new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
}

但是,如果你堅持使用匿名類,只要在生命週期結束時中斷執行緒就可以。

private Thread thread;

@Override
public void onDestroy() {
    super.onDestroy();
    if (thread != null) {
        thread.interrupt();
    }
}

void spawnThread() {
    thread = new Thread() {
        @Override public void run() {
            while (!isInterrupted()) {
            }
        }
    }
    thread.start();
}

Sensor Manager

這種洩漏

void registerListener() {
    SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
    sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

使用Android系統服務不當容易導致洩漏,為了Activity與服務互動,我們把Activity作為監聽器,引用鏈在傳遞事件和回撥中形成了。只要Activity維持註冊監聽狀態,引用就會一直持有,記憶體就不會被釋放。

在Activity結束時登出監聽器

private SensorManager sensorManager;
private Sensor sensor;

@Override
public void onDestroy() {
    super.onDestroy();
    if (sensor != null) {
        unregisterListener();
    }
}

void unregisterListener() {
    sensorManager.unregisterListener(this, sensor);
}

總結

Activity洩漏的案例我們已經都走過一遍了,其他都大同小異。建議日後遇到類似的情況時,就使用相應的解決方法。記憶體洩漏只要發生過一次,通過詳細的檢查,很容易解決並防範於未然。

是時候做最佳實踐者了!



文/豆沙包lo(簡書作者)
原文連結:http://www.jianshu.com/p/c5ac51d804fa
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。