Activitys, Threads, & Memory Leaks 在Android編程中,一個公認的難題是在Activity的生命週期如何協調長期啟動並執行任務和避免有可能出現的記憶體流失問題。考慮下面一段代碼,在Activity建立時啟動了一個線程,線上程中無限迴圈。
/** * Example illustrating how threads persist across configuration * changes (which cause the underlying Activity instance to be * destroyed). The Activity context also leaks because the thread * is instantiated as an anonymous class, which holds an implicit * reference to the outer Activity instance, therefore preventing * it from being garbage collected. */ publicclass MainActivity extends Activity { @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleOne(); } privatevoid exampleOne() { newThread() { @Override publicvoid run() { while(true) { SystemClock.sleep(1000); } } }.start(); } }
當手機配置發生改變時(例如手機朝向發生改變),會導致整個Activity銷毀和重新建立。我們很容易認為Andriod會為我們做好一切清理工作,回收與Activity相關聯的記憶體和正在啟動並執行線程。但是,事情並不和我們想象中的一樣!Activity相關聯的記憶體和正在啟動並執行記憶體都不會回收,會發生泄漏,一個直接的結果就是程式的效能越來越差。 Activity是如何泄漏的 如果讀過LZ的前一篇文章會發現一個非靜態匿名類會持有外部類的一個隱式引用(上述代碼中的MainActivity),只要非靜態匿名類對象沒有被回收,MainActivity就不會被回收,MainActivity所關聯的資源和視圖都不會被回收,發生比較嚴重的記憶體流失。要解決MainActivity的記憶體流失問題,只需把非靜態Thread匿名類定義成靜態內部類就行了(靜態內部類不會持有外部類的一個隱式引用),如下代碼所示
/** * This example avoids leaking an Activity context by declaring the * thread as a private static inner class, but the threads still * continue to run even across configuration changes. The DVM has a * reference to all running threads and whether or not these threads * are garbaged collected has nothing to do with the Activity lifecycle. * Active threads will continue to run until the kernel destroys your * application's process. */ publicclass MainActivity extends Activity { @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleTwo(); } privatevoid exampleTwo() { newMyThread().start(); } privatestatic class MyThread extends Thread { @Override publicvoid run() { while(true) { SystemClock.sleep(1000); } } } }
現在新建立的Thread不會持有MainActivity的一個隱式引用,當手機朝向發生改變時不會阻止記憶體回收行程對舊MainActivity的回收工作~ Thread是如何泄漏的 在提到的第二個問題中,一旦一個新的Activity建立,那麼就有一個Thread永遠得不到清理回收,發生記憶體流失。Threads在Java中是GC roots;意味著Dalvik Virtual Machine (DVM) 會為所有活躍的threads在運行時系統中保持一個硬引用,這會導致threads一直處於運行狀態,垃圾收集器將永遠不可能回收它。出於這個原因,我們應當為threads添加一個結束的邏輯,如下代碼所示
/** * Same as example two, except for this time we have implemented a * cancellation policy for our thread, ensuring that it is never * leaked! onDestroy() is usually a good place to close your active * threads before exiting the Activity. */ publicclass MainActivity extends Activity { privateMyThread mThread; @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleThree(); } privatevoid exampleThree() { mThread = new MyThread(); mThread.start(); } /** * Static inner classes don't hold implicit references to their * enclosing class, so the Activity instance won't be leaked across * configuration changes. */ privatestatic class MyThread extends Thread { privateboolean mRunning = false; @Override publicvoid run() { mRunning = true; while(mRunning) { SystemClock.sleep(1000); } } publicvoid close() { mRunning = false; } } @Override protectedvoid onDestroy() { super.onDestroy(); mThread.close(); } }
在上述的代碼中,當Activity結束銷毀時在onDestroy()方法中結束了新建立的線程,保證了thread不會發生泄漏。但是如果你想在手機配置發生改變時保持原有的線程而不重新建立的話,你可以考慮使用fragment來保留原有的線程,以備下一次使用具體做法可以參考我之前的一篇文章http://blog.csdn.net/tu_bingbing/article/details/9274289,關於這方面 APIdemos 中也做了相關的實現。 結論 在Android中,長時間啟動並執行任務和Acyivity生命週期進行協調會有點困難,如果你不加以小心的話會導致記憶體流失。關於如何處理這個棘手的問題,下面有幾個基本的技巧供參考 1、使用靜態內部類/匿名類,不要使用非靜態內部類/匿名類.非靜態內部類/匿名類會隱式的持有外部類的引用,外部類就有可能發生泄漏。而靜態內部類/匿名類不會隱式的持有外部類引用,外部類會以正常的方式回收,如果你想在靜態內部類/匿名類中使用外部類的屬性或方法時,可以顯示的持有一個弱引用。 2、不要以為Java永遠會幫你清理回收正在啟動並執行threads.在上面的代碼中,我們很容易誤以為當Activity結束銷毀時會幫我們把正在啟動並執行thread也結束回收掉,但事情永遠不是這樣的!Java threads會一直存在,只有當線程運行完成或被殺死掉,線程才會被回收。所以我們應該養成為thread設定退出邏輯條件的習慣。 3、適當的考慮下是否應該使用線程.Android應用程式框架設計了許多的類來簡化執行背景工作,我們可以使用與Activity生命週期相關聯的Loaders來執行簡短的後台查詢任務。如果一個線程不依賴與Activity,我們還可以使用Service來執行背景工作,然後用BroadcastReceiver來向Activity報告結果。另外需要注意的是本文討論的thread同樣使用於AsyncTasks,AsyncTask同樣也是由線程來實現,只不過使用了Java5.0新增並發包中的功能,但同時需要注意的是根據官方文檔所說,AsyncTask適用於執行一些簡短的背景工作