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是什么?

扫描二维码关注公众号,回复: 3017263 查看本文章
  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