Android記憶體洩漏場景

Fxymine4ever發表於2019-03-09

首先我們提出一個問題,什麼是記憶體洩漏? 記憶體洩漏,通俗得來講就是“沒有用的物件無法被回收”

然後我們再提出一個問題,記憶體洩露會導致什麼情況?
肯定是記憶體溢位,然後程式崩潰啊!

區別

相信初學者可能不太清楚記憶體溢位和記憶體洩漏的區別。

  • 記憶體溢位:程式使用的空間大於原本系統給它申請的空間。
  • 記憶體洩漏:在new了物件之後,沒有使用這個物件了,但是又沒有被回收,一直佔用著記憶體。

儲備知識

要想了解記憶體洩露的知識,首先我們要清楚以下的知識點

簡單判斷

如何判斷,只用記住一點:A類例項引用B類例項,而A類例項的生命週期長於B類例項的生命週期。

洩漏場景

在Android開發中,記憶體洩漏的地方還是挺多的,有時候稍不注意就寫出了一個記憶體洩漏的程式碼。所以說我們要熟記哪些地方容易發生記憶體洩漏,在程式碼Review的情況下很容易檢查出來。

單例引起的記憶體洩漏

單例模式使用的地方非常多,它的生命週期常常伴隨著App的一生,所以說也十分容易造成記憶體洩漏。

例如單例模式中引用Activity的Context,而單例模式的生命週期長於Activity。這裡單例模式引用Activity的例項,當Activity被銷燬,Activity無法被回收,造成記憶體洩露。

public class Single {
  private static Single instance;
  private Context context;
  private Single(Context context) {
    this.context = context;
  }
  public static Single getInstance(Context context) {
    if (instance == null) {
        instance = new Single(context);
    }
    return instance;
  }
}
複製程式碼

值得一提的是,如果這裡引用的Application的Context,將無任何影響。因為Application的生命週期與單例模式同樣長。

集合的記憶體洩漏

在靜態集合裡面新增物件,新增完成之後該集合將會一直引用此物件,該物件無法被釋放。(不過我們也寫不出這樣沙雕的程式碼來!

static List<Object> objectList = new ArrayList<>();
   for (int i = 0; i < 10; i++) {
       Object obj = new Object();
       objectList.add(obj);
       obj = null;
    }
複製程式碼

解決方法:在使用完該集合之後,將集合清空。

匿名內部類以及非靜態內部類

特點:匿名類和非靜態內部類都持有外部類的引用

匿名內部類
Handler洩漏

匿名內部類引起的記憶體洩露,最典型的例子就是Handler洩漏。當Handler的訊息沒有傳送完畢,Activity就被銷燬了,此時Activity無法被即時回收。

public class MainActivity extends Activity{
    
    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
           //do something...
        }
    };
    
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
複製程式碼

如何解決Handler洩漏呢?我們用static修飾Handler,這樣Hanlder的生命週期就與Activity無關了。如果想引用Activity例項,這裡可以用一個弱引用來獲取。或者可以在Activity 的onDestroy() 方法中移除所有的訊息 handler.removeCallbacksAndMessages(null);

public class MainActivity extends Activity{
    private final MyHandler handler = new MyHandler(this);
    
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    
    private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
          MainActivity activity = mActivity.get();
        }
  }
}
複製程式碼
Thread洩漏

在Activity中new Thread時,如果在子執行緒做耗時操作,當Activity被銷燬後,子執行緒的工作並未完成,此時會記憶體洩漏。

public class MainActivity extends Activity{
    
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
複製程式碼

這裡同樣可以繼承 Thread 實現靜態內部類來解決。

static修飾的成員變數

如果成員變數被宣告為 static,其生命週期將與整個app程式生命週期一樣。

Stream未關閉

在呼叫了流之後,一定要記得關閉流。用到流的地方一般都是檔案操作,虛擬機器無法通過垃圾回收來釋放這些資源。

其他洩漏

例如service忘記解除繫結,broadcastReceiver忘記解除訂閱,EventBus忘記解除訂閱等。

常用的檢測記憶體洩漏的工具

光憑肉眼我們其實只能找出比較明顯的記憶體洩露點,還有許多隱藏得比較深的記憶體洩露。那麼我們如何找到這些點呢?當然是利用工具。

  • Android Lint:Android Studio提供的程式碼掃描分析工具
  • Leakcanary: Square 公司開源的「Android 和 Java 的記憶體洩漏檢測庫」

總結

在Android系統中,每個App最多能分配大約只有100-200MB的記憶體空間,因為記憶體不夠,溢位而引起的程式崩潰還是不在少數。所以說,日常開發中還是要千萬注意記憶體洩露。

相關文章