Handler 消息机制-简要版

133 阅读4分钟

Handler是老生常谈的一个东西,无论是面试,还是项目操作,各大系统底层都绕不开它。

Handler的概述

Handler是消息通信的一种机制。Handler由Message(消息)MessageQueue(消息队列)Looper(轮循器)进行支撑。Handler的主要作用是将一个任务切换到某个指定线程中去执行。常用在子线程中进行耗时操作后要进行UI的更新。

Handler的工作原理

Handler创建完成后,通过post或者sendMessage方法将一个Runnable投递到Looper中去处理,post最终是通过send方法完成的。

当send方法被调用时,通过MessageQueueenqueueMessage方法将消息放入消息队列中。然后Looper发现有新消息来时,就会处理消息,最终消息中的Runnable或者Handler的handleMessage就会被调用。

关于ThreadLoacl

ThreadLocal是一个线程内部的数据存储类。通过它可以在指定的线程中存储数据。一般来说,当某些数据以线程为作用域且不同线程具有不同的数据副本的时候,就可以通过ThreadLoacl。其中Handler得Looper的作用域就是线程并且不同的线程具有不同的Looper。

另一个作用做对象传递。例如函数调用栈比较深,代码入口多向性,这种情况需要监听器贯穿整个线程的执行过程。就可以通过ThreadLoacl的get方法获取。

以下是set和get源码

set方法

public void set(T value) {
    Thread t = Thread.currentThread(); //获取当前线程
    ThreadLocalMap map = getMap(t); //从当前线程中获取map
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

ThreadLocalMap是线程中的局部变量,说明每一个线程中都有一个自己的局部变量ThreadLocal,并将当前线程作为key存储Looper。

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; // 通过线程获取value
            return result;
        }
    }
    return setInitialValue();
}

通过获取当前线程的key,来确保每一个线程的唯一性。

关于MessageQueue

消息队列主要包含两个操作,插入(enqueueMessage)读取(next)。enqueueMessage负责往消息队列中插入一条数据,next负责从消息队列中取出一条数据并将其从消息队列中移除。其中enqueueMessage的内部实现是通过一个单链表的数据结构来维护消息,单链表的优势在插入和删除上有优势。 而next就是一个无限循环,如果消息队列没有消息,那么next方法就会一直阻塞等待。有兴趣的可以去点开Handler中的send方法翻阅一下源码。

关于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.");
    }
    
    ///...略...
    
for (;;) {
    if (!loopOnce(me, ident, thresholdOverride)) {
        return;
    }
}

}

跳出循环的方法就是MeessageQueue的next方法返回了null。

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    //...略...
  }

关于Handler

上边也讲过,send和post方法,最终调用都是send方法。

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}


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

在最后会调用sendMessageAtTime并拆入一条消息,交由looper处理,如此循环就完成了Handler消息处理。

无构造参数的Handler创建已经被标记为了过时,在创建Handler时必须指定一个Looper。

关于主线程的消息循环

主线程就是ActivityThread,主线程的入口方法为main(),在main中通过Looper.prepare()来创建主线程的Looper以及MessageQueue并通过Looper.loop()来开启主线程中的的消息循环。而所需的Handler,则在ActivityThread.H中,H内部定义了一组消息类型,包括四大组件的启动和停止。

通信过程就是通过Application和AMS,然后通过AMS的回调ApplicationThread的Binder方法。然后ActivityThread向H发送消息,H收到消息后会将ApplicationThread的逻辑切换到ActivityThread(主线程)中执行。

扩展

1.为什么不允许在子线程中访问UI呢?

因为Android的UI控件不是线程安全的,如果在多线程中并发访问会出现不可控状态。如果加上锁机制,会让UI的逻辑变得复杂,再就是锁机制会降低UI访问的效率,锁机制会阻塞某些线程的执行。所以最简单高效的方法就是采用单线程模型来处理UI操作。

2.Handler如何保证只有一个Looper?

在每个线程中的ThreadLocalMap中,并且将当前线程作为Key。当我们通过Handler创建子线程的时候,先调用prepare()方法,来确保ThreadLocal中只有一个Loope

3.ThreadLocal会内存泄漏吗?

不会,因为ThreadLoalMap是通过弱引用的形式继承实现的。

4.如何区分异步消息和同步消息?

// TODO

5.子线程中更新UI的方式

Thread {
    Looper.prepare()
    //...略...
    Looper.loop()
}