在Android开发过程中,我们经常需要在不同线程中传递消息,其中会经常用到Handler,而Android的消息机制,就是Handler和MessageQueue、Looper一起协同工作的过程。
一. 概述
Handler的主要作用是将某个任务切换到Handler所在的线程中执行,一般比较常用的场景是我们在子线程进行网络请求,之所以在子线程进行网络请求是因为在UI线程执行耗时操作会导致ANR,获取数据之后在UI线程进行更新界面,为什么只能在UI线程更新页面,因为Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。
系统为什么不允许在子线程中去访问UI呢? 因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。
Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper就会报错。Handler可以通过post方法发送一个Runnable到消息队列中,也可以通过send方法发送一个消息到消息队列中,其实post方法最终也是通过send方法来完成。
MessageQueue的enqueueMessage方法最终将这个消息放到消息队列中,当Looper发现有新消息到来时,处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用,注意Looper是运行Handler所在的线程中的,这样一来业务逻辑就切换到了Handler所在的线程中去执行了。
二. 消息机制原理
为了更好的理解Looper的工作原理,这里先介绍一下ThreadLocal类。
更详细的可见:https://www.jianshu.com/p/7968fe20d62c
ThreadLocal的工作原理:
ThreadLocal 是线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只有在指定的线程中才可以获取到存储的数据,对于其他线程不能获得数据。对于Handler来说,它需要获取当前线程的Looper,而Looper的作用域就是线程,而且每个线程拥有不同的Looper,通过ThreadLocal可以实现线程中的存取。通过ThreadLocal可以让监听器作为线程内的全局对象存在,在线程内部只要通过get方法获得监听器,ThreadLocal原理:不同线程访问同一个get方法,get方法会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找对应的value值。
(1)ThreadLocal的set方法:
public void set(T value) {
Thread currentThread = Thread.currentThread();
//通过values方法获取当前线程中的ThreadLoacl数据——localValues
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
从上面代码可以看出,在set时,首先要获得当前线程,然后根据当前线程找到对应的values对象,如果没有values对象则创建一个,然后把要保存的数据存储进去。在Thread类内部有一个protected的属性ThreadLocal.Values的成员用于存储线程的ThreadLocal数据,所以values(currentThread)方法就是直接返回currentThread的这个属性。如果这个属性值为空,则可以new一个Values对象并赋值给currentThread的成员。而Values的put方法就是用来保存数据的,Values类是ThreadLocal的静态内部类,而Values里有一个数组:private Object[] table,ThreadLocal的值就存在这个数组中。
/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
从这个put方法的源码可以看出,ThreadLocal的值在table数组中的存储位置总是ThreadLocal的reference字段所标识的对象的下一个位置。
(2)ThreadLocal的get方法:
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread); //找到localValues对象
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) { //找到ThreadLocal的reference对象在table数组中的位置
return (T) table[index + 1]; //reference字段所标识的对象的下一个位置就是ThreadL的值
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
从上面的源码可以看出来,也是通过currentThread来获取到Values对象,然后判断reference属性是否等于Object数组index位置的对象,等于的时候则获取index下一个位置的对象,这个对象就是之前保存的数据。如果没有Values则使用默认的值,默认情况下为null。
从ThreadLocal的set/get方法来看,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set/get方法,它们对ThreadLocal的读/写行为仅限于各自线程的内部。
MessageQueue工作原理:
MessageQueue是消息队列,主要包含两个操作,插入和读取,读取操作本身也包含着删除操作,MessageQueue内部通过一个单链表数据结构来存储消息,插入读取操作对应的方法是enqueueMessage和next,
enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
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 (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;
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
首先先判断Message是否绑定了一个Handler对象,即msg.target对象,再判断message是否正在使用,当都满足条件的时候,将锁定message对象,然后判断消息是否退出,如果是就将消息回收并返回false,否则将消息标记为正在使用,并设置一些信息。Message p = mMessage,当第一次执行到这时,p是空,所以把当前message作为消息队列中链表的头结点,注:when == 0 || when < p.when说明上一个消息已经执行完毕或者是当前消息比上一个消息先执行,所以相当于是第一次执行到这里。如果已经有头结点,那么就遍历到链表尾部,将当前消息插入到尾部。
next方法
Message 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 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) {
// 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.
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 handler
boolean 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;
}
}
从最外面的for循环我们可以看出来这是一个无限循环的过程,当消息队列里有消息时,则会取出这个message,即mMessages。当满足条件时,则取出这个mMessages(通过Message msg = mMessages赋值后返回msg),然后把mMessages赋值为msg.next,即把下一个消息赋值成mMessages。通过这个过程就把当前消息处理完了,并且把处理过的消息删除掉了。
当消息队列里没有消息时,会查看是否存在IdleHandler,如果存在便执行,如果不存在便阻塞等待消息的到来。
Looper工作原理:
Looper在消息机制中负责不断地从消息队列中查看是否有新消息,如果有则立即处理,如果没有则阻塞。
通过Looper.prepare()方法即可为当前线程创建一个Looper,再通过Looper.loop()开启消息循环。prepareMainLooper()方法主要给主线程也就是ActivityThread创建Looper使用的,本质也是通过prepare方法实现的。
prepare方法
public 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));
}
loop方法
public static void loop() {
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
首先是获取到当前线程的Looper对象和绑定到Looper对象的这个消息队列,接着就是一个死循环了。loop方法是调用MessageQueue的next方法获得消息的,从上面的next方法源码可以看出,next是一个阻塞的操作,当没有新消息时会一直阻塞在那里,这导致loop也会阻塞,如果next返回了新消息,Looper就会处理这个消息,把这个消息通过Handler(msg.target是一个Handler对象)的dispatchMessage方法来处理这个消息,这也就到了Handler里面去执行了,这样就成功地将代码逻辑切换到指定的线程中去了。
如果当这个loop执行到一半的时候要退出怎么办呢?Looper提供quit和quitSafely来退出一个Looper,并标识为退出状态,当消息队列被标记为退出状态时,next方法就会返回null,这样loop里面的死循环就跳出去了。
上述通过prepare方法和loop方法只是对普通线程来说的,对于主线程来说,由于主线程情况比较复杂,所以提供了prepareMainLooper来给ActivityThread创建Looper对象,但是其本质也是通过prepare来实现的,这个可以去看源码。同时,也可以通过getMainLooper方法在其他任何地方获取到主线程的Looper对象。
注: 1.当Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false; 2. 在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。
Handler的工作原理:
Handler主要包括消息的发送和接收,也就是相当于Handler给自己发送了一条消息,只是消息经过了消息队列以及Looper,最后才到Handler的handleMessage方法里。Handler的消息的发送主要由post一系列方法以及send的一系列方法来实现,post的一系列方法最终都是通过send的一系列方法来实现的。而send一系列方法最后都是通过sendMessageAtTime方法来实现的。
sendMessageAtTime 方法
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
从上面代码可以看出,发送消息其实就是往消息队列里面插入一个消息。当Handler把消息插入到消息队列里后,MessageQueue就通过next方法把这个消息返回给Looper,而Looper则通过loop方法调用Handler的dispatchMessage方法(msg.target.dispatchMessage(msg);)。下面是dispatchMessage的代码
dispatchMessage 方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//Message的callback是一个Runnable,
//也就是Handler的 post方法所传递的Runnable参数
handleCallback(msg);
} else {
//如果给Handler设置了Callback的实现,
//则调用Callback的handleMessage(msg)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用Handler的handleMessage方法来处理消息,
//该Handler子类需重写handlerMessage(msg)方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
//直接执行Runnable中的run()方法
message.callback.run();
}
public interface Callback {
//不想继承Handler子类,可以通过Callback来实现handleMessage
public boolean handleMessage(Message msg);
}
//默认空实现
public void handleMessage(Message msg) {
}
Thread、Looper、Handler、MessageQueue之间的数量对应关系:
Thread :Handler = 1 :n
Thread :Looper = 1 :1
Looper :MessageQueue = 1 :1
主线程消息循环:
Android的主线程就是ActivityThread,主线程的入口方法为main(String[] args),在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
main方法
public static void main(String[] args) {
...
Process.setArgV0("");
Looper.prepareMainLooper();//创建主线程的Looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();//开启looper
throw new RuntimeException("Main thread loop unexpectedly exited");
}
从源码中可以看到,通过Looper.prepareMainLooper来给主线程创建了一个Looper对象以及MessageQueue对象,然后通过Looper.loop();来开启消息循环。其中,主线程的Handler是mH这个变量,mH是ActivityThread.H的一个实例,它内部定义了一组消息,主要包含了四大组建的启动和停止。
Android主线程的消息循环模型:
ActivityThread 通过ApplicationThread 和AMS 进行进程间通信,AMS 以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread 会向H 发送消息,H 收到消息后会将ApplicationThread 中的逻辑切换到ActivityTread 中去执行,即切换到主线程中去执行。四大组件的启动过程基本上都是这个流程。
思考:
Looper.loop(),这里是一个死循环,如果主线程的Looper终止,则应用程序会抛出异常。所以,既然主线程 “卡” 在这里了,有两个问题
(1)那Activity为什么还能启动;(2)点击一个按钮仍然可以响应?
问题1:startActivity的时候,会向AMS(ActivityManagerService)发一个跨进程请求(AMS运行在系统进程中),之后AMS启动对应的Activity;AMS也需要调用App中Activity的生命周期方法(不同进程不可直接调用),AMS会发送跨进程请求,然后由App的ActivityThread中的ApplicationThread会来处理,ApplicationThread会通过主线程线程的Handler将执行逻辑切换到主线程。所以,主线程的Handler把消息添加到了MessageQueue,Looper.loop会拿到该消息,并在主线程中执行。这就解释了为什么主线程的Looper是个死循环,而Activity还能启动,因为四大组件的生命周期都是以消息的形式通过UI线程的Handler发送,由UI线程的Looper执行分发的。
问题2:和问题1原理一样,点击一个按钮最终都是由系统发消息来进行的,都经过了Looper.loop()处理。