面试之为什么Handler会存在内存泄露

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiatiandefeiyu/article/details/82184806

这两天面试被问到Handler的通信机制,这个问题还是很好回答的,Handler负责发送消息和在handleMessage方法中处理消息,它将消息发送给给消息队列MessageQueue,然后Loop不断从消息队列中取出消息调用handler的dispatchMessage方法,最后调用handleMessage方法交给handler来处理消息,如下:

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

回答完这个问题,感觉自己答的还可以,但是紧接着又来了handler为什么会存在内存泄露的风险,最后谁持有了它的引用,让它无法回收?在听到这个问题的时候,有点懵逼了,实在想不起来了,回来之后,内心还是很坎坷的,那么再来重新梳理一下它的源码吧。

大家都知道垃圾回收器(GC)是用来回收堆内存的,只用当栈中和方法区中不存在对象的引用,那么这个对象在堆中所占有的内存才会被回收,也就是标记清除算法,每次检查是否存在这个对象的引用链,存在不会回收内存,不存则会回收内存。

UI线程和子线程的常用通信就是直接在子线程用handler.sendMessage将消息传给UI线程来处理,那么既然UI线程能自动处理消息的话,在应用进程启动的情况下,它就开启了Loop循环来死循环处理消息,在app进程启动后第一个执行的方法是ActivityThreadmain方法,如下:

public static void main(String[] args) {
       //省略若干行......
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
		}

可以看见ActivityThread通过Loop.prepareMainLooper创建了Loop,然后通过Loop.loop开启了循环遍历消息,先来看一下Loop.prepareMainLooper(这里用的android8.1源码),如下:

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

可以看出这个方法只能被调用一次,如果用户主动调用直接抛异常,继续看prepare(false)方法

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

静态方法只能使用静态变量,那么sThread是静态的也就是全局的,如下:

  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

线程私有变量,也就是每一个创建的Loop都是线程私有的,当前线程的Loop的使用是互不影响的,到这里也就在主线程中创建了Loop对象,接下来看一下Loop的构造函数:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

它做了两件事情,第一是创建了一个消息队列,第二是持有当前线程的引用。接下来就是调用了Loop.loop()方法,如下:

 public static void loop() {
        //得到当前的Loop当前在主线程,所以得到的就是刚刚创建的Loop
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //创建Loop时创建了消息队列mQueue
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
/**
 * 开始循环处理消息
 */
        for (;;) {
            //从消息队列中取得消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                //注意这里是重点
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            //处理完消息回收

            msg.recycleUnchecked();
        }
    }

这个方法是不断的死循环从消息队列MessageQueue中去拿消息,拿到没有延时处理的消息后,什么是延时处理的消息?就是你发送一个延时消息,MessageQueue的next的方法会从消息链表中一个个取消息判断当前时间是大于等于消息要执行的消息,如果满足条件的话就会返回消息,并执行,如果延时的话就会调用底层的nativePollOnce(ptr, nextPollTimeoutMillis);方法进行延时的堵塞,直到时间的到达或者有新的消息的插入唤醒。

调用msg.target.dispatchMessage(msg);方法去处理消息,这里的target就是咱们声明的Handler,代码如下:

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里的msg.callback就是runnable,例如你这么执行

 handler.post(new Runnable() {
            @Override
            public void run() {
                Log.i("huoying","第二个");
            }
        });

如果这么设置了最终将调用run方法,而不去执行handler的回调方法handleMessage,接下来如果设置了mCallback 的调用mCallback.handleMessage,而不执行handlerhandleMessage方法,默认mCallback 为null。看到这也就知道handler为什么存在内存泄露了,假如你这么在activity中声明handler,如下:

private Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {

        super.handleMessage(msg);
    }
};

这里相当于在Activity中声明了一个内部类,内部类默认会持有外部类的引用,也就是handler持有activity的引用而Message持有handler引用,如果在你退出Activity的时候,有一个消息还没有处理的话,那么这时候Activity是没法回收的,因为在方法区里面有static final ThreadLocal<Looper> sThreadLocal持有Loop引用,而Loop持有MessageQueue引用,MessageQueue持有Message引用,Message持有Handler引用,handler持有Activity引用,所以Activity无法回收造成内存泄露。引用链如下

方法区-》sThreadLocal-》Loop-》MessageQueue-》Message-》Handler-》Activity

 

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页