Android提供了Handler 和 Looper 来满足线程间的通信。Handler先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。Handler消息机制可以说是Android系统中最重要部分之一,所以,本篇博客我们就来深入解析Android中Handler消息机制。
Handler的简单使用
为什么系统不允许子线程更新UI
因为的UI控件不是线程安全的。 如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那为什么不对UI控件的访问加上上锁机制呢?因为有这么两个缺点: 1.上锁会让UI控件变得复杂和低效 2.上锁后会阻塞某些进程的执行 对于手机系统来说,这两个缺点是不可接受的,所以最简单高效的方法就是 —— 采用单线程模型来处理UI操作。 对开发者而言也不是很麻烦,只是通过Handler切换一下访问的线程的就好。
Handler的简单使用
既然子线程不能更改界面,那么我们现在就借助Handler让我们更改一下界面: 主要步骤是这样子的: 1.new出来一个Handler对象,复写handleMessage方法 2.在需要执行更新UI的地方 sendEmptyMessage 或者 sendMessage 3.在handleMessage里面的switch里面case不同的常量执行相关操作
代码语言:javascript复制public class MainActivity extends ActionBarActivity {
private TextView mTextView;
private Handler mHandler;
private static final int UI_UPDATE1 = 0;
private static final int UI_UPDATE2 = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UI_UPDATE1:
mTextView.setText("通过Handler方法1修改UI");
break;
case UI_UPDATE2:
mTextView.setText("通过Handler方法2修改UI");
break;
}
}
};
mTextView = (TextView) findViewById(R.id.textview);
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("Test", "点击文字");
updateUi();
}
});
}
protected void updateUi() {
new Thread(new Runnable() {
@Override
public void run() {
// 方式一和方式二可以达到相同的效果,就是更改界面
//方式一
//mHandler.sendEmptyMessage(UI_UPDATE1);
//方式二
Message msg = Message.obtain();
msg.what = UI_UPDATE2;
mHandler.sendMessage(msg);
}
}).start();
}
}
代码语言:javascript复制<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.handlerdemo3.MainActivity" >
<TextView
android:id="@ id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:layout_centerInParent="true"
android:clickable="true" />
</RelativeLayout>
消息机制的分析理解
安卓的异步消息处理机制就是handler机制。 主线程,ActivityThread被创建的时候就会创建Looper Looper被创建的时候创建MessageQueue。 也就是说主线程会直接或间接创建出来Looper和MessageQueue。 Handler的工作机制简单来说是这样的 1.Handler发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue 2.Looper不断轮询调用MeaasgaQueue的next方法 3.如果发现message就调用handler的dispatchMessage,dispatchMessage被成功调用,接着调用handlerMessage()
Handler消息机制的源码分析
ThreadLocal工作原理 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。
如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
我们看一下ThreadLocal的set方法
代码语言:javascript复制public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
查看values方法里面做了什么
代码语言:javascript复制Values values(Thread current) {
return current.localValues;
}
通过上面代码我们可以知道,当values为空的时候,才调用initializeValues方法进行初始化,查看inheritValues相关逻辑:
代码语言:javascript复制private void inheritValues(Values fromParent) {
// Transfer values from parent to child thread.
Object[] table = this.table;
for (int i = table.length - 2; i >= 0; i -= 2) {
Object k = table[i];
if (k == null || k == TOMBSTONE) {
// Skip this entry.
continue;
}
// The table can only contain null, tombstones and references.
Reference<InheritableThreadLocal<?>> reference
= (Reference<InheritableThreadLocal<?>>) k;
// Raw type enables us to pass in an Object below.
InheritableThreadLocal key = reference.get();
if (key != null) {
// Replace value with filtered value.
// We should just let exceptions bubble out and tank
// the thread creation
table[i 1] = key.childValue(fromParent.table[i 1]);
} else {
// The key was reclaimed.
table[i] = TOMBSTONE;
table[i 1] = null;
fromParent.table[i] = TOMBSTONE;
fromParent.table[i 1] = null;
tombstones ;
fromParent.tombstones ;
size--;
fromParent.size--;
}
}
}
其实就是各种赋值table数组,进行初始化 最后才是调用values.put(this, value)把ThreadLocal和value一起保存,我们可以看一下values.put(this, value)方法
代码语言:javascript复制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;
}
}
}
可以看出,Threadlocal的值在table数组的存储位置总是reference的下一个位置.
接下来,查看Threadlocal的get方法
Get方法的逻辑是:通过values方法取出当前线程的localValues对象,如果为null,就返回初始值。如果localValues不为null,取出其table数组,如果reference等于table数组index角标的值,就在table[index 1]取出其Threadlocal值。
MessageQueue工作原理
MessageQueue中文翻译就是消息队列,它内部存储了一组信息,存放的是Message,以队列的形式对外提供了插入和删除的工作(虽然名字叫做队列,但是其内部的 存储结构是单链表) 主要 插入 和 读取 两个操作,这两个操作对应着两个方法:
代码语言:javascript复制插入(入队) enqueueMessage(Message msg, long when)
读取(出队) next()
查看enqueueMessage的源码:
代码语言:javascript复制boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
}
boolean needWake;
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(
msg.target " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
}
msg.when = when;
Message p = mMessages;
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;
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
从enqueueMessage的实现来看,主要操作就是单链表的插入操作,接下来查看next方法的实现:
代码语言:javascript复制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;
}
}
从上面代码可以得出以下结论: 1.next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞 2.当有新消息到来,next方法会返回这条消息,并将其从单链表中移除。
Looper的工作原理
Looper是一个轮询器,它的作用不断轮询MessageQueue,当如果有新的消息就交给Handler处理,如果轮询不到新的消息,那就自身就处于阻塞状态。
查看Looper类的源码,可以发现的他的构造方法里面创建了一个MessageQueue,然后将当前线程的对象保存起来
代码语言:javascript复制private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper的构造方法中初始化了一个消息队列MessageQueue和一个线程Thread。从这可看出:一个Looper对应着一个消息队列以及当前线程。 当收到消息Message后系统会将其存入消息队列中等候处理。至于Looper,它在Android的消息机制中担负着消息轮询的职责,它会不间断地查看MessageQueue中是否有新的未处理的消息;若有则立刻处理,若无则进入阻塞。
相信大家一定有遇到过,在子线程中创建Handler会报如下错误
解决办法就是new Handler的时候加上Looper.prepare(); 而Looper.prepare()的内部实现逻辑就是创建一个Looper
代码语言:javascript复制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));
}
线程默认是没有Looper的,但是为什么在主线程没有创建的Looper就可以使用Handler?主线程是特别的。主线程,也就是ActivityThread,当主线程被创建的时候,会调用Looper内的prepareMainLooper方法,创建Looper,该方法是专门给主线程创建Looper用的。也正因为这点,所以我们在主线程创建了Handler就直接能用了。
代码语言:javascript复制 public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
由于这个特殊性,Looper还提供了一个getMainLooper方法,使得可以在任何地方获取主线程的Looper。
代码语言:javascript复制public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
接下来,我们看一下Looper的退出
Looper提供了两个方法进行退出操作,分别是quit和quitSafely,他们调用的是MessageQueue的quit方法
代码语言:javascript复制public void quit() {
mQueue.quit(false);
}
代码语言:javascript复制public void quitSafely() {
mQueue.quit(true);
}
MessageQueue的quit方法:
代码语言:javascript复制 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);
}
}
如果调用Looper.quit方法,最终会调用removeAllMessagesLocked方法,该方法逻辑:直接遍历所有的消息,并将消息强制回收
代码语言:javascript复制 private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
如果调用Looper.quitSafely方法,最终会调removeAllFutureMessagesLocked方法,该方法逻辑:
代码语言:javascript复制void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
一个无限for循环,只有n.when > now和n == null才会跳出循环,说明是等消息队列中已有的消息处理完毕后,才会跳出,然后执行回收。
Looper这个类里面最重要的方法就是loop()开启消息循环这个方法了,看一下loop代码的实现逻辑:
代码语言:javascript复制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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " msg.target " "
msg.callback ": " msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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方法是一个死循环,唯一跳出的循环的方式是MessageQueue的next方法返回null,但这几乎不可能,因为在MessageQueue的next方法中,假如没有消息加入队列,next方法会一直阻塞,不会返回null。如果我们不手动调用quit或者quitSafely方法的话,MessageQueue的next方法是不可能返回null的。 当MessageQueue没有消息时,next方法会一直阻塞在那里,因为MessageQueue的next方法阻塞了,就导致Looper的loop方法也一直在阻塞了。 这里我们那一分为二的谈, loop轮询不到消息:那么处于阻塞状态,然后就没有然后了,除了又轮询到了新的消息 loop轮到了新的消息:Looper就会处理消息 1、msg.target.dispatchMessage(msg),这里的 msg.target就是指Handler对象 2、到了最后,Handler发送的消息又交给了自己的dispatchMessage方法来处理了。(这个dispatchMessage方法不是Handler自己调用时,是与Handler相相关的Looper间接调用的),这样下来,就成功地将逻辑切换到指定的线程当中去了
Handler的工作原理 Handler的主要工作:消息的 发送 和 接收 。Handler消息发送的形式有post和send两种。 查看Handler中sendMessage代码
代码语言:javascript复制public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
sendMessage调用sendMessageDelayed
代码语言:javascript复制public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() delayMillis);
}
sendMessageDelayed调用sendMessageAtTime
代码语言:javascript复制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);
}
sendMessageAtTime调用enqueueMessage
代码语言:javascript复制 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
代码语言:javascript复制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;
}
得出结论:Handler的发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue,MessageQueue就会返回这条消息给Looper,Looper会交给msg.target.dispatchMessage(msg)方法处理,就进入消息处理阶段。
查看dispatchMessage方法
代码语言:javascript复制/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,判断 Message. Callback是否为null,不为null,就调用handleCallback方法
代码语言:javascript复制private static void handleCallback(Message message) {
message.callback.run();
}
Callback是一个Runnable对象,实际上就是post方法传递的Runnable参数。 其次,检查mCallback是否为null,如果不为null就调用mCallback.handleMessage方法,查看mCallback.handleMessage方法
代码语言:javascript复制 public interface Callback {
public boolean handleMessage(Message msg);
}
源码中注释已经对Callback进行了解释: 可以用来创建一个Handler的实例但不需要派生Handler的子类 在日常开发中,创建Handler最常见的方式就是派生一个Handler的子类并重写handleMessage方法来处理具体的消息,而Callback给我们提供了另外一种方式,不需要派生Handler的子类。
最后,调用Handler的handleMessage方法,这就是我们平时写Handler要实现的方法
主线程的消息循环 Android的主线程就是ActivityThread,主线程的路口方法是main方法,在 main中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue, 并通过Looper.loop()开启主线程消息循环
代码语言:javascript复制public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程开始循环后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H
ApplicationThread通过binder与Ams通信,并将Ams的调用,通过H类(也就是Hnalder)将消息发送到消息队列,然后进行相应的操作,H收到消息后,就会将ApplicationThread中逻辑切换到ActivityThread中执行,也就是主线程中执行,这个过程就是主线程的消息循环。
至此,Handler消息机制就分析完毕,如有错漏,欢迎留言指证。