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 mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference(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 mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference(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操作。

Handler还需要用到弱引用(WeakReference)吗?_第1张图片

此时Dump Java Heap看看内存情况

Handler还需要用到弱引用(WeakReference)吗?_第2张图片

可以看到SampleActivity已经被回收了,什么都没有。

Handler还需要用到弱引用(WeakReference)吗?_第3张图片

而里面的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后

Handler还需要用到弱引用(WeakReference)吗?_第4张图片

 

Handler还需要用到弱引用(WeakReference)吗?_第5张图片

对比前面两张图,可以看到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关键字去掉

Handler还需要用到弱引用(WeakReference)吗?_第6张图片

由于非静态内部类会持有外部类的一个隐式引用。所以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 mActivity;
//
//        public MyHandler(SampleTwoActivity activity) {
//            mActivity = new WeakReference(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 mActivity;

        public MyRunnable(SampleTwoActivity activity) {
            mActivity = new WeakReference(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看看

Handler还需要用到弱引用(WeakReference)吗?_第7张图片

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 mActivity;

        public MyRunnable(SampleThreeActivity activity) {
            mActivity = new WeakReference(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

 

Handler还需要用到弱引用(WeakReference)吗?_第8张图片

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

 

 

Handler还需要用到弱引用(WeakReference)吗?_第9张图片
所以用了弱引用的get方法后,相当于会把内存中的弱引用转为强引用。

 

 

所以如果是这种方式的话,建议退出这个Activity时取消这个任务。Thread是没有提供取消任务的方法的。可以用AsyncTask的cancel方法,ExecutorService的shutdown方法,当然一般网络框架volley、okhttp这些也会提供相应的取消请求方法。

(较真一点如果在使用AsyncTask时也改成静态内部类+弱引用当然也可以,但是非常麻烦,AsyncTask运行在UI线程的onPreExecute、onProgressUpdate、onPostExecute使用到Activity中的成员变量的话,都需要进行弱引用Activity的判空方法,相当麻烦。)

 

    static class MyAsyncTask extends AsyncTask {

        private final WeakReference mActivity;

        public MyAsyncTask(SampleFiveActivity activity) {
            mActivity = new WeakReference(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);这种方法了,方便快捷。

 

你可能感兴趣的:(Android)