从源码中看Handler、Looper、MessageQueue

概述

Handler、Looper、MessageQueue这几个,相信接触android开发的,基本都能说出来这三者的基本关系,但是很多人可能也是停留在三者的调用关系上,你如果问他postDelay是如何实现的为啥子线程中要先prepare为啥handler容易产生内存泄露等等,他可能会一知半解,笔者还是决定从源码的角度重新审视这些问题

从问题中看源码

如果让你在线程中实现handler,你可能会很快的写下如下代码,那笔者就还是以这个为切入点,分析前面提出的几个问题

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(Looper.myLooper());
                Looper.loop();
            }
        }).start();

为啥子线程需要prepare,如果不调用会有什么问题?
我们直接进入Looper.prepare()的源码中看看:

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

public static void prepare() {
    prepare(true);
}

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));
}

我们可以看到调用的其实是一个静态方法,内部的处理就是实例化了一个Looper对象,存放在sThreadLocal中了,ThreadLocal你可以理解为是线程本地存储,用于保存线程共享变量,如果不调用prepare直接进行loop操作,会怎样呢?

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
        //如果没有进行prepare操作会抛异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //拿到当前线程存储的MessageQueue
        final MessageQueue queue = me.mQueue;
		......
        for (;;) {
        //如果poseDelay方法有延时参数,这个方法会阻塞,等待延时
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ......
            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
			......
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
            //调用handler的dispatchMessage方法
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......
            msg.recycleUnchecked();
        }
    }

我们可以很清楚的看到第一行调用的实际是myLooper方法,从线程本地存储空间中拿到共享变量Looper的实例,因此如果子线程中没有进行prepare操作,这里就会抛出异常throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

那么looper和handler究竟什么联系呢?
下面是Handler的构造方法

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看到handler在初始化的时候,会持有looper以及对应looper的MessageQueue的实例

下面是Message类的属性

public final class Message implements Parcelable {
    public static final Creator<Message> CREATOR = null;
    public int arg1;
    public int arg2;
    public Object obj;
    public Messenger replyTo;
    public int sendingUid;
    public int what;

	......
    long when;
    
    Handler target;
    
    Runnable callback;
    
    Message next;
	......
}

可以很清楚的看到Message实例本身会持有handler引用,也会有延迟时长属性when,以及下一个Message的字段next

相信看了上述Looper,Handler,MessageQueue,你对他们的持有关系应该有了解了

handler的postDelayed是如何实现的?

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
     }
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

通过上面的源码,我们可以了解一点:

postDelay最终是调用MessageQueue的enqueueMessage方法,根据延迟时长完成消息排队

Message你可以理解为是一个节点,里面有next字段标识下一个消息,这里要注意上面的loop()方法中有一行代码

Message msg = queue.next(); // might block

因此当loop方法循环从MessageQueue中读取消息,这个MessageQueuenext方法会根据之前定义的延迟时长进行等待,直到达到这个时长才会返回,然后调用message.target即我们目标的handler中的dispatchMessage方法

handler为什么容易造成内存泄露
我们在使用handler的时候,如果不注意:很容易会写出

Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

要知道,匿名内部类会持有外部的引用,比如是一个activity引用,那么你如果了解上述的延迟消息的实现原理就会明白,当我们发送延迟消息的时候,这个消息会被存放在MessageQueue中,等待next方法返回,而对应的Message又会持有handler对象,就会导致我们最终引用的这个activity一直无法释放,从而导致内存泄露的产生

为啥主线程中调用loop不会阻塞呢?

每个应用都有一个主线程,当主线程启动的时候其实也会调用loop方法,但这个过程会不断的循环从MessageQueue中取消息来执行,因为android的设计本身是基于事件驱动的,因此我们都主线程中的操作都可以看做是不同的事件,最终交由主线程的handler来执行,因此如果我们在主线程中做过多耗时操作,会导致主线程阻塞,即ANR的产生,这个消息循环的模型跟以前在WindowsWin32下的消息循环类似

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值