Handler消息机制详解

版权声明:如果有帮助,请点个赞!如果有帮助,请点个赞!如果有帮助,请点个赞! https://blog.csdn.net/feather_wch/article/details/79263855

转载请注明链接:https://blog.csdn.net/feather_wch/article/details/79263855

详解Handler消息机制,包括Handler、MessageQueue、Looper和LocalThread。

本文是我一点点归纳总结的干货,但是难免有疏忽和遗漏,希望不吝赐教。

Handler消息机制详解(24题)

版本:2018/9/3-1(1946)


消息机制概述

1、Handler是什么?

  1. Android消息机制的上层接口(从开发角度)
  2. 能轻松将任务切换到Handler所在的线程中去执行
  3. 主要目的在于解决在子线程中无法访问UI的矛盾

2、消息机制?

  1. Android的消息机制主要就是指Handler的运行机制
  2. Handler的运行需要底层MessageQueueLooper的支撑

3、MeesageQueue是什么?

  1. 消息队列
  2. 内部存储结构并不是真正的队列,而是单链表的数据结构来存储消息列表
  3. 只能存储消息,而不能处理

4、Looper是什么?

  1. 消息循环
  2. Looper以无限循环的形式去查找是否有新消息,有就处理消息,没有就一直等待着。

5、ThreadLocal是什么?

  1. Looper中一种特殊的概念
  2. ThreadLocal并不是线程,作用是可以在每个线程中互不干扰的存储数据提供数据
  3. Handler创建时会采用当前线程的Looper来构造消息循环系统,Handler内部就是通过ThreadLocal来获取当前线程的Looper
  4. 线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper
  5. UI线程就是ActivityThread,被创建时会初始化Looper,因此UI线程中默认是可以使用Handler

6、为什么不能在子线程中访问UI?

ViewRootImpl会对UI操作进行验证,禁止在子线程中访问UI:

void checkThread(){
  if(mThread != Thread.currentThread()){
    throw new CalledFromWrongThreadException("Only th original thread that created a view hierarchy can touch its views");
  }
}

7、Handler的要点

  1. Handler创建时会采用当前线程的Looper
  2. 如果当前线程没有Looper就会报错,要么创建Looper,要么在有Looper的线程中创建Handler
  3. Handlerpost方法会将一个Runnable投递到Handler内部的Looper中处理(本质也是通过send方法完成)
  4. Handlersend方法被调用时,会调用MessageQueueenqueueMessage方法将消息放入消息队列, 然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者HandlerhandleMessage就会被调用
  5. 因为Looper是运行在创建Handler所在的线程中的,所以通过Handler执行的业务逻辑就会被切换到Looper所在的线程中执行。

ThreadLocal

8、ThreadLocal的作用

  1. ThreadLocal是线程内部的数据存储类,可以在指定线程中存储数据,之后只有在指定线程中才开业读取到存储的数据
  2. 应用场景1:某些数据是以线程为作用域,并且不同线程具有不同的数据副本的时候。ThreadLocal可以轻松实现Looper在线程中的存取。
  3. 应用场景2:在复杂逻辑下的对象传递,通过ThreadLocal可以让对象成为线程内的全局对象,线程内部通过get就可以获取。

9、ThreadLocal的使用

mBooleanThreadLocal.set(true);
Log.d("ThreadLocal", "[Thread#main]" + mBooleanThreadLocal.get());

new Thread("Thread#1"){
    @Override
    public void run(){
        mBooleanThreadLocal.set(false);
        Log.d("ThreadLocal", "[Thread#1]" + mBooleanThreadLocal.get());
    }
}.start();

new Thread("Thread#2"){
    @Override
    public void run(){
        Log.d("ThreadLocal", "[Thread#2]" + mBooleanThreadLocal.get());
    }
}.start();
  1. 最终main中输出true; Thread#1中输出false; Thread#2中输出null
  2. ThreadLocal内部会从各自线程中取出数组,再根据当前ThreadLocal的索引去查找出对应的value值。

10、ThreadLocal的set()源码分析

//ThreadLocal.java
public void set(T value) {
    //1. 获取当前线程
    Thread t = Thread.currentThread();
    //2. 获取当前线程对应的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //3. map存在就进行存储
        map.set(this, value);
    else
        //4. 不存在就创建map并且存储
        createMap(t, value);
}
//ThreadLocal.java内部类: ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
    //1. table为Entry数组
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //2. 根据当前ThreadLocal获取到Hash key,并以此从table中查询出Entry
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //3. 如果Entry的ThreadLocal与当前的ThreadLocal相同,则用新值覆盖e的value
        if (k == key) {
            e.value = value;
            return;
        }
        //4. Entry没有ThreadLocal则把当前ThreadLocal置入,并存储value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //5. 没有查询到Entry,则新建Entry并且存储value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
//ThreadLocal内部类ThreadLocalMap的静态内部类
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

11、ThreadLocal的get()源码分析

public T get() {
    //1. 获取当前线程对应的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //2. 取出map中的对应该ThreadLocal的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //3. 获取到entry后返回其中的value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4. 没有ThreadLocalMap或者没有获取到ThreadLocal对应的Entry,返回规定数值
    return setInitialValue();
}
private T setInitialValue() {
    //1. value = null
    T value = initialValue();//返回null
    Thread t = Thread.currentThread();
    //2. 若不存在则新ThreadLocalMap, 在里面以threadlocal为key,value为值,存入entry
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
  1. 当前线程对应了一个ThreadLocalMap
  2. 当前线程的ThreadLocal对应一个Map中的Entry(存在table中)
  3. Entrykey会获取其对应的ThreadLocal, value就是存储的数值

消息队列: MessageQueue

12、MessageQueue的主要操作

  1. enqueueMessage: 往消息队列中插入一条消息
  2. next:取出一条消息,并且从消息队列中移除
  3. 本质采用单链表的数据结构来维护消息队列,而不是采用队列

13、MessageQueue的插入和读取源码分析

//MessageQueue.java:插入数据
boolean enqueueMessage(Message msg, long when) {
    //1. 主要就是单链表的插入操作
    synchronized (this) {
        ......
    }
    return true;
}
/**==========================================
 * 功能:读取并且删除数据
 * 内部是无限循环,如果消息队列中没有消息就会一直阻塞。
 * 一旦有新消息到来,next方法就会返回该消息并且将其从单链表中移除
 *===========================================*/
Message next() {
    for (;;) {
        ......
    }
}

14、MessageQueue的next源码详解

    Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
            /**======================================================================
             * 1、精确阻塞指定时间。第一次进入时因为nextPollTimeoutMillis=0,因此不会阻塞。
             *   1-如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
             *   2-如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
             *   3-如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
             *====================================================================*/
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 当前时间
                final long now = SystemClock.uptimeMillis();
                Message msg = mMessages;
                /**=======================================================================
                 * 2、当前Msg为消息屏障
                 *   1-说明有重要的异步消息需要优先处理
                 *   2-遍历查找到异步消息并且返回。
                 *   3-如果没查询到异步消息,会continue,且阻塞在nativePollOnce直到有新消息
                 *====================================================================*/
                if (msg != null && msg.target == null) {
                   // 遍历寻找到异步消息,或者末尾都没找到异步消息。
                    do {
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                /**================================================================
                 *  3、获取到消息
                 *    1-消息时间已到,返回该消息。
                 *    2-消息时间没到,表明有个延时消息,会修正nextPollTimeoutMillis。
                 *    3-后面continue,精确阻塞在nativePollOnce方法
                 *===================================================================*/
                if (msg != null) {
                    // 延迟消息的时间还没到,因此重新计算nativePollOnce需要阻塞的时间
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 返回获取到的消息(可以为一般消息、时间到的延迟消息、异步消息)
                        return msg;
                    }
                } else {
                    /**=============================
                     * 4、没有找到消息或者异步消息
                     *==============================*/
                    nextPollTimeoutMillis = -1;
                }

                /**===========================================
                 * 5、没有获取到消息,进行下一次循环。
                 *   (1)此时可能处于的情况:
                 *      1-没有获取到消息-nextPollTimeoutMillis = -1
                 *      2-没有获取到异步消息(接收到同步屏障却没找到异步消息)-nextPollTimeoutMillis = -1
                 *      3-延时消息的时间没到-nextPollTimeoutMillis = msg.when-now
                 *   (2)根据nextPollTimeoutMillis的数值,最终都会阻塞在nativePollOnce(-1),
                 *      直到enqueueMessage将消息添加到队列中。
                 *===========================================*/
                if (pendingIdleHandlerCount <= 0) {
                    // 用于enqueueMessage进行精准唤醒
                    mBlocked = true;
                    continue;
                }
            }
        }
    }
  1. 如果是一般消息,会去获取消息,没有获取到就会阻塞(native方法),直到enqueueMessage插入新消息。获取到直接返回Msg。
  2. 如果是同步屏障,会去循环查找异步消息,没有获取到会进行阻塞。获取到直接返回Msg。
  3. 如果是延时消息,会计算时间间隔,并进行精准定时阻塞(native方法)。直到时间到达或者被enqueueMessage插入消息而唤醒。时间到后就返回Msg。

Looper

15、Looper的构造

private Looper(boolean quitAllowed) {
    //1. 会创建消息队列: MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //2. 当前线程
    mThread = Thread.currentThread();
}

16、为线程创建Looper

//1. 在没有Looper的线程创建Handler会直接异常
new Thread("Thread#2"){
    @Override
    public void run(){
        Handler handler = new Handler();
    }
}.start();

异常:
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

//2. 用prepare为当前线程创建一个Looper
new Thread("Thread#2"){
    @Override
    public void run(){
        Looper.prepare();
        Handler handler = new Handler();
        //3. 开启消息循环
        Looper.loop();
    }
}.start();

17、主线程ActivityThread中的Looper

  1. 主线程中使用prepareMainLooper()创建Looper
  2. getMainLooper能够在任何地方获取到主线程的Looper

18、Looper的退出

  1. Looper的退出有两个方法:quitquitSafely
  2. quit会直接退出Looper
  3. quitSafely只会设置退出标记,在已有消息全部处理完毕后才安全退出
  4. Looper退出后,Handler的发行的消息会失败,此时send返回false
  5. 子线程中如果手动创建了Looper,应该在所有事情完成后调用quit方法来终止消息循环

19、Looper的loop()源码分析

//Looper.java
public static void loop() {
    //1. 获取Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //2. 获取消息队列
    final MessageQueue queue = me.mQueue;
    ......
    for (; ; ) {
        //3. 获取消息,如果没有消息则会一直阻塞
        Message msg = queue.next();
        /**=================================
         * 4. 如果消息获得为null,则退出循环
         * -Looper退出后,next就会返回null
         *=================================*/
        if (msg == null) {
            return;
        }
        ......
        /**==========================================================
         * 5. 处理消息
         *  -msg.target:是发送消息的Handler
         *  -最终在该Looper中执行了Handler的dispatchMessage()
         *  -成功将代码逻辑切换到指定的Looper(线程)中执行
         *========================================================*/
        msg.target.dispatchMessage(msg);
        ......
    }
}

Handler

20、Handler使用实例post/sendMessage

post

handler.post(new Runnable() {
        @Override
        public void run() {

        }
});

sendMessage

// 自定义msg的what
static final int INT_WHAT_MSG = 1;
// 0、两种创建Msg的方法
Message message = new Message();
message = Message.obtain();
// 1、自定义MSG的类型,通过what进行区分
message.what = INT_WHAT_MSG;
// 2、发送Msg
handler.sendMessage(message);
// 3、自定义Handler处理Msg
class MsgHandler extends android.os.Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case INT_WHAT_MSG:
                    // 识别出了Msg,进行逻辑处理
                    break;
                default:
                    break;
            }
        }
}

post内部还是通过sendMessage实现的。

21、Handler的post/send()源码分析

//Handler.java: post最终是通过send系列方法实现的
//Handler.java
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}
//Handler.java
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//Handler.java
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);
}
//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //1. 最终是向消息队列插入一条消息
    return queue.enqueueMessage(msg, uptimeMillis);
}
  1. sendMessage()会将消息插入到消息队列中
  2. MessageQueuenext方法就会返回这条消息交给Looper
  3. 最终Looper会把消息交给HandlerdispatchMessage

22、Handler的postDelayed源码分析

    //Handler.java---层层传递,和一般的post调用的同一个底层方法.
    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    //xxxxxx
    //Handler.java
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        ...
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    //MessageQueue.java
    boolean enqueueMessage(Message msg, long when) {
        //会直接加进队列
    }
  1. postDelayed和post调用的底层sendMessage系列方法,只不过前者有延迟,后者延迟参数=0。
  2. 最终会直接将Msg加入到队列中。
  3. MessageQueue.next()在取出Msg时,如果发现消息A有延迟且时间没到,会阻塞消息队列。
  4. 如果此时有非延迟的新消息B,会将其加入消息队列, 且处于消息A的前面,并且唤醒阻塞的消息队列。
  5. 唤醒后会拿出队列头部的消息B,进行处理。然后会继续因为消息A而阻塞。
  6. 如果达到了消息A延迟的时间,会取出消息A进行处理。

23、Handler的消息处理源码

//Handler.java
public void dispatchMessage(Message msg) {
    //1. Msg的callback存在时处理消息——Handler的post所传递的Runnable
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        /**===============================================
         *2. mCallback不为null时调用handleMessage
         * -Handler handle = new Handler(callback)
         * -好处在于不需要派生Handler子类并且也不需要重写其handleMessage
         *=============================================*/
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //3. 如果其他条件都不符合,最后会调用Handler的handleMessage进行处理
        handleMessage(msg);
    }
}
//Handler.java-调用Handler的post所传递的Runnable的run()方法
private static void handleCallback(Message message) {
    message.callback.run();
}
//Handler.java-Callback接口用于不需要派生Handler就能完成功能
public interface Callback {
    public boolean handleMessage(Message msg);
}

24、Handler的特殊构造方法

  1. Handler handle = new Handler(callback);-不需要派生Handler
  2. 通过特定Looper构造Handler
public Handler(Looper looper) {
    this(looper, null, false);
}
  1. 默认构造函数
public Handler(Callback callback, boolean async) {
    ......
    mLooper = Looper.myLooper();
    //1. 在没有Looper的线程中创建Handler
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

主线程的消息循环

25、主线程ActivityThread的消息循环

//ActivityThread.java
public static void main(String[] args) {
    //1. 创建主线程的Looper和MessageQueue
    Looper.prepareMainLooper();
    ......
    //2. 开启消息循环
    Looper.loop();
}
/**=============================================
 * ActivityThread中需要Handler与消息队列进行交互
 * -内部定义一系列消息类型:主要有四大组件等
 * //ActivityThread.java
 *=============================================*/
private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    ......
}
  1. ActivityThread通过ApplicationThreadAMS进行IPC通信
  2. AMS完成请求的工作后会回调ApplicationThread中的Binder方法
  3. ApplicationThread会向Handler H发送消息
  4. H接收到消息后会将ApplicationThread的逻辑切换到ActivityThread中去执行

面试题:练一练

1、Android如何保证一个线程最多只能有一个Looper?

1-Looper的构造方法是private,不能直接构造。需要通过Looper.prepare()进行创建,

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

2-如果在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException异常

public class Looper {

    static final HashMap<Long, Looper> looperRegistry = new HashMap<Long, Looper>();

    private static void prepare() {
        synchronized(Looper.class) {
            long currentThreadId = Thread.currentThread().getId();
            // 根据线程ID查询Looper
            Looper l = looperRegistry.get(currentThreadId);
            if (l != null)
                throw new RuntimeException("Only one Looper may be created per thread");
            looperRegistry.put(currentThreadId, new Looper(true));
        }
    }
    ...
}

2、Handler消息机制中,一个looper是如何区分多个Handler的?

  1. Looper.loop()会阻塞于MessageQueue.next()
  2. 取出msg后,msg.target成员变量就是该msg对应的Handler
  3. 调用msg.target的disptachMessage()进行消息分发。这样多个Handler是很容易区分的。

3、主线程向子线程发送消息的方法?

  1. 通过在主线程调用子线程中Handler的post方法,完成消息的投递。
  2. 通过HandlerThread实现该需求。

猜你喜欢

转载自blog.csdn.net/feather_wch/article/details/79263855