Android内存泄漏(五):Handler

上一节我们介绍了非静态内部类作为静态变量造成的内存泄漏情况,这一节我们介绍一下Handler的使用造成的内存泄漏情况

知识点

非静态内部类匿名类内部类的实例都会潜在持有它们所属的外部类的强引用,但是静态内部类却不会

使用匿名内部类

我们来看一段代码:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

这个Activity中new Runnable()是一个匿名内部类,这个内部类持有外部类Activity的强引用,内部类被封装成消息Message被传递到Handler的消息队列MessageQueue中,即消息持有Activity的强引用。在Message消息没有被Handler处理之前,Activity实例不会被销毁了,于是导致内存泄漏。发送postDelayed这样的消息,你输入延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会照成一个临时的泄露。可参考Android内存泄漏之匿名内部类

使用静态内部类

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final TextView mTextView;

        protected MyRunnable(TextView textView) {
            mTextView = textView;
        }

        @Override
        public void run() {
            mTextView.setText("Finished");
        }
    }
}

这段代码中,我们使用的是静态内部类,那么这样还会内存泄漏吗?
答案:会
上面知识点中我们提到了静态内部类不会持有外部类的引用,那么为什么这里还会内存泄漏呢。
因为TextView持有Activity的强引用,我们都知道View都持有Context的引用,这里的Context就是Activity。new MyRunnable(textView)持有TextView的强引用,这样MyRunnable也就持有Activity的强引用了,所以消息为处理之前,Activity实例不会被销毁,于是导致内存泄漏。

解决方案1:弱引用+静态内部类

匿名内部类因为持有Activity的强引用,所以会导致内存泄漏。
静态内部类中的TextView持有Activity的强引用,所以也会导致内存泄漏
Android内存泄漏和引用的关系中,我们有讲到弱引用:如果一个对象具有弱引用,在GC线程扫描内存区域的过程中,不管当前内存空间足够与否,都会回收内存。
代码如下:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);

        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final WeakReference<TextView> wr;

        protected MyRunnable(TextView textView) {
            wr = new WeakReference<TextView>(textView);
        }

        @Override
        public void run() {
            final TextView tv = wr.get();
            if (tv != null) {
                tv.setText("Finished");
            }
        }
    }
}

这里我们把静态内部类的TextView改成弱引用了,这样虽然textView持有activity的强引用,但是new MyRunnable(textView)持有的是TextView的弱引用,这样MyRunnable持有Activity的引用也是弱引用,所以内存回收的时候,是可以回收的,我们可以通过wr.get()是否为空判断是否已经回收。

解决方案2:在onDestory的时候,手动清除Message

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }

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

如上方法,我们在ondestory的时候,清除所有未处理的Message,就不会有哪个消息持有Activity的强引用了,这样也不会导致内存泄漏。

解决方案3:使用第三方控件WeakHandler

WeakHandler是一个第三方库,我们看看他是怎么使用的:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private WeakHandler handler = new WeakHandler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

它用起来很简单,不需要考虑弱应用的情况,你只需要把以前的Handler替换成WeakHandler就行了。
我们看看WeakHandler的源代码:

private final WeakHandler.ExecHandler mExec;
public final boolean postDelayed(Runnable r, long delayMillis) {
    return this.mExec.postDelayed(this.wrapRunnable(r), delayMillis);
}
private WeakHandler.WeakRunnable wrapRunnable(@NonNull Runnable r) {
    if(r == null) {
        throw new NullPointerException("Runnable can\'t be null");
    } else {
        WeakHandler.ChainedRef hardRef = new WeakHandler.ChainedRef(this.mLock, r);
        this.mRunnables.insertAfter(hardRef);
        return hardRef.wrapper;
    }
}
static class WeakRunnable implements Runnable {
    private final WeakReference<Runnable> mDelegate;
    private final WeakReference<WeakHandler.ChainedRef> mReference;

    WeakRunnable(WeakReference<Runnable> delegate, WeakReference<WeakHandler.ChainedRef> reference) {
        this.mDelegate = delegate;
        this.mReference = reference;
    }

    public void run() {
        Runnable delegate = (Runnable)this.mDelegate.get();
        WeakHandler.ChainedRef reference = (WeakHandler.ChainedRef)this.mReference.get();
        if(reference != null) {
            reference.remove();
        }

        if(delegate != null) {
            delegate.run();
        }

    }
}
private static class ExecHandler extends Handler {
    private final WeakReference<Callback> mCallback;

    ExecHandler() {
        this.mCallback = null;
    }

    ExecHandler(WeakReference<Callback> callback) {
        this.mCallback = callback;
    }

    ExecHandler(Looper looper) {
        super(looper);
        this.mCallback = null;
    }

    ExecHandler(Looper looper, WeakReference<Callback> callback) {
        super(looper);
        this.mCallback = callback;
    }

    public void handleMessage(@NonNull Message msg) {
        if(this.mCallback != null) {
            Callback callback = (Callback)this.mCallback.get();
            if(callback != null) {
                callback.handleMessage(msg);
            }
        }
    }
}

源代码中可以清楚的看到它将Handler和Runnable做了弱引用封装,而ExecHandler和WeakRunnable也就是封装之后的内部类。

上一节:Android内存泄漏(四):非静态内部类作为静态变量

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    宇宙只有巴掌大阅读 2,357评论 0 12
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    _痞子阅读 1,611评论 0 8
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    apkcore阅读 1,214评论 2 7
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    DreamFish阅读 789评论 0 5
  • 金指尖的花园阅读 338评论 0 2