2016-05-12 55 views
8

處理程序引用泄漏由於此Handler被聲明爲內部類,因此可能會阻止外部類被垃圾收集。如果處理程序對主線程以外的線程使用Looper或MessageQueue,則沒有問題。如果Handler使用主線程的Looper或MessageQueue,則需要修復Handler聲明,如下所示:將Handler聲明爲靜態類;在外部類中,實例化WeakReference到外部類並在實例化Handler時將此對象傳遞給Handler;使用WeakReference對象創建對外部類成員的所有引用。此Handler類應該是靜態的或可能發生泄漏:AsyncQueryHandler

我需要一個解決方案!!!!!

public class EMG_Activity extends AppCompatActivity { 

    // An object that manages Messages in a Thread 
    public static Handler HandlerMessager; 
    private static AsyncQueryHandler queryHandler; 

    private static final String EMG = "EMG"; 
    private ManageConnectedSocket manageConnectedSocket; 
    private Thread manageThread; 
    private Button button_start_pause; 


    private int id = 0; 
    private int xValue = 0; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_emg); 

     queryHandler = new AsyncQueryHandler(getContentResolver()) { 

      @Override 
      protected void onInsertComplete(int token, Object cookie, Uri uri) { 
       if(cookie != null) 
        id = (int) ContentUris.parseId(uri); 
      } 
     }; 
       ........... 

     button_start_pause = (Button) findViewById(R.id.button_start_pause); 
     button_start_pause.setOnClickListener(new View.OnClickListener() { 

      @Override 
      public void onClick(View arg0) { 

       .................. 

       manageConnectedSocket = new ManageConnectedSocket(MainActivity.bluetoothDevice, MainActivity.samplingFrequency, new int[]{0}, new int[]{1, 0, 1, 1}); 
       manageThread = new Thread(manageConnectedSocket); 
       manageThread.start(); 

       ContentValues values = getContentValuesExam (EMG, AlsrmSchema.PROGRESS, Utils.getCurrentDate()); 
       queryHandler.startInsert(1, id, AlsrmContract.Exam.CONTENT_URI, values); 

       ...... 
      } 
     }); 
    } 

    @Override 
    public void onDestroy() { 
     super.onDestroy(); 

     ..... 

      ContentValues values = getContentValuesExam (EMG, AlsrmSchema.CORRUPTED, Utils.getCurrentDate()); 
      queryHandler.startUpdate(1, null, AlsrmContract.Exam.CONTENT_URI, values, AlsrmSchema.id + " = ? ", new String[]{"" + id}); 
     } 
    } 
} 

回答

20

考慮下面的代碼:

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. 

但是泄漏究竟在哪裏以及它會發生什麼?首先記錄下我們所知道的問題來確定問題的根源:

  1. 當Android應用程序第一次啓動時,框架會爲應用程序的主線程創建一個Looper對象。一個Looper實現一個簡單的消息隊列,一個接一個地循環處理消息對象。所有主要的應用程序框架事件(如Activity生命週期方法調用,按鈕點擊等)都包含在Message對象中,這些對象被添加到Looper的消息隊列中並逐個處理。主線程的Looper存在於整個應用程序的生命週期中。
  2. 在主線程上實例化Handler時,它將與Looper的消息隊列關聯。發送到消息隊列的消息將持有對Handler的引用,以便當Looper最終處理消息時,框架可以調用Handler#handleMessage(Message)。
  3. 在Java中,非靜態內部類和匿名類擁有對其外部類的隱式引用。另一方面,靜態內部類沒有。

那麼究竟是內存泄漏?這是非常微妙的,但考慮下面的代碼爲例:

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(); 
    } 
} 

當活動結束後,延遲的消息將繼續在被處理前住在主線程的消息隊列中10分鐘。該消息保存對活動的Handler的引用,Handler持有對其外部類(本例中爲SampleActivity)的隱式引用。該引用將持續到消息處理完畢,從而阻止活動上下文被垃圾收集並泄漏所有應用程序的資源。請注意,對於第15行中的匿名Runnable類也是如此。匿名類的非靜態實例持有對其外部類的隱式引用,因此上下文將被泄漏。

要解決此問題,請在新文件中將Handler子類化或使用靜態內部類。靜態內部類不包含對其外部類的隱式引用,因此該活動不會泄露。如果您需要在處理程序中調用外部活動的方法,請讓Handler在活動中保留一個WeakReference,以免意外泄漏上下文。要解決內存泄漏發生時,我們實例匿名運行的類,我們把這些變量的類的靜態字段(因爲匿名類的靜態實例並不持有的隱式引用其外部類):

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開發人員都應該理解。底線是什麼? 如果內部類的實例可能超出活動的生命週期,請避免在活動中使用非靜態內部類。取而代之的是,首選靜態內部類,並對內部活動持有一個弱引用。

+0

我的警告是AsyncQueryHandler –

+0

這也適用於AsyncQueryHandler –

+0

@shriduttkothari是不是更安全的實例化來自調用者類的弱引用?更改爲'私人最終MyHandler mHandler =新MyHandler(新WeakReference <>(this));' – hannunehg

相關問題