本人翻譯, 略有改動, 原文地址如下:
http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html
考慮如下代碼:
- public class SampleActivity extends Activity {
- private final Handler mLeakyHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- /* ... */
- }
- }
- }
你可能看不出來這段代碼會造成內存泄漏, 確實, 它不那麼容易被發現. 如果你運行Android的lint工具, 它會給你一個警告, 提示你把handler定義成靜態的(static), 否則可能造成內存泄漏. 但內存泄漏是怎麼發生的呢?
首先, 我們應該知道如下幾點:
1: 當一個android程序啓動時, 框架層(framework)會爲程序的主線程創建一個Looper對象. 該對象實現了一個簡單的消息隊列, 循環不斷的處理隊列上的消息對象(Message), 主線程上的Looper對象在程序的整個生命週期中一直存在.
2: 當一個Handler在主線程上被實例化時, 它就與Looper的消息隊列關聯到一起了. 隊列上的Message對象持有一個handler的引用, 這使得Looper處理到某一個Message時, 能夠調用handler.handleMessage()方法.
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() {
- public void run() { }
- }, 600000);
- // Go back to the previous Activity.
- finish();
- }
- }
當Activityfinish後, 我們發出的那個"延遲處理消息"將在主線程的消息隊列中保持10分鐘, 直到該消息最終被處理. 由於消息持有一個handler的引用, 而handler又持有一個它的外部類-SampleActivity的引用, 這樣就阻止了activity的context被垃圾回收, 從而泄漏了Activty引用的所有的應用資源. 注意上述例子中的匿名的Runnable對象也一樣造成了context的泄露.
要避免這個問題, 就需要將Handler改爲靜態內部類. 如果你需要在Handler中調用Activity外部類的方法, 你可以在handler中使用一個WeakReference來持有activity對象.
(注意我們將Handler和Runnable都定義成了static的)
- public class SampleActivity extends Activity {
- 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 final static Runnable sRunnable = new Runnable() {
- 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, 600000);
- // Go back to the previous Activity.
- finish();
- }
- }
結論: 在Activity中使用非靜態的內部類時, 儘量避免內部類生命週期超出了Activity之外. 類似的例子還有AsyncTask.
=================================
補充例子:
如果你看過PendingIntent的源代碼, 你會看到它有一些send(Handler...)的方法, 如果某個Activity調用了PendingIntent.send(...), 並且傳入一個非靜態的內部Handler類, 當activity被銷燬後, 內部類仍然持有它的引用, 導致它無法被垃圾收集.
當然, 如果你沒有像這樣不當的發佈一個Handler到其它的類, 你就不用擔心泄露發生.
=================================
如果你不想每次都創建一個WeakReference, 可以先創建這樣一個通用類:
- public abstract class WeakReferenceHandler<T> extends Handler {
- private WeakReference<T> mReference;
- public WeakReferenceHandler(T reference) {
- mReference = new WeakReference<T>(reference);
- }
- @Override
- public void handleMessage(Message msg) {
- if (mReference.get() == null)
- return;
- handleMessage(mReference.get(), msg);
- }
- protected abstract void handleMessage(T reference, Message msg);
- }