Andriod-記憶體洩露-分析-解決方案
Activity 洩漏
我們第一個需要修復的問題就是 Activity 洩漏,我們先來看看記憶體洩漏是怎麼發生的。 Activity 洩漏通常是記憶體洩漏的一種。為什麼會洩漏呢?如果你持有一個未使用的 Activity 的引用,其實也就持有了 Activity 的佈局,自然也就包含了所有的 View。最棘手的是持有靜態引用。別忘了,Activity 和 Fragment 都有自己的生命週期。一旦我們持有了靜態引用,Activity 和 Fragment 就不會被垃圾回收器清理掉了。這就是為什麼靜態引用很危險。
m_staticActivity = staticFragment.getActivity ()
我看過太多次這樣的程式碼了。
另外,洩漏 Listener 也是經常會發生的事情。比如說,我有下面的程式碼。LeakActivity
繼承自Activity
,我們有一個單例:NastyManager
,當我們通過 addListener(this)
將
Activity 作為 Listener 和 NastyManager 繫結起來的時候,不好的事情就發生了。
public class LeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
NastyManager.getInstance().addListener(this);
}
}
想要修復這樣的 Bug,其實相當簡單,就是在你的 Acitivity 被銷燬的時候,將他和 NastyManager
取消掉繫結就好了。
@Override
public void onDestroy() {
super.onDestroy();
NastyManager.getInstance().removeListener(this);
}
相對上面的解決方案,我們自然還有更好的。比如我們真的需要用到單例嗎?通常,並不需要。不過某些時候可能真的很需要。我們得權衡和設計。不過無論如何,記住,當 Activity 銷燬的時候,在單例中移除掉對 Activity 的引用
儘管它看起來很短,但是隻要它還存活著,那麼包含它的 Activity 就會存活著。如果你不信我,在 VM 裡試試看。這就是另一個記憶體洩漏的案例:Activity 內部的 Handler。
public class MainActivity extends Activity {
//...
Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
}
}
Handler 是個很常用也很有用的類,非同步,執行緒安全等等。如果有下面這樣的程式碼,會發生什麼呢?handler.postDeslayed
,假設 delay 時間是幾個小時…
這意味著什麼?意味著只要 handler 的訊息還沒有被處理結束,它就一直存活著,包含它的 Activity 就跟著活著。我們來想辦法修復它,修復的方案是WeakReference
,也就是所謂的弱引用。垃圾回收器在回收的時候,是會忽視掉弱引用的,所以包含它的 Activity
會被正常清理掉。大概程式碼如下:
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) {
}
//...
}
概括來說:我們有個內部類,就像 Handler,內部非靜態類是不能脫離所屬類而單獨存活的,Android 裡通常是 Activity。所以,看看你的程式碼裡的內部類,確保他們沒有出現記憶體洩漏。
相比非靜態內部類,最好使用靜態內部類。區別就是靜態內部類不依賴所屬類,他們擁有不同的生命週期。我經常 見到類似的原因引起的記憶體洩露。
如何避免 Activity 洩漏?
-
移除掉所有的靜態引用。
-
考慮用 EventBus 來解耦 Listener。
-
記著在不需要的時候,解除 Listener 的繫結。
-
儘量用靜態內部類。
-
做 Code Review。個人經驗:Code Review 能很早的發現記憶體洩漏。
-
瞭解你程式的結構。
-
用類似 MAT,Eclipse Analyzer,LeakCanary 這樣的工具分析記憶體。
-
在 Callback 裡列印 Log。