欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > 内容正文

Handler学习(二):详解Handler的内存泄露,详解handler

来源: 开发者 投稿于  被查看 10196 次 评论:12

Handler学习(二):详解Handler的内存泄露,详解handler


一.什么是内存泄漏

1.内存泄露的定义

本该被回收的对象不能被回收而停留在堆内存中。

2.内存泄露出现的原因

当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。
这就导致了内存泄漏。

本文要分享的,是Handler中发生的内存泄漏。

二.一般用法导致内存泄漏

1.一般写法

我们先来看下日常Handler的一般用法,代码如下:

package com.glh.handlertest;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    //实例化Handler
    //这里并无指定Looper,即自动绑定当前线程(主线程)的Looper和MessageQueue
    private Handler showhandler = new Handler(){
        //通过复写handlerMessage()从而决定如何进行更新UI操作
        @Override
        public void handleMessage(Message msg) {
            //UI更新操作
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //启动子线程
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    showhandler.sendEmptyMessageDelayed(0x1,10000);

                }
            }
        }.start();
    }
}

代码编写完成后,Android Studio给出了提示:

从上图可以看出来,这个警告的原因是:该Handler造成了严重的内存泄漏。
那么,该Handler是怎么样造成内存泄露的呢?

2.内存泄漏的原因

首先,我们需要了解到:
(1)主线程的Looper对象会伴随该应用程序的整个生命周期
(2)在Java里,非静态内部类和匿名类都会潜在引用它们所属的外部类
在了解到上述两条后,从上面的代码中可以知道:

  • 在发送的延迟空消息(EmptyMessageDelayed)后、消息处理被前,该消息会一直保存在主线程的消息队列里持续10s

  • 这条引用关系会一直保持直到消息得到处理,从而,这阻止了MainActivity被垃圾回收器(GC)回收,同时造成应用程序的内存泄漏,如下图:

其实很好理解:
当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)。一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。

三.解决方案

1.解决方案1:

使用静态内部类+弱引用。
我们知道,在Java里,非静态内部类和匿名类都会潜在的引用它们所属的外部类。但是,静态内部类不会。
因此,解决方法主要在于两点:
1.将Handler声明为静态内部类。因为静态内部类不会持有外部类的引用,所以不会导致外部类实例出现内存泄露。
2.在Handler中添加对外部Activity的弱引用。由于Handler被声明为静态内部类,不再持有外部类对象的引用,导致无法在handleMessage()中操作Activity中的对象,所以需要在Handler中增加一个对Activity的弱引用。
代码如下:

package com.glh.handlertest;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;

import java.lang.ref.WeakReference;

public class MainActivity1 extends AppCompatActivity {

    //实例化Handler的子类
    //这里并无指定Looper,即自动绑定当前线程(主线程)的Looper和MessageQueue
    private final MyHandler showhandler = new MyHandler(this);

    //将Handler改成静态内部类
    private static class MyHandler extends Handler {

        //定义弱引用实例
        private WeakReference<MainActivity1> reference;

        //在构造方法中传入需要持有的Activity实例
        public MyHandler(MainActivity1 activity) {
            reference = new WeakReference<MainActivity1>(activity);
        }

        //通过复写handlerMessage()从而决定如何进行更新UI操作
        @Override
        public void handleMessage(Message msg) {
            //省略代码
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //主线程创建时便自动创建Looper和对应的MessageQueue,之前执行Loop()进入消息循环
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

2.解决方案2:

从上面分析,内存泄露的原因是:
当Activity结束生命周期时,Handler里的Message可能还没处理完,从而导致一系列的引用关系。
其实,我们只要在当Activity结束生命周期时清除掉消息队列(MessageQueue)里的所有Message,那么这一系列引用关系就不会存在,就能防止内存泄露。
解决方案:当Activity结束生命周期时(调用onDestroy()方法),同时清除消息队列里的所有回调消息(调用removeCallbacksAndMessages(null))
代码如下:

package com.glh.handlertest;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;

public class MainActivity2 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //启动子线程
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    showhandler.sendEmptyMessageDelayed(0x1,10000);

                }
            }
        }.start();
        finish();
    }

    //实例化Handler
    //这里并无指定Looper,即自动绑定当前线程(主线程)的Looper和MessageQueue
    private Handler showhandler = new Handler(){
        //通过复写handlerMessage()从而决定如何进行更新UI操作
        @Override
        public void handleMessage(Message msg) {
            //UI更新操作
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        showhandler.removeCallbacksAndMessages(null);
    }
}
查看评论
相关频道:

用户评论