Android的消息机制概述
- Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。
- MessageQueue是消息队列,内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但它的内部并不是真正地队列,而是采用单链表的数据结构来存储消息列表。
- Looper是消息循环,MessageQueue只是一个消息的存储单元,而Looper会以无线循环的形式去处理消息。
- Looper还有个特殊的概念,是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。Handler创建的时候会采用当前线程的Looper来构造消息循环系统,而ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
- 线程是默认没有Looper的,如果需要用到Handler就必须为线程创建Looper,而主线程ActivityThread被创建时就会初始化Looper,这也就是主线程中默认可以使用Handler的原因。
ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储之后,只有在指定线程中才能获取到数据,对于其他线程来说无法获取数据。
使用例子
|
|
|
|
运行结果如下图所示:
在不同线程中访问同一个ThreadLocal对象,但获取到的值不一样。而在同一线程中的两个TheadLocal
ThreadLocal的实现
从ThreadLocal的get方法为切入点看源码:
getMap方法:
t表示当前的线程,从Thread的源码中可以看到,的确是有一个ThreadLocalMap实例:
每一个Thread都有一个ThreadLocalMap属性,这个属性是类似于HashMap的,ThreadLocalMap是ThreadLocal的一个静态内部类,它以ThreadLocal为键,以属于该线程的资源副本为值。我们可以这样看待ThreadLocal:ThreadLocal是为一组线程维护资源副本的对象,通过它,可以为每一个线程创建资源副本,也可以正确获得属于某一线程的资源副本。
Entry是ThreadLocalMap的静态内部类,代表着ThreadLocalMap的一个键值对,其定义如下所示:
它继承与弱引用,在ThreadLocalMap类中保持着这样的键值对数组:
在ThreadLocal的get()方法中调用e.value方法就可以获取实际的资源副本值。但是如果有一个为空,说明属于该线程的资源副本还不存在,则需要去创建资源副本,从代码中可以看到是调用setInitialValue()方法,其定义如下:
|
|
先调用initialValue方法初始化一个空值,接下来是判断线程的ThreadLocalMap是否为空,不为空就直接设置值(键为this,值为value),为空则创建一个Map,调用方法为createMap(),其定义如下:
而ThreadLocalMap的这个构造方法的实现如下:
实例化table数组用于存储键值对,然后通过映射将键值对存储进入相应的位置。下面是set方法:
ThreadLocalMap中的每一个键值对Entry的key是当前的ThreadLocal,传入的是this,使key彼此不同的是ThreadLocal的常量:
|
|
AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减,因此可保证多线程下threadLocalHashCode是唯一的,即key值也是唯一的,因此才会出现在上面的例子中同一线程中两个同一泛型的ThreadLocal对象获取到的值也是不一样的。
ThreadLocal的作用
ThreadLocal对Thread的引用全部通过局部变量完成,而没有一个全局变量。而实际的资源副本则存储在Thread的自身的属性ThreadLocalMap中,这说明,其实ThreadLocal只是关联一个Thread和其资源副本的桥梁,并且实际上Thread和资源副本的生命周期是紧密相连的,在线程被回收的时候,其资源副本也会被回收。
MessageQueue的工作原理
MessageQueue主要包含两个操作:插入和读取。读取本身会伴随着删除操作,插入和读取的方法对应着enqueueMessage和next。尽管MessageQueue叫消息队列,但是其内部是通过一个单连标点恶数据结构来维护消息列表的单链表在插入和删除上比较有优势。
enqueueMessage和next方法的:
- enqueueMessage方法其实就是单链表的插入操作,代码比较多,这里就不贴出了。
- 而next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中删除。源码如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106Message next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint 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) {// Next message is not ready. Set a timeout to wake up when it is ready.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.//很关键,根据Looper传进来的标志mQuitting返回null,使Looper的loop方法停止消息循环处理if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run. Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}}
Looper的工作原理
Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中,我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:
通过上面两行核心代码,你的线程就升级为Looper线程了
Looper.prepare()
通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,下面来看一下源码:
通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。ThreadLocal前面已经讲得很详细了。Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。
Looper.loop()
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:
除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如
Looper.myLooper()得到当前线程looper对象:
getThread()得到looper对象所属线程:
quit()和quitSafely()方法结束looper循环:
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null,。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。loop方法会调用MessageQueue的next方法来获取新消息,否则loop方法就会无限循环下去,当没有消息时,next方法会一直阻塞在那,这也就导致了loop方法一直阻塞在那里。
如果MessageQueue的next方法返回新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target就是发送这条消息的Handler对象,这样Handler发送的消息最终又交到了它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。
到此为止,你应该对Looper有了基本的了解,总结几点:
每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal
Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
Looper使一个线程变成Looper线程。
Handler工作原理
什么是handler?handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。
Handler初始化
handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:
下面我们就可以为之前的LooperThread类加入Handler:
加入handler后的效果如下图:
可以看到,一个线程可以有多个Handler,但是只能有一个Looper!
Handler发送消息
有了handler之后,我们就可以使用 post(Runnable), postAtTime(Runnable,long),postDelayed(Runnable,long),sendEmptyMessage(int), sendMessage(Message),sendMessageAtTime(Message, long)和 sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:
其他方法就不罗列了,总之通过handler发出的message有如下特点:
message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码
1msg.target.dispatchMessage(msg);post发出的message,其callback为Runnable对象
Handler处理消息
消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的,见源码
可以看到,除了handleMessage(Message msg)和Runnable对象的run方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!
Handler消息循环机制总结
Handler的特点
handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。
handler是在它关联的looper线程中处理消息的。
这就解决了android最经典的不能在其他非主线程中更新UI的问题。android的主线程也是一个looper线程(looper在android中运用很广),我们在其中创建的handler默认将关联主线程MQ。因此,利用handler的一个solution就是在activity中创建handler并将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知activity更新UI。(过程如图)
主线程的消息循环
Android的主线程是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper来创建主线程的Looper以及MessageQueue,并通过Looper.loop方法来开启主线程的消息循环,这个过程如下所示:
主线程的消息循环开始了以后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。如下所示:
ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ACtivityThread的请求后会回调ApplicationThread中Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行。这个过程就是主线程的消息循环模型。