1. 程式人生 > >Handler還需要用到弱引用(WeakReference)嗎?

Handler還需要用到弱引用(WeakReference)嗎?

網上很多文章都說寫Hanlder,需要用static宣告為靜態的,還需要用弱引用包裹建構函式傳來的Activity例項。

比如這篇英文部落格

http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

裡面的Sample是這樣寫的

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

post,postDelayed這種傳Runnable的方法是不會觸發handleMessage方法的。

所以用一下sendEmptyMessageDelayed測試一下

public class SampleActivity extends Activity {

    private ImageView iv;

    /**
     * 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();
            Log.e("YAO", "MyHandler - handleMessage ------ 訊息到達了  activity!=null:" + (activity != null));
            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);

        //塞一張大一點的圖片,用來增大Activity的所需記憶體,可以更好的檢視Memory趨勢圖
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

        // Post a message and delay its execution for 10 minutes.
        //mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
        mHandler.sendEmptyMessageDelayed(0, 1000L * 30);

        // Go back to the previous Activity.
        finish();
    }
}

開啟這個Activity後會自動finish,然後點一下Android Monitor裡面的Initiate GC按鈕,觸發GC操作。

此時Dump Java Heap看看記憶體情況

可以看到SampleActivity已經被回收了,什麼都沒有。

而裡面的MyHandler還存在,這裡還可以看到Handler只持有一個Message物件。

等了30秒訊息到達後,日誌打印出

02-11 15:22:15.033 7153-7153/com.yao.memorytest E/YAO: MyHandler - handleMessage ------ 訊息到達了  activity!=null:false

說明SampleActivity已經被回收了。

重來一次,這次不點選Initiate GC按鈕,Dump Java Heap後

對比前面兩張圖,可以看到SampleActivity還存在。從MyHandler看,這個例項除了有一個Message物件,還有一個SampleActivity物件(注意這個物件是紅色的)。

等30秒結果打印出

02-11 15:24:15.033 7153-7153/com.yao.memorytest E/YAO: MyHandler - handleMessage ------ 訊息到達了  activity!=null:true

說明關閉頁面30秒後,弱引用Activity還在。

如果把static關鍵字去掉

由於非靜態內部類會持有外部類的一個隱式引用。所以MyHandler持有一個當前SampleActivity物件例項(此時這個物件是白色的)。

可以看到,對於static內部類,用弱引用都會把SampleActivity標成紅色。而不用static關鍵字,則是白色。

猜測一下,紅色代表的是待回收記憶體,下次GC後會被回收。白色代表還在使用,GC後不會被回收的記憶體。

改寫一下程式碼,現在改成使用postDelayed方法,此時按照之前得出的結論Runnable也必須是靜態的。

如果Runnable裡面有跟當前Activity相關的程式碼,也得加個弱引用Activity。

public class SampleTwoActivity extends Activity {

    private ImageView iv;

    /**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class MyHandler extends Handler {
//        private final WeakReference<SampleTwoActivity> mActivity;
//
//        public MyHandler(SampleTwoActivity activity) {
//            mActivity = new WeakReference<SampleTwoActivity>(activity);
//        }
//
//        @Override
//        public void handleMessage(Message msg) {
//            SampleTwoActivity activity = mActivity.get();
//            Log.e("YAO", "MyHandler - handleMessage ------ 訊息到達了  activity!=null:" + (activity != null));
//            if (activity != null) {
//                // ...
//            }
//        }
    }

    private final MyHandler mHandler = new MyHandler();

    /**
     * Instances of anonymous classes do not hold an implicit
     * reference to their outer class when they are "static".
     */
    private static class MyRunnable implements Runnable {

        private final WeakReference<SampleTwoActivity> mActivity;

        public MyRunnable(SampleTwoActivity activity) {
            mActivity = new WeakReference<SampleTwoActivity>(activity);
        }

        @Override
        public void run() {
            SampleTwoActivity activity = mActivity.get();
            Log.e("YAO", "MyRunnable - run ------ 執行延時Runnable  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.iv.setVisibility(View.VISIBLE);
            }
        }
    }

    private final MyRunnable mMyRunnable = new MyRunnable(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //塞一張大一點的圖片,用來增大Activity的所需記憶體,可以更好的檢視Memory趨勢圖
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(mMyRunnable, 1000L * 30);
        //mHandler.sendEmptyMessageDelayed(0, 1000L * 30);

        // Go back to the previous Activity.
        finish();
    }
}

Dump Java Heap看看

MyRunnable也持有一個Activity的弱引用,紅色的。執行一下GC,Activity也意料之中的被回收了。

等30秒結果打印出

02-12 03:31:18.215 7501-7501/com.yao.memorytest E/YAO: MyRunnable - run ------ 執行延時Runnable  activity!=null:false

以上是傳送用Handler傳送延時訊息,延時任務的情況。

可以看出用上靜態內部類+弱引用Handler的確能解決記憶體洩露的問題,同時得注意Runnable也需要用上靜態弱引用才行。

然而有個更好的方法removeCallbacksAndMessages

public class SampleFourActivity extends Activity {

    private ImageView iv;

    private class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            Log.e("YAO", "MyHandler - handleMessage ------ 訊息到達了");
            iv.setVisibility(View.VISIBLE);
        }
    }

    private final MyHandler mHandler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //塞一張大一點的圖片,用來增大Activity的所需記憶體,可以更好的檢視Memory趨勢圖
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

        // Post a message and delay its execution for 10 minutes.
        //mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
        mHandler.sendEmptyMessageDelayed(0, 1000L * 30);

        // Go back to the previous Activity.
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

只需要在onDestroy裡面Handler.removeCallbacksAndMessages(null);,無論runnbale還是message訊息全清空,自然也不會關聯上Activity。下次GC就能順利回收了。
 

傳送長時間的延時訊息/任務其實是少見,更多的是比如我們經常開執行緒聯網訪問或者開執行緒做一些耗時計算,結束後才通過Handler傳送訊息更新UI,應該是這樣的。

public class SampleThreeActivity extends Activity {

    private ImageView iv;

    /**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class MyHandler extends Handler {

    }

    private final MyHandler mHandler = new MyHandler();

    /**
     * Instances of anonymous classes do not hold an implicit
     * reference to their outer class when they are "static".
     */
    private static class MyRunnable implements Runnable {

        private final WeakReference<SampleThreeActivity> mActivity;

        public MyRunnable(SampleThreeActivity activity) {
            mActivity = new WeakReference<SampleThreeActivity>(activity);
        }

        @Override
        public void run() {
            Log.e("YAO", "TestMemoryActivity.java - run() ---------- 工作執行緒正在執行耗時任務....");
            SystemClock.sleep(1000L * 30);
            SampleThreeActivity activity = mActivity.get();
            Log.e("YAO", "MyRunnable - run ------ 執行延時Runnable  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.mHandler.obtainMessage().sendToTarget();
            }
        }
    }

    private final MyRunnable mMyRunnable = new MyRunnable(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //塞一張大一點的圖片,用來增大Activity的所需記憶體,可以更好的檢視Memory趨勢圖
        iv = new ImageView(this);
        iv.setImageResource(R.drawable.demo);

        // Post a message and delay its execution for 10 minutes.
        //mHandler.postDelayed(mMyRunnable, 1000 * 30);
        //mHandler.sendEmptyMessageDelayed(0, 1000L * 30);
        new Thread(mMyRunnable).start();

        // Go back to the previous Activity.
        finish();
    }
}

Dump Java Heap

發現Activity是紅色的可回收記憶體,沒啥問題。

但是改一下run方法裡面的程式碼。

@Override
public void run() {
	SampleThreeActivity activity = mActivity.get();
	if (activity != null) {
		//通過弱引用去獲取Activity的成員變數引數(比如網址url),再跑耗時任務。
		String url = activity.url;
		Log.e("YAO", "TestMemoryActivity.java - run() ---------- 工作執行緒正在執行耗時任務....");
		SystemClock.sleep(1000L * 30);
		activity.mHandler.obtainMessage().sendToTarget();
	}
}


所以用了弱引用的get方法後,相當於會把記憶體中的弱引用轉為強引用。

所以如果是這種方式的話,建議退出這個Activity時取消這個任務。Thread是沒有提供取消任務的方法的。可以用AsyncTask的cancel方法,ExecutorService的shutdown方法,當然一般網路框架volley、okhttp這些也會提供相應的取消請求方法。

(較真一點如果在使用AsyncTask時也改成靜態內部類+弱引用當然也可以,但是非常麻煩,AsyncTask執行在UI執行緒的onPreExecute、onProgressUpdate、onPostExecute使用到Activity中的成員變數的話,都需要進行弱引用Activity的判空方法,相當麻煩。)

    static class MyAsyncTask extends AsyncTask<Void, Void, Void> {

        private final WeakReference<SampleFiveActivity> mActivity;

        public MyAsyncTask(SampleFiveActivity activity) {
            mActivity = new WeakReference<SampleFiveActivity>(activity);
        }

        @Override
        protected void onPreExecute() {
            SampleFiveActivity activity = mActivity.get();
            Log.e("YAO", "MyAsyncTask - onPreExecute ------  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.iv.setVisibility(View.VISIBLE);
            }
        }

        @Override
        protected Void doInBackground(Void... params) {
            Log.e("YAO", "TestMemoryActivity.java - run() ---------- 工作執行緒正在執行耗時任務....");
            SystemClock.sleep(1000L * 30);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            SampleFiveActivity activity = mActivity.get();
            Log.e("YAO", "MyAsyncTask - onPostExecute ------  activity!=null:" + (activity != null));
            if (activity != null) {
                activity.iv.setVisibility(View.VISIBLE);
            }
        }
    }

總結就是,以後我使用handler估計只會用Handler.removeCallbacksAndMessages(null);這種方法了,方便快捷。