Android 线程简单分析(一)
Android 并发之synchronized锁住的是代码还是对象(二)
Android 并发之CountDownLatch、CyclicBarrier的简单应用(三)
Android 并发HashMap和ConcurrentHashMap的简单应用(四)(待发布)
Android 并发之Lock、ReadWriteLock和Condition的简单应用(五)
Android 并发之CAS(原子操作)简单介绍(六)
Android 并发Kotlin协程的重要性(七)(待发布)
Android 并发之AsyncTask原理分析(八)
Android 并发之Handler、Looper、MessageQueue和ThreadLocal消息机制原理分析(九)
Android 并发之HandlerThread和IntentService原理分析(十)

Handler消息机制在Android中的重要性,在Android中Handler消息机制其实也就涉及几个组件:Handler、Looper、MesageQueue、Mesage和ThreadLocal,下面来一个一个说说?

先抛出几个异常?

  • Handler消息机制的流程是什么?
  • Handler如何处理延迟消息?
  • 为什么消息入队列和消息出队列会加同步代码块?会出现呢什么问题?
  • 消息时如何进入队列的?
  • 消息屏障也就是msg.target == null的消息的作用是什么或者如何处理的?

Handler消息机制的流程

Android 并发之Handler、Looper、MessageQueue和ThreadLocal消息机制原理分析_第1张图片 Handler机制.png

Handler创建的时候会获取当前线程的Looper,通过Looper获取MessageQueue,然后通过Handler发消息,最后Looper不断轮训取出消息交给Handler处理。

public Handler(Callback callback, boolean async) {    //获取当前线程的Looper    mLooper = Looper.myLooper();    //如果获取不到Looper则抛出异常,一般主线程中默认系统已经创建了Looper    if (mLooper == null) {    // 如果是在工作线程中,就需要手动调用Looper.prepare()创建Looper        throw new RuntimeException( "Can't create handler inside thre" + Thread.currentThread()+ " that has not called Looper.prepare()");    }    //获取到Looper的MessageQueue    mQueue = mLooper.mQueue;    mCallback = callback;    //是否是异步,后面会讲    mAsynchronous = async;}

源码有注释就不解析了,在看看sendMessageAtTime方法。

// 不管是同步消息还是异步消息最终发消息都会调用此方法public boolean sendMessageAtTime(Message msg, long uptimeMillis) {    //获取消息队列    MessageQueue queue = mQueue;    if (queue == null) {        // 如果没有消息队列,则抛异常        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");        return false;    }    // 消息准备进入队列    return enqueueMessage(queue, msg, uptimeMillis);}

在sendMessageAtTime方法最终调用enqueueMessage 方法并调用MessageQueue的enqueueMessage(msg, uptimeMillis)把消息放入队列:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this;    if (mAsynchronous) {        msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);}

来看看消息是如何加入MessageQuueue:

 boolean enqueueMessage(Message msg, long when) { //这里和next方法取出消息时判断msg.target == null不矛盾,因为系统内发消息并不经过此方法    if (msg.target == null) {        throw new IllegalArgumentException("Message must have a target.");    }    // 消息是否正在使用    if (msg.isInUse()) {        throw new IllegalStateException(msg + " This message is already in use.");    }    // 为什么加synchronized?    //因为消息队列内部维护的是单链表,线程不安全,消息入队列可能同时有消息出队列    // 那么消息出队列可能会把消息移除,还有一种情况就是入队时可能会造成顺序错乱    synchronized (this) {        if (mQuitting) {            IllegalStateException e = new IllegalStateException(                    msg.target + " sending message to a Handler on a dead thread");            Log.w(TAG, e.getMessage(), e);            msg.recycle();            return false;        }        //标志消息正在使用        msg.markInUse();        msg.when = when;        // 下面就是链表的操作了        Message p = mMessages; //head        boolean needWake; // 需不需要阻塞          //如果消息为null、时间为0或者时间比队头的小,直接插入在对头,如果队列正在阻塞就唤醒        if (p == null || when == 0 || when < p.when) {  //消息入队列,按照时间从小到大排序            msg.next = p;            mMessages = msg;            needWake = mBlocked;        } else {            // 只有在Delay的消息 mBlocked = true并且是异步的,异步消息优先级高的很          //插入队列的中间。 通常我们不必唤醒事件队列,除非队列头部有屏障,并且消息是队列中最早的异步消息。            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;            prev.next = msg;        }        /**         * 在next中,如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true,         * 在下一个Message进队时会判断这个message的位置,如果在队首就会调用nativeWake()方法唤醒线程!         */        if (needWake) {            nativeWake(mPtr);        }    }    return true;}

在来看看消息入队列:

 Message next() {    final long ptr = mPtr;    if (ptr == 0) {        return null;    }    int pendingIdleHandlerCount = -1; /    int nextPollTimeoutMillis = 0;     // 不断循环取出消息    for (; ; ) {        if (nextPollTimeoutMillis != 0) {            Binder.flushPendingCommands();        }         //挂起,即阻塞        nativePollOnce(ptr, nextPollTimeoutMillis);        // 为什么加synchronized?        //因为消息队列内部维护的是单链表,线程不安全,消息入队列可能同时有消息出队列        // 那么消息出队列可能会把消息移除,还有一种情况就是入队时可能会造成顺序错乱        synchronized (this) {            final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            Message msg = mMessages;            // 同步屏障点,优先处理所有的异步消息            if (msg != null && msg.target == null) {                do {                    prevMsg = msg;                    msg = msg.next;                } while (msg != null && !msg.isAsynchronous());            }            if (msg != null) {                /**                 *                 * 1、每个入MessageQueue之前是都有出发时间when,是按照出发使劲按从小到大排序的,默认触发时间为当前系统时间;                 * 2、延迟发送的话:系统时间 + 延迟时间;                 * 3、MessageQueue轮询时会判断:如果 触发时间 > 系统时间 说明时延迟消息,暂时不处理并阻塞对应的触发时间减去系统时间                 */                if (now < msg.when) {// 触发时间 > 系统时间 延迟消息                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                } else { // 正常消息                    mBlocked = false;                    if (prevMsg != null) {                        prevMsg.next = msg.next;                    } else {                        mMessages = msg.next;                    }                    msg.next = null;                    msg.markInUse();                    return msg;                }            } else {               /**                     *                      * 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。                     *                     * 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。                     *                     * 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。                     */                nextPollTimeoutMillis = -1;            }            if (mQuitting) {                dispose();                return null;            }            /**             * 如果有阻塞(没有消息了或者只有Delay的消息),会把mBlocked这个变量标记为true,             * 在下一个Message进队时会判断这个message的位置,如果在队首就会调用nativeWake()方法唤醒线程!             * 也就是在enqueueMessage方法中调用nativeWake()方法唤醒线程!             * 注意:nativePollOnce 方法有两种唤醒方式:1、超时;2、nativeWake方法*/            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {                pendingIdleHandlerCount = mIdleHandlers.size();            }            if (pendingIdleHandlerCount <= 0) {                mBlocked = true;                continue;            }            if (mPendingIdleHandlers == null) {                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];            }            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);        }            boolean keep = false;            try {                keep = idler.queueIdle();            } catch (Throwable t) {                Log.wtf(TAG, "IdleHandler threw exception", t);            }            if (!keep) {                synchronized (this) {                    mIdleHandlers.remove(idler);                }            }        }        nextPollTimeoutMillis = 0;    }}

注释已经很清楚了

前面讲到Handler创建时会取出当前线程的Looper,并获取Looper中的MessageQueue,然后发消息到MessageQueue,通过Looper不断轮训取出消息交给Handler处理,看看Looper是如何创建?

//构造方法private Looper(boolean quitAllowed) {    mQueue = new MessageQueue(quitAllowed);    mThread = Thread.currentThread();}// Looper 创建 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的构造方法是private的,Looper通过 Looper.prepare()方法创建,Looper创建成功并且将Looper实例保存在ThreadLocal中,在Looper创建时,会创建一个MessageQueue消息队列。

调用Looper.loop()方法开始轮训同步取出消息交给Handler处理;

loop
 public static void loop() {    //获取当前线程绑定的Looper,如果为null则抛出异常,否则取出MessageQueue    final Looper me = myLooper();    if (me == null) {        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");    }    final MessageQueue queue = me.mQueue;    // 开始不断轮训取出消息    for (; ; ) {        Message msg = queue.next(); // MessageQueue 可能会被挂起或阻塞,底层管道发通知        // 如果消息不为null,调用dispatchMessage分发消息        if (msg == null) { return; }        msg.target.dispatchMessage(msg);        //把消息放入消息池中        msg.recycleUnchecked();    }}
loop
public static @Nullable Looper myLooper() {    return sThreadLocal.get();  //获取与当前线程绑定的Looper}
Handler中的dispatchMessage方法对消息进行分发
public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}
Message中的recycleUnchecked方法:
void recycleUnchecked() {    flags = FLAG_IN_USE;    what = 0;    arg1 = 0;    arg2 = 0;    obj = null;    replyTo = null;    sendingUid = -1;    when = 0;    target = null;    callback = null;    data = null;    synchronized (sPoolSync) {        if (sPoolSize < MAX_POOL_SIZE) {            next = sPool;            sPool = this;            sPoolSize++;        }    }}

ThreadLocal

Looper是存放在ThreadLocal中,ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题,所以Looper使用ThreadLocal保存在当前线程中,其他线程对它不可见,这样就解决了,每个线程只有一个Looper实例。

 public void set(T value) {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null)        map.set(this, value);    else        createMap(t, value);} public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            T result = (T)e.value;            return result;        }    }    return setInitialValue();}

ThreadLocal作为Looper的静态成员变量,即Looper全局仅仅只有一个ThreadLocal,而存放Looper对象的并不是TheradLocal,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的内部类是一个自定义的HashMap,而ThreadLocalMap又作为Thread类的成员成员变量即每一个Thread的实例仅有一个ThreadLocalMap,所以总结就是ThreadLocal仅仅是使用了装饰模式封装了ThreadLocalMap并对ThreadLocalMap的功能进行了增强,中一个过程就是,Looper调用ThreadLocal,然后ThreadLocal通过Thread得到当前线程的ThreadLocalMap,并将Looper作为value存储到ThreadLocalMap中而Key是ThreadLocal。

每个Thread的对象都有一个ThreadLocalMap,而ThreadLocalMap是ThreadLocal的静态内部类,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象作为Key添加到该Map中,其中Key就是ThreadLocal,值可以是Looper。
在该类中,我觉得最重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样。

下面贴出一张Looper保存到TheadLocal中的时序图:


Android 并发之Handler、Looper、MessageQueue和ThreadLocal消息机制原理分析_第2张图片 looper_and_threadlocal.png

回答问题:

  • Handler消息机制的流程是什么?(文章首部有图)
    1、Handler创建的时候会获取当前线程的Looper;
    2、通过Handler通过Looper获取MessageQueuer发消息;
    3、Looper不断轮训取出消息交给Handler处理。

  • Handler如何处理延迟消息?
    1、每个消息都有触发时间when,消息进入队列是按照触发时间按从小到大排序的;
    2、默认触发时间为当前系统时间,延迟发送的:系统时间 + 延迟时间;
    3、MessageQueue轮询时会判断:如果触发时间 > 系统时间 说明时延迟消息暂时不处理并阻塞对应的触发时间减去系统时间;
    4、Handler发消息进入队列时是根据时间wahen的消息排序的,在取消息时根据当前系统时间对比,如果系统时间now < when,说明是延迟消息,
    那么 when - now = nextPollTimeoutMillis,调用nativePollOnce(ptr, nextPollTimeoutMillis)进行挂起(底层的技术),
    注意:nativePollOnce有超时换新,当然在消息入队列也会判断是否唤醒nativeWake(mPtr)

  • 为什么消息入队列和消息出队列会加同步代码块?会出现呢什么问题?
    1、因为消息队列内部维护的是单链表,是线程不安全的,
    2、消息入队列可能同时有消息出队列 那么消息出队列可能会把消息移除,还有一种情况就是入队时可能会造成顺序错乱。

  • 为什么两个锁在两个不同方法的同步代码块能起到同步?
    1、实际上是同一个对象,即MessageQueue,所以他们是同一个监视器。
    如果想更进一步了解synchronized请移步Android 并发之synchronized锁住的是代码还是对象(二)介绍

  • 消息时如何进入队列的?
    1、消息队列内部维护一个单链表,消息进入队列时是根据时间wahen的消息排序的,从小到大排序

  • 消息屏障也就是msg.target == null的消息的作用是什么或者如何处理的?
    1、消息屏障是为了区别同步消息和异步消息的优先级;
    2、在Handler中异步消息优先处理,而我们平时发送的消息全是同步消息,一般异步消息系统发的,如:系统发送更新UI的消息,这样能够在第一时间更新UI。

  • 为什么在子线程中创建Handler会crach,在主线程中就不会发生呢?
    1、这个问题如果要展开来讲,就是一篇文章,为了回答这个问题,我先上ActivityThread部分代码:

    public static void main(String[] args) {  // ...此处省略一万行代码 // 准备主线程的 Looper  Looper.prepareMainLooper();   // 创建 ActivityThread实例,用于管理应用程序进程中主线程的执行  ActivityThread thread = new ActivityThread();    // 进入 attach 方法  thread.attach(false, startSeq);   // ...此处省略一万行代码   // 开始 Looper  Looper.loop();

    }

大家都知道App启动一般都是先进入ActivityThread的 main 方法,看到Handler的身影。在安卓中都是以消息进行驱动,在这里也不例外,我们可以看到先进行 Looper 的准备,在最后开启 Looper 进行轮训获取消息,用于处理传到主线程的消息。这也是为什么我们在主线程不需要先进行 Looper 的准备和开启而子线程则必须要进行 Looper 的准备和开启。

更多相关文章

  1. Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面
  2. Android 多线程之synchronized锁住的是代码还是对象(二)
  3. Android Handler机制5之Message简介与消息对象对象池
  4. Android——多线程
  5. [51CTO]Android消息机制
  6. Android消息机制浅析——基本使用
  7. 消息驱动 Looper类
  8. input系统一 loop线程的创建与运行
  9. Android 单选队列 RadioGroup与RadioButton详解

随机推荐

  1. android 公用的ViewHolder
  2. android文件操作工具类
  3. Android(安卓)NDK r6 下载链接
  4. Android(安卓)ADB Commands Using Batch
  5. Android(安卓)微信界面 Fragment
  6. android图片异步加载解决步骤
  7. android.app.WallpaperManager壁纸管理类
  8. Android实现各种对话框的变体
  9. android 子线程handler 实现
  10. android截屏实现