在Activity中使用Thread致使的內存泄漏

在Activity中使用Thread致使的內存泄漏

https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/%E5%9C%A8Activity%E4%B8%AD%E4%BD%BF%E7%94%A8Thread%E5%AF%BC%E8%87%B4%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8Fhtml

 

已測試android

 

 

在Activity中使用Thread致使的內存泄漏

注:這篇博文涉及的源碼能夠在 GitHub 上面下載哦工具

作 Android 開發最常遇到的問題就是在 Activity 的生命週期中協調耗時任務,避免執行任務致使不易察覺的內存泄漏。不妨先讀一讀下面的代碼,代碼寫了一個簡單的 Activity,Activity 在啓動後就會開啓一個線程,並循環執行該線程中的任務post

    /** 
     *  示例向咱們展現了在 Activity 的配置改變時(配置改變會致使其下的 Activity 實例被銷     *  毀)存活。此外,Activity 的 context 也是內存泄漏的一部分,由於每個線程都被初始     *  化爲匿名內部類,使得每個線程都持有一個外部 Activity 實例的隱式引用,使得     *  Activity 不會被 Java 的垃圾回收機制回收。     */  
    public class MainActivity extends Activity {      @Override      protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        exampleOne();
      }      private void exampleOne() {        new Thread() {          @Override          public void run() {            while (true) {              SystemClock.sleep(1000);
            }
          }
        }.start();
      }
    }

Activity 配置發生改變會使 Activity 被銷燬,並新建一個 Activity,咱們總會以爲 Android 系統會將與被銷燬的 Activity 相關的一切清理乾淨,例如回收與 Activity 關聯的內存,Activity 執行的線程等等……然而,現實老是很殘酷的,剛剛提到的這些東西都不會被回收,並致使內存泄漏,從而顯著地影響應用的性能表現。

Activity 內存泄漏的根源

若是你讀過我之前寫的一篇有關 Handler 和 內部類的博文,那我接下來要講的知識你確定知道。在 Java 中,非靜態匿名內部類會持有其外部類的隱式引用,若是你沒有考慮過這一點,那麼存儲該引用會致使 Activity 被保留,而不是被垃圾回收機制回收。Activity 對象持有其 View 層以及相關聯的全部資源文件的引用,換句話說,若是你的內存泄漏發生在 Activity 中,那麼你將損失大量的內存空間。

而這樣的問題在 Activity 配置改變時會更加嚴重,由於 Activity 的配置改變表示 Android 系統將要銷燬當前 Activity 並新建一個 Activity。舉例來講吧,在使用應用的時候,你執行了10次橫屏/豎屏操做,每一次方向的改變都會執行下面的代碼,那麼咱們會發現(使用 Eclipse 的內存分析工具能夠看到)每個 Activity 對象都會由於留有一個隱式引用而被保留在內存中。

每一次配置的改變都會使 Android 系統新建一個 Activity 並把改變前的 Activity 交給垃圾回收機制回收。但由於線程持有舊 Activity 的隱式引用,使該 Activity 沒有被垃圾回收機制回收。這樣的問題會致使每個新建的 Activity 都將發生內存泄漏,與 Activity 相關的全部資源文件也不會被回收,其中的內存泄漏有多嚴重可想而知。

看到這裏可能你會很懼怕,很惶恐,很無助,那咱們該怎麼辦……莫慌,解決辦法很是簡單,既然咱們已經肯定了問題的根源,那麼對症下藥就能夠了:咱們把該線程類聲明爲私有的靜態內部類就能夠解決這個問題:

     /**      * 示例經過將線程類聲明爲私有的靜態內部類避免了 Activity context 的內存泄漏問題,但      * 在配置發生改變後,線程仍然會執行。緣由在於,DVM 虛擬機持有全部運行線程的引用,不管      * 這些線程是否被回收,都與 Activity 的生命週期無關。運行中的線程只會繼續運行,直到      * Android 系統將整個應用進程殺死      */ 
    public class MainActivity extends Activity {      @Override      protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        exampleTwo();
      }      private void exampleTwo() {        new MyThread().start();
      }      private static class MyThread extends Thread {        @Override        public void run() {          while (true) {            SystemClock.sleep(1000);
          }
        }
      }
    }

經過上面的代碼,新線程不再會持有一個外部 Activity 的隱式引用,並且該 Activity 也會在配置改變後被回收。

線程內存泄漏的根源

第二個問題是:對於每一個新建 Activity,若是 Activity 中的線程發生發生內存泄漏。在Java中線程是垃圾回收機制的根源,也就是說,在運行系統中DVM虛擬機總會使硬件持有全部運行狀態的進程的引用,結果導 致處於運行狀態的線程將永遠不會被回收。所以,你必須爲你的後臺線程實現銷燬邏輯!下面是一種解決辦法:

    /**     * 除了咱們須要實現銷燬邏輯以保證線程不會發生內存泄漏,其餘代碼和示例2相同。在退出當前     * Activity 前使用 onDestroy() 方法結束你的運行中線程是個不錯的選擇     */    public class MainActivity extends Activity {      private MyThread mThread;      @Override      protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        exampleThree();
      }      private void exampleThree() {
        mThread = new MyThread();
        mThread.start();
      }      /**       * 私有的靜態內部類不會持有其外部類的引用,使得 Activity 實例不會在配置改變時發生內       * 存泄漏       */      private static class MyThread extends Thread {        private boolean mRunning = false;        @Override        public void run() {
          mRunning = true;          while (mRunning) {            SystemClock.sleep(1000);
          }
        }        public void close() {
          mRunning = false;
        }
      }      @Override      protected void onDestroy() {        super.onDestroy();
        mThread.close();
      }
    }

經過上面的代碼,咱們在 onDestroy() 方法中結束了線程,確保不會發生意外的線程的內存泄漏問題。若是你想要在配置改變後保留該線程(而不是每一次在關閉 Activity 後都要新建一個線程),那我建議你使用 Fragment 去完成該耗時任務。你能夠翻我之前的博文,一名叫做「Handling Configuration Changes with Fragments」應該能知足你的需求,在API demo中也提供了很好理解的例子來爲你闡述相關概念。

結論

Android 開發過程當中,在 Activity 的生命週期裏協調耗時任務可能會很困難,你一不當心就會致使內存泄漏問題。下面是一些小提示,能幫助你預防內存泄漏問題的發生:

  • 儘量使用靜態內部類而不是非靜態內部類。每個非靜態內部類實例都會持有一個外部類的引用,若該引用是 Activity 的引用,那麼該 Activity 在被銷燬時將沒法被回收。若是你的靜態內部類須要一個相關 Activity 的引用以確保功能可以正常運行,那麼你得確保你在對象中使用的是一個 Activity 的弱引用,不然你的 Activity 將會發生意外的內存泄漏。

  • 不要總想着 Java 的垃圾回收機制會幫你解決全部內存回收問題。就像上面的示例,咱們覺得垃圾回收機制會幫 咱們將不須要使用的內存回收,例如:咱們須要結束一個 Activity,那麼它的實例和相關的線程都該被回收。但現實並不會像咱們劇本那樣走。Java 線程會一直存活,直到他們都被顯式關閉,抑或是其進程被 Android 系統殺死。因此,爲你的後臺線程實現銷燬邏輯是你在使用線程時必須時刻銘記的細節,此外,你在設計銷燬邏輯時要根據 Activity 的生命週期去設計,避免出現 Bug。

  • 考慮你是否真的須要使用線程。Android 應用的框架層爲咱們提供了不少便於開發者執行後臺操做的類。例如:咱們可使用 Loader 代替在 Activity 的生命週期中用線程經過注入執行短暫的異步後臺查詢操做,考慮用 Service 將結構通知給 UI 的 BroadcastReceiver。最後,記住,這篇博文中對線程進行的討論一樣適用於 AsyncTask(由於 AsyncTask 使用 ExecutorService 執行它的任務)。然而,雖然說 ExecutorService 只能在短暫操做(文檔說最多幾秒)中被使用,那麼這些方法致使的 Activity 內存泄漏應該永遠不會發生。

相關文章
相關標籤/搜索