Android 中 Handler 引起的記憶體泄露

来源:http://www.cnblogs.com/xiaoxiaojia/archive/2016/04/21/5415762.html
-Advertisement-
Play Games

在Android常用編程中,Handler在進行非同步操作並處理返回結果時經常被使用。其實這可能導致記憶體泄露,代碼中哪裡可能導致記憶體泄露,又是如何導致記憶體泄露的呢?那我們就慢慢分析一下。http://www.jinhusns.com/Products/Download/?type=xcj 在Andro ...


在Android常用編程中,Handler在進行非同步操作並處理返回結果時經常被使用。其實這可能導致記憶體泄露,代碼中哪裡可能導致記憶體泄露,又是如何導致記憶體泄露的呢?那我們就慢慢分析一下。http://www.jinhusns.com/Products/Download/?type=xcj

 

在Android常用編程中,Handler在進行非同步操作並處理返回結果時經常被使用。通常我們的代碼會這樣實現。

public class SampleActivity extends Activity { 
 
  private final Handler mLeakyHandler = new Handler() { 
    @Override 
    public void handleMessage(Message msg) { 
      // ...  
    } 
  } 
}

但是,其實上面的代碼可能導致記憶體泄露,當你使用Android lint工具的話,會得到這樣的警告

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

看到這裡,可能還是有一些搞不清楚,代碼中哪裡可能導致記憶體泄露,又是如何導致記憶體泄露的呢?那我們就慢慢分析一下。

1. 當一個Android應用啟動的時候,會自動創建一個供應用主線程使用的Looper實例。Looper的主要工作就是一個一個處理消息隊列中 的消息對象。在Android中,所有Android框架的事件(比如Activity的生命周期方法調用和按鈕點擊等)都是放入到消息中,然後加入到 Looper要處理的消息隊列中,由Looper負責一條一條地進行處理。主線程中的Looper生命周期和當前應用一樣長。

2. 當一個Handler在主線程進行了初始化之後,我們發送一個target為這個Handler的消息到Looper處理的消息隊列時,實際上 已經發送的消息已經包含了一個Handler實例的引用,只有這樣Looper在處理到這條消息時才可以調用 Handler#handleMessage(Message)完成消息的正確處理。

3.在Java中,非靜態的內部類和匿名內部類都會隱式地持有其外部類的引用。靜態的內部類不會持有外部類的引用。關於這一內容可以查看細話Java:”失效”的private修飾符

確實上面的代碼示例有點難以察覺記憶體泄露,那麼下麵的例子就非常明顯了

public class SampleActivity extends Activity { 
 
  private final Handler mLeakyHandler = new Handler() { 
    @Override 
    public void handleMessage(Message msg) { 
      // ... 
    } 
  } 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
 
    // Post a message and delay its execution for 10 minutes. 
    mLeakyHandler.postDelayed(new Runnable() { 
      @Override 
      public void run() { /* ... */ } 
    }, 1000 * 60 * 10); 
 
    // Go back to the previous Activity. 
    finish(); 
  } 
}

分 析一下上面的代碼,當我們執行了Activity的finish方法,被延遲的消息會在被處理之前存在於主線程消息隊列中10分鐘,而這個消息中 又包含了Handler的引用,而Handler是一個匿名內部類的實例,其持有外面的SampleActivity的引用,所以這導致了 SampleActivity無法回收,進行導致SampleActivity持有的很多資源都無法回收,這就是我們常說的記憶體泄露。

註意上面的new Runnable這裡也是匿名內部類實現的,同樣也會持有SampleActivity的引用,也會阻止SampleActivity被回收。

要解決這種問題,思路就是不適用非靜態內部類,繼承Handler時,要麼是放在單獨的類文件中,要麼就是使用靜態內部類。因為靜態的內部類不會持有外部類的引用,所以不會導致外部類實例的記憶體泄露。當你需要在靜態內部類中調用外部的Activity時,我們可以使用弱引用來處理。另外關於同樣也需要將Runnable設置為靜態的成員屬性。註意:一個靜態的匿名內部類實例不會持有外部類的引用。 修改後不會導致記憶體泄露的代碼如下

public class SampleActivity extends Activity { 
 
  /** 
   * Instances of static inner classes do not hold an implicit 
   * reference to their outer class. 
   */ 
  private static class MyHandler extends Handler { 
    private final WeakReference<SampleActivity> mActivity; 
 
    public MyHandler(SampleActivity activity) { 
      mActivity = new WeakReference<SampleActivity>(activity); 
    } 
 
    @Override 
    public void handleMessage(Message msg) { 
      SampleActivity activity = mActivity.get(); 
      if (activity != null) { 
        // ... 
      } 
    } 
  } 
 
  private final MyHandler mHandler = new MyHandler(this); 
 
  /** 
   * Instances of anonymous classes do not hold an implicit 
   * reference to their outer class when they are "static". 
   */ 
  private static final Runnable sRunnable = new Runnable() { 
      @Override 
      public void run() { /* ... */ } 
  }; 
 
  @Override 
  protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
 
    // Post a message and delay its execution for 10 minutes. 
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 
 
    // Go back to the previous Activity. 
    finish(); 
  } 
}

其 實在Android中很多的記憶體泄露都是由於在Activity中使用了非靜態內部類導致的,就像本文提到的一樣,所以當我們使用時要非靜態內部 類時要格外註意,如果其實例的持有對象的生命周期大於其外部類對象,那麼就有可能導致記憶體泄露。個人傾向於使用文章的靜態類和弱引用的方法解決這種問題。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CSS徹底研究(3)-浮動,定位 一 . 浮動float I . 定義及規則 float預設為none,對應標準流的情況。當float : left;時,元素就會向其父元素的左側靠緊,脫離標準流,同時寬度不再伸展至充滿父容器,而是根據自身內容來確定。 II . 演示規則 準備代碼 <html xml ...
  • 最近迷戀上了釣魚,可是總釣不到大魚,所以就畫條大魚來安慰一下我這柔弱的心靈。 先上圖: 上面這個就是今晚上我要跟大家分享的小DEMO,我給他起名字就“大魚吃小魚之孤單的大魚”。 轉入正題,這條大魚分為三部分:頭,尾巴,眼睛。首先看一下這條魚的框架,如下麵所示,class已經說得很直接了 首先給整條魚 ...
  • 介紹 Emmet (前身為 Zen Coding) 是一個能大幅度提高前端開發效率的一個工具。 基本上,大多數的文本編輯器都會允許你存儲和重用一些代碼塊,我們稱之為“片段”。雖然片段能很好地推動你得生產力,但大多數的實現都有這樣一個缺點:你必須先定義你得代碼片段,並且不能再運行時進行拓展。 Emme ...
  • AngularJS Select(選項框) AngularJS 可是使用數組或對象創建一個下拉列表選項。使用ng-options創建選項框 在AngularJS 中我們可以使用ng-option指令來創建一個下拉列表,列表通過對象和數組迴圈輸出 實例: <div ng-app="myApp" ng- ...
  • 前言 通過百度搜索歌曲,進入到酷我聽歌頁面時,發現沒有歌詞定位功能。 突然想到自己可不可以實現這個效果,於是就有了這篇文章。 分析 放上我喜歡的一首歌《Hello》 。界面如下 通過分析html源碼,得到以下結果 1、最重要的兩部分區域: 歌詞區域 、 播放進度區域 2、歌詞區域是一個 div [c ...
  • 移動設備優先: 為了讓開發的網站對移動設備友好,確保適當的繪製和觸屏縮放,需要在網頁的head之中添加viewport meat標簽:如下: <metaname="viewport"content="width=device-width, initial-scale=1.0"> 響應式圖像: 通過對 ...
  • 今天商城系統的後臺要添加一個Tab切換的效果,一開始沒有思路想要自己去實踐這個效果 從網上找jquery 已經有了很好看的案例,實現之後我來學習下思路是如何完成的 ...
  • 頁面必須設置為html5文檔類型 <!DOCTYPE html> <html lang="zh-CN"> ... </html> 適應移動設備 <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scal ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...