package com.cheng.highqualitycodestudy.memoryleakhandle; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.TextView; import com.cheng.base.BaseActivity; import com.cheng.highqualitycodestudy.R; import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; public class HowToUseHandlerActivity extends BaseActivity { private TextView mTestTV; private GoodMyHandler mGoodMyHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_howtousehandler); this.mTestTV = findViewByID(R.id.tv_test); this.mGoodMyHandler = new GoodMyHandler(); } /** * 参考: * http://blog.csdn.net/FeeLang/article/details/39059705 */ /****************************************************** * BadCode * ******************************************************/ /** * 错误的代码示例: */ /** * 因为Runnable不是static类型,所以会有一个包含Activity实例的implicit reference --- Activity.this。 * 如果Activity在runnable变量run之前(10s内)被finish掉了但是Activity.this仍然存在, * 那么Activity的对象就不会被GC回收,从而导致memory leak。 */ private final Runnable runnable = new Runnable() { @Override public void run() { // ... do some work } }; /** * 即使使用一个静态内部类,也不能保证万事大吉。 * 假设在runnable执行之前,View被移除了,但是成员变量view还在继续引用它,仍然会导致memory leak。 */ static class BadMyRunnable implements Runnable { private View view; public BadMyRunnable(View v) { this.view = v; } @Override public void run() { // ... do some work } } /** * 上面的两个例子当中,导致内存泄露的两种用法分别是隐式引用(implicit reference) * 和 显式引用(explicit reference)。 * * 解决方法 * 解决隐式引用的方法比较简单,只要使用内部非静态类(non-static inner class)或者 * top-level class(在一个独立的java文件中定义的变量)就可以将隐式变为显式,从而避免内存泄露。 * 如果继续使用非静态内部类,那么就要在onPause的时候手动结束那些挂起的任务(pending task)。 * 解决第二个问题要用到WeakReference,WeakReference的用法可以google一下, * 简而言之就是:只要还有其他的stronger reference,WeakReference就可以继续引用。 */ private void badCodeDemo() { new Handler().postDelayed(runnable, TimeUnit.SECONDS.toMillis(10)); } /****************************************************** * GoodCode * ******************************************************/ /** * 使用WeakReference * 这样一来问题就解决了,美中不足的是每次使用view之前都要做空指针判断。 */ static class GoodMyRunnable implements Runnable { private WeakReference<View> view; public GoodMyRunnable(View v) { this.view = new WeakReference<View>(v); } @Override public void run() { View v = view.get(); if (v == null) return; // ... do something with the view } } /** * 另外一个比较高效的方法就是在onResume中为runnable的view赋值,在onPause中赋值为null。 */ private static class GoodMyHandler extends Handler { private TextView textView; public void attach(TextView v) { this.textView = v; } public void detach() { this.textView = null; } @Override public void handleMessage(Message msg) { // ... } } private static final class InnerHandler extends Handler { private WeakReference<HowToUseHandlerActivity> mActivityWR; public InnerHandler(HowToUseHandlerActivity theActivity) { this.mActivityWR = new WeakReference<>(theActivity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); HowToUseHandlerActivity theActivity = mActivityWR.get(); if (theActivity == null) return; // TODO 使用theActivity调用成员方法 } } @Override protected void onResume() { super.onResume(); mGoodMyHandler.attach(mTestTV); } @Override protected void onPause() { super.onPause(); mGoodMyHandler.detach(); } /** * 总结 在继承Handler或者HandlerThread的时候, 尽量定义一个static类或者top-level类。 如果用到了ui元素,一定要在Activity的生命周期接触之前释放掉。 */ }