logo头像
从未如此简单有趣

Android中Handler导致内存泄漏的原理分析

本文于384天之前发表,文中内容可能已经过时。

1. 简介

在写Android应用程序时,Handler应该是很常见的一个类。我们一般在使用该类时有采用如下方法:

// 1.新建一个匿名的Handler内部类,并重新handleMessage
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                //主线程可以更新UI
                Log.e("handler", "case 1");
                break;
            default:
                Log.e("handler", "default");
                break;
        }
    }
};

private void doInBackground() {
    new Thread(){
        @Override
        public void run() {
            //2.在工作线程中完成具体的事情
            try {
                // TODO doSomething();
                sleep(6000);
            } catch (Exception e) {

            }
            //3.事情完成后,通过mHandler线程间消息传递给主线程的Handler进行更新操作
            mHandler.sendEmptyMessage(1);
        }
    }.start();
}

上面新建Handler时也可以采用如下方法:

private Handler mHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.about_and_feedback);
    mContext = this;
    mHandler = new MyHandler();
}

private class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                //主线程可以更新UI
                Log.e("handler", "case 1");
                break;
            default:
                Log.e("handler", "default");
                break;
        }
    }
}

以上两种方式都是新建了一个匿名的内部类,并直接new给了mHandler变量。采用如上方式使用Handler时会有内存泄漏的风险。

2. 原理剖析

2.1 Android的约定

在Android中主线程的Looper对象的生命周期是和应用程序的生命周期是一样的,这也就意味在主线程的Looper是一直存在的。同时在Java中非静态内部类或者匿名内部类都是默认持有外部类的引用的,这两个内部类的存在是依赖于外部类的对象的。比如如下例子:

//OutClass.java
public class OutClass {
    public class InnerClass {

    }
}
//Client.java
public class Client {
    public static void main(String[] args) {
        //要想new一个InnerClass,必须先new一个OutClass
        //OutClass.InnerClass inn = new OutClass.InnerClass();//这句代码是错误的,会编译不过
        OutClass out = new OutClass();
        OutClass.InnerClass inn = out.new InnerClass();//这句可以编译过,这里就新建了一个依赖于out这个变量的内部类
    }
}

单独new InnerClass()是编译不过的,非静态内部类或者匿名内部类是依赖于外部类的,默认是持有外部类的引用的。

2.2 具体原理分析

我们在通过mHandler.sendEmptyMessage(1)时,消息Message会被发送的MessageQueue里面,也就是Looper里面的mQueue,消息Message如果有延迟处理时,会留在MessageQueue里面,而Message是持有mHandler的引用,而mHandler是通过匿名内部类的形式创建的,会默认持有外部类Activity的引用。而我们的Looper对象的生命周期和应用程序生命周期一样长。这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity。
具体原理

3. 解决方案

3.1 在Activity结束时将Handler里面的Message清空

由于在Activity结束后,Handler里面的消息还没有被处理导致,消息不处理完Handler的引用就一直存在。因而我们可以在onDestroy中将Handler里面的消息给清空了,这样就不会有消息引用Handler了,也就不会因为Handler引用Activity导致Activity无法释放了。具体代码如下:

@Override
protected void onDestroy() {
    super.onDestroy();
    //将Handler里面消息清空了。
    mHandler.removeCallbacksAndMessages(null);
}

3.2 静态内部类+弱引用

为了避免非静态内部类&匿名内部类持有外部类引用可以采用静态内部类或者直接在外部编写该Handler的继承类。如果该类需要使用Activity相关变量,可以采用弱引用的方式将Activity的变量传过去。在获取Activity的时候还可以加上判断当前Activity是不是isFinishing的代码,避免因为当前Activity已经进入了finish状态,还去引用这个Activity。具体代码如下:

public class NoLeakHandler<T> extends Handler {

    private final WeakReference<T> mTargetRef;

    public NoLeakHandler(T target) {
        mTargetRef = new WeakReference(target);
    }
    public NoLeakHandler(Looper looper, T target) {
        super(looper);
        mTargetRef = new WeakReference(target);
    }

    public NoLeakHandler(Callback callback, T target) {
        super(callback);
        mTargetRef = new WeakReference(target);
    }

    public NoLeakHandler(Looper looper, Callback callback, T target) {
        super(looper, callback);
        mTargetRef = new WeakReference(target);
    }

    @Override
    public final void dispatchMessage(Message msg) {
        //这里可以判断下当前的Activity是不是处于finish状态,如果处于activity.isFinishing()状态则target也为空
        if (checkNotLeak(mTargetRef) != null) {
            super.dispatchMessage(msg);
        }
    }

    @Override
    public final void handleMessage(Message msg) {
        T target;
        if ((target = checkNotLeak(mTargetRef)) != null) {
            processMessage(target, msg);
        }
    }
    protected void processMessage(T context, Message msg) {}
}

Utils.java


private static Class<?> sFragmentClassSupport;
private static Method sGetActivitySupport;
private static Class<?> sFragmentClass;
private static Method sGetActivity;

static {
    try {
        sFragmentClassSupport = Class.forName("android.support.v4.app.Fragment", false,
                Thread.currentThread().getContextClassLoader());
        sGetActivitySupport = sFragmentClassSupport.getDeclaredMethod("getActivity");
    } catch (Exception e) {
        sFragmentClassSupport = null;
        sGetActivitySupport = null;
    }

    try {
        sFragmentClass = Class.forName("android.app.Fragment", false,
                Thread.currentThread().getContextClassLoader());
        sGetActivity = sFragmentClass.getDeclaredMethod("getActivity");
    } catch (Exception e) {
        sFragmentClass = null;
        sGetActivity = null;
    }
}
//该方法主要判断当前的Activity是不是isFinishing,如果是则返回null。
public static <T> T checkNotLeak(WeakReference<T> ref) {
    T target = (ref != null ? ref.get() : null);
    if (target != null) {
        Activity activity = null;
        //is the target instance which shows in the foreground (usually {@link Activity} or {@link Fragment})
        boolean isShowForeground = false;
        if ((sFragmentClassSupport != null) && (sFragmentClassSupport.isInstance(target))) {
            isShowForeground = true;
            try {
                activity = (Activity) sGetActivitySupport.invoke(target, (Object[]) null);
            } catch (Exception ignored) {
            }
        } else if ((target instanceof Activity)) {
            isShowForeground = true;
            activity = (Activity) target;
        } else if ((sFragmentClass != null) && (sFragmentClass.isInstance(target))) {
            isShowForeground = true;
            try {
                activity = (Activity) sGetActivity.invoke(target, (Object[]) null);
            } catch (Exception ignored) {
            }
        }

        if (isShowForeground) {
            if ((activity == null) || activity.isFinishing()) {
                return null;
            }
        }
    }
    return target;
}

4. 总结

Handler导致内存泄漏的原因也就是因为在Looper的MessageQueue里面有还未处理的Message,而该Message又引用了Handler,如果该Handler采用的是非静态内部类或匿名内部类则该Handler又持有外部类Activity的引用,导致了Activity无法是否,因而产生了内存泄漏。
在最开始的代码中其中的线程Thread是直接new Thread(){}.start();的,这也会产生内存泄漏,因为该Thread也是匿名内部类,会持有Activity的引用,如果该Thread一直未执行完,也会导致内存泄漏。解决办法还是静态内部类+弱引用的方法。大家可以自己试试。

上一篇