Android之消息机制学习

940 阅读11分钟

前言

Android的日常开发离不开消息机制,本主题主要就Android的消息机制进行学习与探讨。

Android消息机制主要是通过Handler实现的,而Handler底层需要MessageQueueLooper来分别实现消息队列以及消息处理。

1. 概述

首先来看一下Handler的作用。

根据上文所述,MessageQueue是消息队列用于存储消息,Looper是循环处理用于处理消息,那么Handler是干嘛的呢?Handler的主要作用就是将某一任务(如发送消息,处理消息)放到特定的线程中去执行。

  1. 为什么非要在特定线程中去执行?

    这是由于Android规定了对UI进行操作只可以在主线程进行,在ViewRootImpl中有着UI操作线程的验证。具体如下:

    //ViewRootImpl.java
        void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    
  2. 为什么UI操作要规定只能主线程去执行?

    首先,观察UI控件的源码就知道,控件是线程不安全的,因此多线程并行操作UI会导致UI控件状态不可控。

  3. 为什么不写成线程安全的呢?

    线程安全意味着加锁,加锁意味着UI访问逻辑繁琐并且降低了UI访问的效率,毕竟加锁操作会阻塞其他一些线程的运行。

    因此似乎只能够从主线程对UI进行操作,但是Android又说耗时操作呢最好别在主线程使用,否则可能会造成ANR(Application No Response)。

那么当我们想进行UI修改等耗时过程时就必须在主线程操作,该怎么办呢?

这就是Handler机制的工作了,Handler机制可以将UI操作线程切换到主线程来执行。

大概过程如下:

Handler通过post方法将Runnable放到Looper中进行处理,或者通过send方法将Message发送到Looper中进行处理。

postDelayed方法为例,

//Handler.java

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        ......
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ......
        return enqueueMessage(queue, msg, uptimeMillis);
    }

   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       ...... 
       return queue.enqueueMessage(msg, uptimeMillis);
    }

其内部调用流程大致如上,post方法实质上也是调用send方法,然后调用MessageQueue中的enqueueMessage,将消息加入到消息队列中。

//MessageQueue.java

    boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {//Handler内部机制是自己加锁的
            if (mQuitting) {//判断线程是否已经销毁
                .....
            }
			//占用消息,并备注占用的时间
            msg.markInUse();
            msg.when = when;
            //往下开始将消息加入消息队列
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //新的消息队列头,如果被阻塞了,唤醒事件队列
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                ......
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            ......
        }
        return true;
    }

这一小节大致知道其中流程即可,不具体分析代码。

在此之后,Looper发现新消息到来,对消息进行处理。

这里有个挺有意思的逻辑关系,从MessageQueue的注释中可以看到,MessageQueue可以用来存储消息,但消息并不是直接加入到消息队列,而是通过Handler对象交给Looper进行调度的。

//MessageQueue.java

/**
 * Low-level class holding the list of messages to be dispatched by a
 * {@link Looper}.  Messages are not added directly to a MessageQueue,
 * but rather through {@link Handler} objects associated with the Looper.
 *
 * <p>You can retrieve the MessageQueue for the current thread with
 * {@link Looper#myQueue() Looper.myQueue()}.
 */

Looper源码中,注释中给出了最终处理消息的方式。在内部也声明了线程以及消息队列。

捋一捋三者之间的关系,可以说Looper是指挥官,Handler是执行官,MessageQueue是兵营,Message是小兵。指挥官(Looper)负责排兵布阵并对小兵(Message)下达各种命令操作(处理消息),而命令操作是由执行官(Handler)传达到兵营(MessageQueue)并对小兵进行执行(handleMessage)。

//Looper.java
/**
	 ......
 	* <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */
public final class Looper {
    
    private static final String TAG = "Looper";

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;
    ......
}

这里也需要注意一点,Looper是运行在创建Handler所在的线程中的,这样Handler中的UI操作就会被切换到创建Handler所在的线程中去执行。

2. ThreadLocal

在正式介绍Handler机制前,先引入ThreadLocal的概念,这对Handler机制有着重大影响。

细心的人可能已经在Looper中发现了ThreadLocal这一定义了。

//Looper.java

public final class Looper {
    ......
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    ......
}

ThreadLocal是一个仅仅存储线程本身数据的数据存储类。可以类比于代码中变量的作用域,出了这个线程(作用域)就获取不到线程内部的数据(变量)。

2.1 使用场景

  1. 当前数据只想在本线程使用,并且不同线程有着不同的副本

    比如对于Handler来说,Looper就是这样,每个线程需要有自己的Looper来处理本线程的消息,那么Looper的作用域很明显就是本线程,因此ThreadLocal是个不错的选择。

  2. 复杂逻辑业务下的对象传递

    比如监听器的传递,有些任务过于复杂,需要用到函数调用栈相对较深,并同时希望监听器能够贯穿整个线程。一般情况下,有两种解决方式,一种是将监听器作为参数一直传下去,一种是将监听器定义为静态变量。这两种方式都有问题,第一种,将监听器一直作为参数传递下去,听起来就十分有问题,这样的程序设计是非常笨重且不可理喻的。第二种,扩展性相对较差,预先不知道有多少个线程在执行或许就没法确定监听器个数,只能写死灵活性太差。

    而ThreadLocal不会存在这样的问题,每个线程存储着自我的监听器,想要获取监听器可以写入一个get方法获取监听器。

2.2 小小实践

声明一个布尔型ThreadLocal变量。

在这里插入图片描述

如下,在主线程,线程1,线程2中分别打印日志,显示get值。

在这里插入图片描述

得出如下结果:

在这里插入图片描述

2.3 ThreadLocal源码解析

2.3.1 set方法

ThreadLocalset方法,ThreadLocalMapThreadLocal的一个内部类,其实质就是一个映射Map,以ThreadLocal为键,以ThreadLocal中泛型T为值,用一个键值对数组table进行存储,这样的话,有些像哈希表的开发地址法解决哈希冲突了。

ThreadLocalMap的构建在代码中已经标注了,这一块的逻辑比较简单,相当于写了一个开放地址法处理哈希冲突的哈希表,自定义了一个Entry用来接收键值对。

实例化一个ThreadLocal对象,然后使用它的set方法时会先判断当前线程是否有自己的ThreadLocalMap,如果有就给当前线程设置一个键值对<ThreadLocal(自己实例化的对象), value>。

这个过程实质上就是ThreadLocal把自己保存到了相应Thread的ThreadLocalMap中。

//ThreadLocal.java

   static class ThreadLocalMap {

       	.......
         //键值对,弱引用?这个不是很懂
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

		//初始容量,扩容的话必须是2的次幂
        private static final int INITIAL_CAPACITY = 16;

        //键值对数组 长度必须为2的次幂
        private Entry[] table;

        //键值对个数
        private int size = 0;

        //扩容阈值
        private int threshold; // Default to 0

       
       //装载因子是长度的2/3倍数
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

       //构造函数,初始化一个ThreadLocalMap
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];//数组长度
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//计算哈希值,放到相应的位置
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
	......
   }


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

	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

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

上代码中Thread类中有着ThreadLocalMap类型的变量用来保存线程的ThreadLocal数据。

//Thread.java

	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

2.3.2 get方法

//ThreadLocal.java

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

        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }

3. MessageQueue作用

消息队列(MessageQueue)用来存储Message,主要操作就是将消息插入(enqueueMessage())到队列中,将Message从队列中读取next()出来。有趣的是,MessageQueue虽然成为队列但是实质上是一个链表。

//MessageQueue.java
public final class MessageQueue {
 	......
    Message mMessages;
    ......
}


//Message.java
public final class Message implements Parcelable {
    ......
    // sometimes we store linked lists of these things
    /*package*/ Message next;
    ......
}

消息加入队列的操作上文在概述消息分发机制中已经梳理,下面主要是消息读取流程。

   Message next() {
		......
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        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) {
                        //新消息没有准备好进入队列,设置时间唤醒,同样产生阻塞
                        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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();//调度被处理过的消息
                    return null;
                }
                .......
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;//如果没有来的消息就进行阻塞
                    continue;
                }
                ......
            }
		.......
        }
    }

可以看到当没有接收到新消息时,next方法就一直在循环中进行,产生阻塞(mBlock);如果得到新消息,那么就会返回这个消息并且从链表中删除这条消息。

4. Looper作用

Looper的作用主要作用是循环检测消息队列中的消息,即loop是Looper模块其主要作用的函数,从代码中可以看出,只有当没有消息在消息队列的时候才会跳出循环。

//Looper.java
public final class Looper {
    
    private static final String TAG = "Looper";

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

    final MessageQueue mQueue;
    final Thread mThread;
    
    //构造函数
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//消息队列
        mThread = Thread.currentThread();//Looper所在线程
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//获取线程的Looper
    }
    //循环主函数
    public static void loop() {
        final Looper me = myLooper();//拿到本线程Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        ......
            
         //Looper循环检测消息队列
        for (;;) {
            Message msg = queue.next(); //next方法从消息队列提取消息
            if (msg == null) {
                //只有消息为空时候,才退出
                return;
            }
            ......
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);//消息调度处理
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
			......
            msg.recycleUnchecked();//有消息,则打上inUse的标识
        }
    }
    ......
}

如果Looper接收到消息,就会调用msg.target来处理消息,而msg.target是Handler对象,调用Handler的dispatchMessage方法来处理这个消息。

//Message.java
   public final class Message implements Parcelable {
       ......
       /*package*/ Handler target;
       ......
   }

而在Looper发挥作用之前,需要做一些准备,即提前调用prepare()创建一个Looper,然后再调用loop()开启循环。但是很多时候我们在主线程似乎直接定义Handler并定义要发送的消息Message就可以实现消息机制了,这是因为ActivityThread即主线程已经创建了一个Looper,但是与其他方式不同,Looper专门为这个特殊的线程(也就是主线程)提供了prepareMainLooper方法进行主线程Looper的定义,同时留有sMainLoopergetMainLooper用来获取主线程Looper。

//Looper.java  
	public static void prepare() {
        prepare(true);
    }

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

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

当然,Looper也可以退出,Looper类中有quitquitSafely方法,但其实还是调用了消息队列的退出方法,然后消息队列将自己清空,于是Looper在循环中收到的msg = queue.next() => msg = null,也退出循环,结束。

//Looper.java

   public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }


//MessageQueue.java

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

5. Handler作用

经过以上流程,我们可以把过程相对具体化(列出关键函数)如下:

Handler通过post/send方法调用enqueueMessage把消息加到MessageQueue中,然后Looper循环检测获取到新消息再调用Handler的dispatchMessage对消息进行处理。

Handler的post与send方法已经看过了,现在就只剩dispatchMessage方法了。过程如下:

  1. 消息的回调接口不为空则直接调用消息的callback(Runnable),即为Handler的post的参数Runnable

  2. 如若为空,则判断自身接口mCallback是否为空,不为空则调用接口的处理消息机制,如果处理失败,最后还有一个子类重写的handleMessage可以调用。

  3. 这里注意,一般我们定义Handler对象时,都会重写handleMessage方法,这样其实就跳过了第二步骤,因为本身并没有传入或者设置一个CallBack接口。因此,其实也可以采用Handler的另一种构造函数设置CallBack,在CallBack接口的handleMessage中处理消息。

//Message.java
    /*package*/ Runnable callback;

//Handler.java
	//m.callback即为post方法中的Runnable对象	
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    public interface Callback {
        
        public boolean handleMessage(Message msg);
    }
    
    public void handleMessage(Message msg) {
    }
    
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//不为空
            handleCallback(msg);//则调用handleCallback
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

	private static void handleCallback(Message message) {
        message.callback.run();
    }

6. Android消息机制的过程图

在这里插入图片描述

总结

本文主要介绍了Android的消息机制并从源码层面解析Handler、MessageQueue、Looper三者如何协调工作。