Handler 源码分析

一、Handle的简单使用

废话不多说,先来看看Handler的简单使用。

val mHandler = Handler(object : Callback() {
    fun handleMessage(msg: Message?): Boolean {
        //处理消息
        return false
    }
})

以上是处理消息的过程,比如网络返回之后需要更新UI之类的操作可以在Handler的handleMessage回调中完成。

mHandler.message = msg

发送消息自然是使用handler的message赋值。当然发送消息还有POST的方式,我们在这里就不深入挖掘了两种方式的区别了。直奔主题。

二、Handler的构造函数解析

@Deprecated
public Handler() {
    this(null, false);
}

最新的源码中把这个构造函数废弃了,废弃的原因总结一句话就是这样用可能出现bug,所以就不要用了。哈哈哈哈哈。

/**
 * Use the provided {@link Looper} instead of the default one.
 *
 * @param looper The looper, must not be null.
 */
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

/**
 * Use the provided {@link Looper} instead of the default one and take a callback
 * interface in which to handle messages.
 *
 * @param looper The looper, must not be null.
 * @param callback The callback interface in which to handle messages, or null.
 */
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}

现在推荐的是这样的构造函数。显式传递looper,所有不显示传递looper的构造方法都被标记废弃掉了。至于原因就和上面废弃的原因返过来就好了。

那Handler在构造函数里都做了啥呢,不要着急,一般构造函数里都是进行了一些变量的初始化。Handler也不例外。

在分析构造函数之前我们先来看看Handler处理消息的整个流程。

三、 Handler处理消息的流程解析

话不多说,先来看一张图。

在这里插入图片描述

这一张图描述的应该是描述清楚了。

Handler发送消息到消息队列里,Looper从消息队列里去取消息,然后再发回给Handler处理。其中的细节我们再一一讨论。

四、Handler的初始化以及发送消息解析

4.1 初始化

在构造函数那部分我们说到,构造函数里面进行了初始化的操作。我们来看看。

public Handler(@Nullable Callback callback, boolean async) {
  	//.......
    //隐藏部分代码
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

没错的,构造函数里初始化了looper,queue,还有回调和是不是异步消息。当然有人就问那显式传递looper的构造函数里面怎么初始化的呢,没错,大家猜到了,大同小异。

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

只是少了从Looper中取looper实例的操作。

4.2 发送消息解析

发送消息大家最常用的是什么呢,大声的说出来,sendMessage(),没错呢,就是它。

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

sendMessage方法调用了sendMessageDelayed方法。

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageDelayed又调用了sendMessageAtTime方法

public boolean sendMessageAtTime(@NonNull 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);
}

不知道大家发现没,在sendMessageAtTime方法中把之前在Handler构造函数中初始化完成的mQueue拿过来了,什么没有注意到?

public Handler(@Nullable Callback callback, boolean async) {
  	//.......
    //隐藏部分代码
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
  	//消息队列赋值
    mQueue = mLooper.mQueue; 
    mCallback = callback;
    mAsynchronous = async;
}

再放一遍初始化的过程,不知道大家发现没,这个消息队列是把looper的消息队列拿过来赋值了。

咱们接着往下走。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

最终发送消息是到了 enqueueMessage方法里,最后调用了queue.enqueueMessage,这是消息队列里的方法呢。说明到这里,Handler发送消息的路程到此结束了,下一棒就交给queue了。看源码可以发现,所有的消息发送方法都会走到这个方法里面来,因为所有的消息都是需要插入到消息队列里面的,我们在Handler没有发现插入队列的操作,那么会不会在MessageQueue里面呢,那我们收拾行李去往MessageQueue。

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

   //....省略部分代码
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
  		//...省略部分代码
    }
    return true;
}

我们可以看到这个方法里是处理了把消息插入到消息队列里了,这里处理了两个逻辑。

 if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        }

第一个逻辑就是如果这个消息不是延时消息或者这个消息比头部的消息要执行的时间要早,则把这个消息插入到头部。这里我们看到还有一个needWake的赋值操作,这个我们再聊。

 	Message prev;
  for (;;) {
  	prev = p;
   	p = p.next;
    if (p == null || when < p.when) {
    	break;
    }
    if (needWake && p.isAsynchronous()) {
      needWake = false;
    }
  }
  msg.next = p; 
  prev.next = msg;

第二个逻辑就是这个消息不满足第一个逻辑的条件的话,就在队列里找到合适的位置插入。这使用了双指针的操作方式,直接插入到合适的位置。没有很复杂的逻辑操作,是吧。

那么分析到这里大家应该清楚了,这个消息队列其实是一个自己维护的一个单链表结构。连接的先后顺序就是消息的开始执行时间了。整个的消息发送流程到这里就结束了。是的,没错的,到这里就结束了。简单梳理了下整个消息发送的流程。

五、Handler分发消息和处理消息解析

看之前的消息处理流程图,我们是不是漏掉了一个重要角色呢,没错就是Looper。那么分发消息就是我们的Looper来操作的。大家还记不记得我们的looper怎么来的呢,你说对了,就是调用了Looper里面myLooper()方法。我们去看看吧。

 /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

哎,怎么出现了一个新东西。sThreadLocal是什么呢。我们再看。

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

是不是很迷惑,怎么越来越复杂了呢。我们再回到上一步,看看looper的获取方式,是不是发现了一个get()方法。我们去看看get()方法里面怎么获取的。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

哦豁,我们看前两句话,获取当前线程,再获取当前线程的Map。看看getMap()方法哟

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

原来这个Map是存放在了Thread中啊。

我们捋一下这个逻辑啊,每个线程中都有一个ThreadLocalMap变量存储当前线程需要存储的东西,这样不同的线程访问同一个Map变量得到值就不一样。这样的做的好处就是做到每个线程之间隔离,每个线程只访问自己的map。有人就问了,为什么不用map直接存放线程和局部变量呢。个人觉得这样做的话,每个线程之间会有联系了,会容易造成内存泄漏。

既然我们搞清楚了looper怎么获取的,那么就会有一个新的问题了,looper怎么创建的呢?

大家应该注意到了吧,在获取looper的方法上有一个注释,翻译过来就是返回当前线程相关联的looper对象,如果没有关联则返回null。那么在哪里关联的呢?是不是好奇怪?

揭晓谜底。

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

是不是很眼熟,看到上面那句注释,如果你不先调用prepare(),那么就会返回null。那我们去看看这个方法吧。

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

prepare最后会调用到这个方法,看看怎么赋值的呀。进入到set方法里瞧瞧。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

似曾相识,获取当前线程,获取map。不一样的是,用当前线程当做key,然后把值塞到map里。这里就是looper和线程绑定了,一个线程对应一个looper。

既然我们知道了looper是怎么塞到map里的,但是呢,looper里面都有啥,looper的构造函数里面都有啥我们还不知道呀。肯定会有消息队列呢。

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

看吧,也是初始化了队列和获取了当前线程。似乎到这里我们分析完了,大结局了吗?但是大家有没有想过,是在哪里开启的消息循环呢,我们使用的handler的时候,也没有调用prepare方法啊,从头到尾似乎都没有讲到消息是在哪里分发并处理的呢。大家想想会是在哪呢。应用程序的入口ActivityThread,nice,你答对了。

public static void main(String[] args) {
   //...省略代码

    Looper.prepareMainLooper();

 	//....省略代码
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

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

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

   //....省略代码
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

以上是处理过得ActivityThread的main方法我们只看和消息有关的代码,一开始调用了 Looper.prepareMainLooper(),不用想就和我们之前分析的一样,这里是和主线程绑定并把looper塞到map里。

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,这个参数允不允许退出的意思。不明白的同学,可以往上翻翻瞧一瞧。为什么会设置为false呢,这是主线程的消息循环啊,一旦主线程的消息循环退出了,那么整个APP也就被杀死了。是不是这个道理。

接着往下看,我们又看了一个和Looper相关的方法 Looper.loop()。我们去看看内部的操作。

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    if (me.mInLoop) {
        Slog.w(TAG, "Loop again would have the queued messages be executed"
                + " before this one completed.");
    }

    me.mInLoop = true;
    final MessageQueue queue = me.mQueue;
		//....省略代码
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

       //....省略代码
        try {
            msg.target.dispatchMessage(msg);
          //.....省略代码
        }
    }
}

我们只看关键的操作,显示取出当前的looper,然后开启死循环,取消息queue.next()。我们接着往下走

Message next() {
  
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
    }
}

这也是省略了无数的代码,我们可以看到内部也是开启了死循环。取出合适的消息并返回。

我们再看 loop里面的这个这句话 msg.target.dispatchMessage(msg),这个target是何方神圣呢,大家有没有想起来呢。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);

}

看到这段代码是不是很清晰了呢。还不清晰,再看看下面段代码。

 Handler target;

没错,就是把当前的handler保存了起来,正所谓冤有头债有主啊,你把我送出去,我得知道是你把我送出去的,我还会来找你的。这下就清晰了吧,是怎么准确找到Handler处理消息的。

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

喜闻乐见的回调处理,兜兜转转就回到了Handler。到此整个消息处理流程都完结了。不知道大家是不是还有很多疑问呢。

六、Handler的一些疑问

6.1 为什么开启消息死循环不会出现ANR

哈哈哈哈,老生常谈的问题,先说说为什么开启消息死循环。

因为Android各种动作是需要依靠Handler来处理消息的,是需要各种事件的,是需要不断的进行事件分发处理的。比如界面的交互处理等等,可以说的是,我们的程序就是运行在死循环中。

ANR是什么呢?ANR是Application Not Responding也就是程序无响应。为什么会出现这个情况呢,大家都说因为阻塞主线程了啊,其实根本上是阻塞了Looper中的loop方法。导致loop没办法处理其他的message,就造成了ANR。Android是依靠Linux 内核的,所以这里就涉及到pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法。

有消息时,就会唤起线程处理消息。

6.2 Handler导致的内存泄漏的原因

一说到这个问题就是Handler持有了外部的引用,导致内存无法回收。其实是不明确的,这部分不会被回收的引用是啥呢,其实看整个调用链,从Handler的初始化到looper再到Handler,其实它一直持有的是主线程的引用,主线程会被回收吗,不会的。

6.3 Handler是怎么做到切换线程的呢?

不知道大家在上面的讲解中有没有发现,Handler很自然的就切换到了主线程。怎么做到的呢,答案是共享内存,就是那个消息队列啊。

你的Handler是在主线程里创建的,使用Handler在子线程里面发送消息之后,通过上面的一层层调用最后回到Handler,而此时Handler在哪呢,主线程,就这样很顺滑的切换了线程。

6.4 Handler、Looper、MessageQueue、线程是一一对应关系吗?

Looper和MessageQueue是一一对应的,MessageQueue是在looper的构造函数里面初始化的,而looper又是和线程一一绑定的,但是Handler可以多对一,因为可能是不同的Handler发送的消息。

七、总结

其实关于Handler还有很多的知识点,比如异步消息,同步屏障等。pipe/epoll机制是个啥?怎么唤醒和阻塞的消息队列的呢?诸多问题,再接再励。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值