1.引子
在上一篇Android消息机制基本原理,我们学习了Android消息机制基本原理和使用方法,看到原理比较复杂,什么Handler啊,Looper啊,MessageQueue啊,但使用消息机制却是十多行代码搞定,感觉跟漫威电影一样,前期渲染反叛如何如何逆天,结果被主角轻松用嘴炮打败。
当然Android开发和电影不同,电影中我们希望主角打败反派过程复杂一点,这样才有戏剧冲突性,但在Android开发中,我们是希望越简单越好,Android消息机制通过精妙的设计,使得使用变得简单了,我们可以通过源码分析一下,看看岁月简单的背后,谁在替我们负重前行。
2.ThreadLocal
有必要介绍一下ThreadLocal,这个概念在日常开发中很少涉及,但在Android消息机制的源码里出现了,如果不了解其功能,会对理解源码形成一定的障碍。
首先要强调的是,ThreadLocal不是一个线程,而是和线程有关的泛型类,该来类提供线程本地变量(thread-local variables)。线程本地变量与普通变量不同,顾名思义,这个变量在每个线程里都有一个独立初始化的变量副本,也就是说,这个变量可以实现线程之间相互隔离,互不干扰。
我们可以通过一个示例来看看效果。
我们声明一个String类型的ThreadLocal变量:
//声明类型String的变量
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
然后创建两个子线程,分别在其中通过set方法设置ThreadLocal,完了之后我们在主线程和两个子线程里分别打印ThreadLocal变量值,完整代码如下:
package com.test.threaddemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tshow;
private Button button;
private String TAG="ThreadLocal";
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tshow=findViewById(R.id.tvshow);
button=findViewById(R.id.bClick);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Thread2();
Thread1();
//主线程ThreadLocal
threadLocal.set("UI线程的数据");
Log.d(TAG,"UI线程:"+threadLocal.get());
}
});
}
@Override
protected void onResume() {
super.onResume();
}
/**
* 线程1
*/
private void Thread1() {
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("线程1的数据");
Log.d(TAG,"线程1:"+threadLocal.get());
}
}).start();
}
/**
* 线程2
*/
private void Thread2() {
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("线程2的数据");
Log.d(TAG,"线程2:"+threadLocal.get());
}
}).start();
}
}
运行之后,我们看输出结果,可以看到,虽然是同一个ThreadLocal变量,但不同线程打印出来的ThreadLocal值是不同的,主线程和两个子线程输出在本线程里设置的值。我想有人会说,这是每个线程执行有先后顺序导致的,因为设置后立刻打印,所以并不能确定这是ThreadLocal的功劳,我用一个普通的String变量也可以达到一样的打印效果。
要验证这个问题比较简单,在主线程里不设置ThreadLocal变量的值,如果ThreadLocal是线程隔离的,那么主线程里的应该为空,否则应该是子线程设置的值,代码调整见下,这里加了一个延时,是为了等子线程都执行完了自己的代码,然后再打印主线程里的ThreadLocal变量。
Thread2();
Thread1();
//加上延时,确保等子线程代码执行结束
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(threadLocal.get()==null)
{
Log.d(TAG,"threadLocal值为空");
}else{
Log.d(TAG,threadLocal.get());
代码运行结果如下,我们看到,主线程里的threadlocal的确还是空的,证明了ThreadLocal是能够实现线程隔离的:
那么ThreadLocal是如何实现这样的功能的呢?
先来看ThreadLocal的set方法的实现
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
粗略理解这6行代码,有以下步骤
- 获取当前线程;
- 获取当前线程的成员ThreadLocalMap变量map;
- 如果map非空,将ThreadLocal和value存入到map中;
- 如果map为空,创建map,并将ThreadLocal和value副本放入map中。
看这些流程,感觉是似懂非懂,懂的部分是感觉逻辑上是这么回事,不懂的地方是,getMap做了什么操作,map是什么?createMap又做了什么操作?当你按Ctrl+B点开这些函数定义时,估计得头大一会。看源码碰到这种情况是常见的事,往往看起来只有几行,但嵌套的东西特别多,不了解嵌套的东西,你对源码肯定理解不完整,但要把嵌套的都了解,内容又不少。言归正传,刚说到ThreadLocalMap,简单理解,ThreadLocalMap是一个Hash Map,是存储当前线程数据的Map集合,既然是Map,必定是以键值对存储数据,那么键是什么,值是什么?我们看到map.set方法第一个参数是this,虽然知道this是用来指向当前对象或类实例,但不太确定这个this指的是什么,那么可以看源码的注释部分,会发现官方已经说的比较清楚
Sets the current thread’s copy of this thread-local variable to the specified value
设置当前线程的线程本地变量副本为特定值
根据源码和注释,我们可以确认ThreadLocalMap存储的是ThreadLocal变量副本和对应的值,例如在前边的例子中,我们new了一个ThreadLocal变量,那么ThreadLocalMap存储的便是threadLocal副本,一个线程存一个副本,各个副本之间是相互隔离,这解释了同一个变量在不同线程可以设置不同的值的原因。对这个问题,刚开始我理解成ThreadLocalMap是一个跨线程的map,也就是多个线程共用一个map,以线程的ID作为Key,后来发现这么理解不对,而是每个线程有一个ThreadLocalMap,TheadLocal变量以副本形式存储在每个线程的ThreadLocalMap中。
getMap()函数即获取当前线程的ThreadLocalMap,我看下getMap的定义,代码如下:
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到,代码很简单,就一个返回,我开始看这段代码,纠结t.threadLocals
是什么,因为看到函数的返回值类型是ThreadLocalMap
,但threadLocals
从名字看又像是ThreadLocal啊,怎么和ThreadLocalMap关联起来,后来查其他资料才确定,t.threadLocals
就是ThreadLocalMap
,证据在Thread
的源码,里面有一段代码,如下所示,疑惑的是为何变量起名不带个map,这样意思更明白一点。Thread
源码同时还显示了每个线程内部有一个ThreadLocalMap,Thread只是提供这个成员变量,由ThreadLocal对其维护。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
希望这么多名称没把你绕晕。我们可以用一个图总结Thread,ThreadLocal,ThreadLocalMap的关系,简单来说是Thread里有ThreadLocalMap,ThreadLocalMap由ThreadLocal维护,维护是指创建,管理ThreadLocal变量副本的值。
关于ThreadLocal简单介绍就到此,我们主要是想知道,ThreadLocal如何做到线程之间相互隔离的,而这个目标已经达到,其实ThreadLocal还有很多内容值得去挖掘,例如怎么设置值,如何删除键值等,有兴趣的读者可以深入了解一下,据说面试官喜欢考察这些内容。
我们再回忆一下Android消息机制,UI线程里有个消息队列MessageQueue,Looper开启无限循环,不断查看消息队列中是否有消息进来,如果有则取出,主线程创建Handler实例给子线程引用,子线程通过Handler发送消息给主线程,其实是把Message压入消息队列中,这样实现了子线程和主线程消息的传递。
3.Mesage和MessageQueue源码
回忆上一篇博客里提到的传票叉例子,在这个例子中,我们能够有条不紊处理其他家庭成员发来的消息,正是依靠传票叉,同样道理,主线程有条不紊处理子线程发过来的消息,也是依靠消息队列,正因如此,个人认为消息队列是Android消息机制的核心,因此我们从Message,MessageQueue开始。
我们先来看Message对象的成员变量:
成员变量 | 含义 | 备注 |
---|---|---|
what | 消息类别 | 用户定义的消息代码,以便收件人可以识别此消息的含义 |
arg1 | 参数1 | 用于传递轻量级数据例如一些整数值 |
arg2 | 参数2 | 用于传递轻量级数据例如一些整数值 |
obj | 消息内容 | 任意对象,在使用Messenger跨进程传递Message时不能为null |
data | Bundle数据 | 比较复杂的数据建议使用该变量 |
target | 消息响应方 | 关联的Handler对象,处理Message时会调用它分发处理Message对象 |
when | 触发响应时间 | 处理消息时间 |
next | 下一个Message对象 | 用next指向下一条Message |
我们注意到Message包含一个next
的成员变量,该变量指向了下一条Message,next
作用是把一系列Message给串联起来,实际上,Message是单链表的节点,每个节点包含该节点的数据和指向下一个节点的数据,单链表样子见图。
Message只是构成单链表结构,它不能自己管理自己节点的添加和读取,这个工作由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;
}
该函数包含两个入参,第一个是消息,第二个是消息的时间。
源码中,首先做两个判断,第一个判断msg.target == null
作用是判断负责分发消息的Handler是否为空,第二个判断msg.isInUse()
是确认消息是否已使用,已使用表示消息已经被创建并加入队列,但还没读取;接下来进入synchronized
代码块,synchronized表示对这段代码加锁,一次只允许一个线程操作,等当前线程处理完毕后下一个线程才能使用,加锁保证子线程排队处理消息入列,不能随便插队。synchronized
代码块里的第一句还是做个判断mQuitting
,这个判断作用我们可以从打印错误的消息得知,这是判断是不是发送消息到一个死线程上了。
三个判断都通过之后,可以开始执行入队操作了。
在开始之前,还是需要介绍一些概念,首先是mMessage
,表示当前待处理的消息,MessageQueue是根据消息的时间顺序处理消息,如果已经存在消息队列,那么mMessage
的时间一定消息队列里最早的,when
是消息的时间,时间排序即根据when
来的。
现在要插入消息,无非是往队列的头部,中部还是尾部插入消息,第一个判断if (p == null || when == 0 || when < p.when)
成立表明是要将消息插入队列头部,p==null
表示代表MessageQueue没有消息待处理,准备插入消息是第一个消息,when == 0
表示待插入的消息需要插入到头部,因为时间是非负值,现在时间设置为0,明摆着是要暴力插队到消息头部,when < p.when
表示待插入的消息时间早于mMessage的时间,而mMessage又是当前消息队列里时间最早的,自然也是要插队到队首,我们可以看图来理解如何将消息插到队列的头部,一个将待插入消息msg的next指向mMessage,再一个是将mMessage赋值为待插入的消息即可。
插入其他位置则是通过循环,从队列头开始遍历,逐一比较待插入消息的时间和其他节点消息的时间,如果早于某个节点时间或者到了队列的尾部,退出循环并执行插入队列操作。
我们再来看看消息出队next()
方法,源代码比较长,不一一分析了,消息出队的基本逻辑就是如果当前MessageQueue中存在mMessages,就将这个消息出队,然后让下一条消息成为mMessages,否则进入阻塞状态,一直等到有新的消息入队。另外,消息出队同时也会删除该消息。
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;
}
}
4.Handler源码
消息入队enqueueMessage由谁调用呢?我们还记得在最上层应用mHandler.sendMessage(msg);
来发送消息,我们可以打开这个函数的定义,经过穿越层层封装,最后定位到sendMessageAtTime函数,在里面我们找到了消息入队函数的身影,由于我们已经了解enqueueMessage
,再看它时倍感熟悉,sendMessageAtTime
有两个入参,第一个是消息,不用过多解释了,第二个是我们知道是时间,但这是什么时间呢,官方注释有答案,这是系统启动到现在这个过程中的处于非休眠期的时间,单位是毫秒。
public boolean sendMessageAtTime(@NonNull 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);
}
消息出队又有谁调用?还是一样的思路,我们从上层应用入手,我们在实例化Handler时重写了handleMessage
方法,通过查看定义(快捷方式Ctrl+B),定位到Handler源代码里的里的handleMessage
,现在再来看谁调用了这个接口,我们在dispatchMessage
方法里找到handleMessage
的身影,dispatchMessage
源码如下。
源码里有两个判断,第一个是判断消息消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback,如果有则使用该方法处理消息,第二个是是判断handler的mCallback是否为空,不为空则消息交由callback创建handler方式去处理,如果以上判断都不通过,则使用我们在上层创建handler对象时重写handlerMessage中处理。
接下来应该是去查哪里调用了dispatchMessage
,因为无法通过查看引用的方式找到所有调用dispatchMessage
,查到的引用不是我们想要的答案,这里直接说答案吧,需要到looper源代码中查找答案。
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
5.Looper源码
在上一篇博客,我们讲到Looper作用是创建无限循环,不断查看是否有消息入队,有消息进来则取出。Looper里开启无限循环是在loop()方法里,对于该方法,官方注释解释道,此线程中运行消息队列,源代码比较长(故附在文末),我们只挑重点的来说。
源代码的第一个重点和消息分发无关,但日常开发会碰到,我们注意到,代码上来判断Looper是否为空,如果为空则抛出No Looper; Looper.prepare() wasn't called on this thread
异常,我们去看看Looper.prepare()
是干啥子的,注释说的很清楚,该方法是将当前普通线程设置成Looper
,这也才能创建一个指向该Looper
的Handler实例,在普通线程里没有调用Looper.prepare()
,创建Handler是会报错的。细心的读者会发现,我们在Activity例子里并没有调用什么Looper.prepare()
,还是可以成功创建Handler实例啊,这又是为什么?这实际是因为Activity已经帮我们调用了,我们可以在Activity里加上Looper.prepare()
,肯定会报错Only one Looper may be created per thread,因为重复创建了。
第二个重点还是和消息分发无关,而是在这里总算见到老朋友ThreadLocal,不然上文一大堆ThreadLocal白介绍了。使用ThreadLocal来保存什么?保存Looper对象,这样一个Looper对象和一个线程绑定,使用ThreadLocal的作用有两个,一个是技术上避免线程里重复创建Looper,另一个是不同线程的Looper能够相互隔离。为何要用ThreadLocal保存Looper对象呢?是为了方便Handler调用当前线程的Looper,Handler要发送消息和获取消息,需要知道消息队列,而消息队列由Looper提供,如果没有ThreadLocal,Handler想要找到指定线程的Looper比较麻烦。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
第三个重点终于到我们关心的消息分发。我们跳到代码的无限循环for (;;)
部分, 第一句Message msg = queue.next();
就是取出消息代码,对,这里就用到了消息队列的next()函数。取到消息后,我们一路追踪msg变量,看在哪里使用了这个变量,最后定位到这句代码msg.target.dispatchMessage(msg);
,dispatchMessage
不就是是Handler源码里提到的分发消息函数嘛,那msg.target又是什么?其实就是Handler,Handler类型的target是Message的一个成员变量,我想为啥在Handler源码里无法通过查看引用找到这里,就是因为调用比较隐晦。
Looper的源码比较长,读者可以自己在Android Studio里查看,阅读体验应该会更好。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
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);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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();
}
}
6.总结
虽然说源码解析,但也仅仅分析其中一小部分,目的是为了从源码层面看看Handler,Looper,Message,MessageQueue和ThreadLocal在消息机制中如何运作,主要脉络是看消息怎么运转,从哪里来,到哪里去,期间经历了什么。Message是消息的载体,可组成单链表结构的队列,这个队列由MessageQueue管理,例如消息的入队和消息的出队,而MessageQueue又由Looper维护,Handler通过ThreadLocal找到当前线程的Looper,进而找到MessageQueue并实现消息的发送,也就是消息的入队,而消息的出队也就是消息的分发,也是在Looper里使用MessageQueue的next方法,然后通过Handler进行分发。