Android記憶體洩漏終極解決篇(下)

NO IMAGE

一、概述

Android記憶體洩漏終極解決篇(上)中我們介紹瞭如何檢查一個App是否存在記憶體洩漏的問題,本篇將總結典型的記憶體洩漏的程式碼,並給出對應的解決方案。記憶體洩漏的主要問題可以分為以下幾種型別:

靜態變數引起的記憶體洩漏
非靜態內部類引起的記憶體洩漏
資源未關閉引起的記憶體洩漏

二、靜態變數引起的記憶體洩漏

在java中靜態變數的生命週期是在類載入時開始,類解除安裝時結束。換句話說,在android中其生命週期是在程序啟動時開始,程序死亡時結束。所以在程式的執行期間,如果程序沒有被殺死,靜態變數就會一直存在,不會被回收掉。如果靜態變數強引用了某個Activity中變數,那麼這個Activity就同樣也不會被釋放,即便是該Activity執行了onDestroy(不要將執行onDestroy和被回收劃等號)。這類問題的解決方案為:1.尋找與該靜態變數生命週期差不多的替代物件。2.若找不到,將強引用方式改成弱引用。比較典型的例子如下:

單例引起的Context記憶體洩漏


public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
mInstance = new IMManager(context);
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}

當呼叫getInstance時,如果傳入的context是Activity的context。只要這個單例沒有被釋放,這個Activity也不會被釋放。

解決方案
傳入Application的context,因為Application的context的生命週期比Activity長,可以理解為Application的context與單例的生命週期一樣長,傳入它是最合適的。


public class IMManager {
private Context context;
private static IMManager mInstance;
public static IMManager getInstance(Context context) {
if (mInstance == null) {
synchronized (IMManager.class) {
if (mInstance == null)
//將傳入的context轉換成Application的context
mInstance = new IMManager(context.getApplicationContext());
}
}
return mInstance;
}
private IMManager(Context context) {
this.context = context;
}
}

三、非靜態內部類引起的記憶體洩漏

在java中,建立一個非靜態的內部類例項,就會引用它的外圍例項。如果這個非靜態內部類例項做了一些耗時的操作,就會造成外圍物件不會被回收,從而導致記憶體洩漏。這類問題的解決方案為:1.將內部類變成靜態內部類 2.如果有強引用Activity中的屬性,則將該屬性的引用方式改為弱引用。3.在業務允許的情況下,當Activity執行onDestory時,結束這些耗時任務。

內部執行緒造成的記憶體洩漏


public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
public void test() {
//匿名內部類會引用其外圍例項LeakAty.this,所以會導致記憶體洩漏
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}

解決方案
將非靜態匿名內部類修改為靜態匿名內部類


public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
test();
}
//加上static,變成靜態匿名內部類
public static void test() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}

Handler引起的記憶體洩漏


public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
fetchData();
}
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 0:
// 重新整理資料
break;
default:
break;
}
};
};
private void fetchData() {
//獲取資料
mHandler.sendEmptyMessage(0);
}
}

mHandler 為匿名內部類例項,會引用外圍物件LeakAty.this,如果該Handler在Activity退出時依然還有訊息需要處理,那麼這個Activity就不會被回收。

解決方案


public class LeakAty extends Activity {
private TextView tvResult;
private MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
tvResult = (TextView) findViewById(R.id.tvResult);
handler = new MyHandler(this);
fetchData();
}
//第一步,將Handler改成靜態內部類。
private static class MyHandler extends Handler {
//第二步,將需要引用Activity的地方,改成弱引用。
private WeakReference<LeakAty> atyInstance;
public MyHandler(LeakAty aty) {
this.atyInstance = new WeakReference<LeakAty>(aty);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LeakAty aty = atyInstance == null ? null : atyInstance.get();
//如果Activity被釋放回收了,則不處理這些訊息
if (aty == null||aty.isFinishing()) {
return;
}
aty.tvResult.setText("fetch data success");
}
}
private void fetchData() {
// 獲取資料
handler.sendEmptyMessage(0);
}
@Override
protected void onDestroy() {
//第三步,在Activity退出的時候移除回撥
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}

四、資源未關閉引起的記憶體洩漏

當使用了BraodcastReceiver、Cursor、Bitmap等資源時,當不需要使用時,需要及時釋放掉,若沒有釋放,則會引起記憶體洩漏。

綜上所述,記憶體洩漏的主要情況為上面的三大型別,最終歸結為一點,就是資源在不需要的時候沒有被釋放掉。所以在編碼的過程中要注意這些細節,提高程式的效能。

您可能感興趣的文章:

Android記憶體洩漏終極解決篇(上)淺談Android應用的記憶體優化及Handler的記憶體洩漏問題Android 記憶體洩漏的幾種可能總結Android 有效的解決記憶體洩漏的問題例項詳解Android效能優化之利用強大的LeakCanary檢測記憶體洩漏及解決辦法Android開發:淺談MVP模式應用與記憶體洩漏問題解決使用Android Studio檢測記憶體洩露(LeakCanary)Android記憶體洩漏排查利器LeakCanary