首先我們提出一個問題,什麼是記憶體洩漏? 記憶體洩漏,通俗得來講就是“沒有用的物件無法被回收”
然後我們再提出一個問題,記憶體洩露會導致什麼情況?
肯定是記憶體溢位,然後程式崩潰啊!
區別
相信初學者可能不太清楚記憶體溢位和記憶體洩漏的區別。
- 記憶體溢位:程式使用的空間大於原本系統給它申請的空間。
- 記憶體洩漏:在new了物件之後,沒有使用這個物件了,但是又沒有被回收,一直佔用著記憶體。
儲備知識
要想了解記憶體洩露的知識,首先我們要清楚以下的知識點
- Java的GC(Garbage Collection,垃圾回收)機制。
- Java的記憶體管理機制
詳情可以見 淺談垃圾回收演算法
簡單判斷
如何判斷,只用記住一點: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的記憶體空間,因為記憶體不夠,溢位而引起的程式崩潰還是不在少數。所以說,日常開發中還是要千萬注意記憶體洩露。