原文出處: Drakeet (@drakeet) html
Android 編程所使用的 Java 是一門使用垃圾收集器(GC, garbage collection)來自動管理內存的語言,它使得咱們再也不須要手動調用代碼來進行內存回收。那麼它是如何判斷的呢?簡單說,若是一個對象,從它的根節點開始不可達的話,那麼這個對象就是沒有引用的了,是會被垃圾收集器回收的,其中,所謂的 「根節點」 每每是一個線程,好比主線程。所以,若是一個對象從它的根節點開始是可達的有引用的,但實際上它已經沒有再使用了,是無用的,這樣的對象就是內存泄漏的對象,它會在內存中佔據咱們應用程序本來就不是不少的內存,致使程序變慢,甚至內存溢出(OOM)程序崩潰。android
內存泄漏的緣由並不難理解,但僅管知道它的存在,每每咱們仍是會不知覺中寫出導致內存泄漏的代碼。在 Android 編程中,也是有許多情景容易致使內存泄漏,如下將一一列舉一些我所知道的內存泄漏案例,從這些例子中應該能更加直觀瞭解怎麼致使了內存泄漏,從而在編程過程當中去避免。
git
首先,比較簡單的一種狀況是,靜態變量導致內存泄漏,說到靜態變量,咱們至少得了解其生命週期才能完全明白。靜態變量的生命週期,起始於類的加載,終止於類的釋放。對於 Android 而言,程序也是從一個 main 方法進入,開始了主線程的工做,若是一個類在主線程或旁枝中被使用到,它就會被加載,反過來講,假如一個類存在於咱們的項目中,但它從未被咱們使用過,算是個孤島,這時它是沒有被加載的。一旦被加載,只有等到咱們的 Android 應用進程結束它纔會被卸載。github
因而,當咱們在 Activity 中聲明一個靜態變量引用了 Activity 自身,就會形成內存泄漏:編程
Java架構
1異步 2ide 3oop 4post 5 6 7 8 9 10 |
public class LeakActivity extends AppCompatActivity {
private static Context sContext;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); sContext = this; } } |
這樣的代碼會致使當這個 Activity 結束的時候,sContext 仍然持有它的引用,導致 Activity 沒法回收。解決辦法就是在這個 Activity 的 onDestroy 時將 sContext 的值置空,或者避免使用靜態變量這樣的寫法。
一樣的,若是一個 Activity 的靜態 field 變量內部得到了當前 Activity 的引用,好比咱們常常會把 this 傳給 View 之類的對象,這個對象如果靜態的,而且沒有在 Activity 生命週期結束以前置空的話,也會致使一樣的問題。
也是一個很常見的情景,常常會遇到的 Handler 問題就是這樣一種狀況,若是咱們在 field 聲明一個 Handler 變量:
Java
1 2 3 4 5 |
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; |
因爲在 Java 中,非靜態內部類(包括匿名內部類,好比這個 Handler 匿名內部類)會引用外部類對象(好比 Activity),而靜態的內部類則不會引用外部類對象。因此這裏 Handler 會引用 Activity 對象,當它使用了 postDelayed 的時候,若是 Activity 已經 finish 了,而這個 handler 仍然引用着這個 Activity 就會導致內存泄漏,由於這個 handler 會在一段時間內繼續被 main Looper 持有,致使引用仍然存在,在這段時間內,若是內存吃緊至超出,就很危險了。
解決辦法就是你們都知道的使用靜態內部類加 WeakReference:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private StaticHandler mHandler = new StaticHandler(this);
public static class StaticHandler extends Handler { private final WeakReference mActivity;
public StaticHandler(Activity activity) { mActivity = new WeakReference(activity); }
@Override public void handleMessage(Message msg) { super.handleMessage(msg); } } |
另外,綜合上面兩種狀況,若是一個變量,既是靜態變量,並且是非靜態的內部類對象,那麼也會形成內存泄漏:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class LeakActivity extends AppCompatActivity {
private static Hello sHello;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak);
sHello = new Hello(); }
public class Hello {} } |
注意,這裏咱們定義的 Hello 雖然是空的,但它是一個非靜態的內部類,因此它必然會持有外部類即 LeakActivity.this 引用,致使 sHello 這個靜態變量一直持有這個 Activity,因而結果就和第一個例子同樣,Activity 沒法被回收。
到這裏你們應該能夠看出,內存泄漏常常和靜態變量有關。和靜態變量有關的,還有一種常見情景,就是使用單例模式沒有解綁導致內存泄漏,單例模式的對象常常是和咱們的應用相同的生命週期,若是咱們使用 EventBus 或 Otto 並生成單例,註冊了一個 Activity 而沒有在頁面結束的時候進行解除註冊,那麼單例會一直持有咱們的 Activity,這個 Activity 雖然沒有使用了,但會一直佔用着內存。
另外當咱們使用屬性動畫,咱們須要調用一些方法將動畫中止,特別是無限循環的動畫,不然也會形成內存泄漏,好在使用 View 動畫並不會出現內存泄漏,估計 View 內部有進行釋放和中止。
最後說一說 RxJava 使用不當形成的內存泄漏,RxJava 是一個很是易用且優雅的異步操做庫。對於異步的操做,若是沒有及時取消訂閱,就會形成內存泄漏:
Java
1 2 3 4 5 6 |
Observable.interval(1, TimeUnit.SECONDS) .subscribe(new Action1() { @Override public void call(Long aLong) { // pass } }); |
一樣是匿名內部類形成的引用無法被釋放,使得若是在 Activity 中使用就會致使它沒法被回收,即便咱們的 Action1 看起來什麼也沒有作。解決辦法就是接收 subscribe 返回的 Subscription 對象,在 Activity onDestroy 的時候將其取消訂閱便可:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class LeakActivity extends AppCompatActivity {
private Subscription mSubscription;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak);
mSubscription = Observable.interval(1, TimeUnit.SECONDS) .subscribe(new Action1() { @Override public void call(Long aLong) { // pass } }); }
@Override protected void onDestroy() { super.onDestroy(); mSubscription.unsubscribe(); } } |
除了以上這種解決方式以外,還有一種解決方式就是經過 RxJava 的 compose 操做符和 Activity 的生命週期掛鉤,咱們可使用一個很方便的第三方庫叫作 RxLifecycle 來快捷作到這點,使用起來就像這樣:
Java
1 2 3 4 5 6 7 8 9 |
public class MyActivity extends RxActivity { @Override public void onResume() { super.onResume(); myObservable .compose(bindToLifecycle()) .subscribe(); } } |
另外,它還提供了和 View 的便捷綁定,詳情能夠點擊我提供的連接進行了解,這裏很少說了。
總結來講,仍然是前面說的內部類或匿名內部類引用了外部類形成了內存泄漏,因此在實際編程過程當中,若是涉及此類問題或者線程操做的,應該特別當心,極可能不知不覺中就寫出了帶內存泄漏的代碼了。
前面說了很多內存泄漏的場景和對應的解決辦法,但若是咱們不知不覺中寫出了帶有內存泄漏隱患的代碼怎麼辦,面對這個問題,其實到如今,咱們是很幸運的,由於有不少相關的檢查方式或組件能夠選擇,好比最簡單的:觀察 Memory Monitor 內存走勢圖,能夠或多或少知道內存狀況,但若是要精確地追蹤到內存泄漏點,這裏特別推薦偉大的 Square 公司開源的 LeakCanary 方案,LeakCanary 能夠作到很是簡單方便、低侵入性地捕獲內存泄漏代碼,甚至不少時候你能夠捕捉到 Android 官方組件的內存泄漏代碼,具體使用你們能夠自行參看其說明,因爲本文主要想講的是內存泄漏的緣由和一些常見場景,對於檢測,這裏就很少說啦
問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com 下載問啊APP,參與官方懸賞,賺百元現金。
QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!