Android中的Handler机制详解
Handler机制的源码十分重要!
基本属于面试必问点!
我们是基于SDK 31的Handler机制去分析的,可能与以前的博客有略微细节变动,但是大体框架不会变。
Handler有什么用?
提到消息机制大家应该都不陌生,在日常开发中不可避免地要涉及这方面的内容。
当我们在子线程去更新UI的时候,会报错,此时我们需要使用Handler去切换线程到主线程更新UI
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
thread {
Thread.sleep(2000)
textview.text = "text"
}
}
}
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.redrock.handler, PID: 4124
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at android.view.View.requestLayout(View.java:25390)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3593)
at android.view.View.requestLayout(View.java:25390)
at android.widget.TextView.checkForRelayout(TextView.java:9719)
at android.widget.TextView.setText(TextView.java:6311)
at android.widget.TextView.setText(TextView.java:6139)
at android.widget.TextView.setText(TextView.java:6091)
at com.redrock.handler.MainActivity$onResume$1.invoke(MainActivity.kt:19)
at com.redrock.handler.MainActivity$onResume$1.invoke(MainActivity.kt:17)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
所以Handler设计的初衷其实就是为了跨线程通信,更具体的说,
是为了解决子线程中无法访问UI的问题
上面那段在子线程更新UI的代码用Handler改造后的代码长这样
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> textview.text = msg.obj as String
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
thread {
Thread.sleep(2000)
val msg = Message.obtain()
msg.what = 1;
msg.obj = "test"
handler.sendMessage(msg)
}
}
(我这里的代码有内存泄漏的风险,后面在细说)
主流程原理
带大家回顾了以下相关的基础知识后,我们就来讲Handler的原理
我们主要是想要搞明白,Handler是怎么做到跨线程通信的,怎么我在这个子线程用Handler发了条消息,却在主线程去更新了UI呢?
这个是我们想要去搞清楚的主要问题,接下来我们就来探索
Handler发送消息有很多种重载,我们常用的就是sendMessage这个方法,但其实它还有这些用来发送消息的方法
sendMessageDelayed是用来发送延时消息的
通过查看源码,发现不管你调用哪个发送消息的方法,其实最后都会走到enqueueMessage这个方法
以sendMessage为例
public final boolean sendMessage(@NonNull Message msg) {
//在这里调用了发送延时消息的函数,延时的时间为0
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
//这个函数的时间参数指明执行消息的时间,所以是当前时间+延时的时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//在这里拿到了一个消息队列MessageQueue,然后调用了enqueueMessage方法
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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
因此,接下来我们重点来看这个enqueueMessage做了什么
注意,我们看源码时一定要避免只见树木不见森林,对于一个迭代了很久的框架来说,里面会有很多的细节,我们在第一次读源码时注意只弄懂关键流程,哪些细分分支可以在弄懂主流程之后再来弄懂,那么问题来了,我自己看源码怎么去知道哪些是主流程的代码,哪些是细分的代码呢?我的看法要么你是个Android老手,靠着经验可以知道,要么就是去参考别人,所以接下来我会先带着大家走一遍主流程再来看那些细节
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//msg里面有一个target字段,类型是Handler
//因此这里把发送消息的那个Handler给保存在了msg里
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
//这里是细节 后面再来看
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
因此接下来分析
queue.enqueueMessage(msg, uptimeMillis);
这里的代码很多,我们慢慢来分析
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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;
}
我把一些为了保证系统稳健性判空的代码以及不是主流程的代码删掉后只保留主流程的代码,可能更好看懂
//这些是主流程的代码
boolean enqueueMessage(Message msg, long when) {
//首先将消息应该去执行的时间保存在了消息中
msg.when = when;
//然后拿到了mMessages并赋值给p
//那么什么是mMessages呢?
//MessageQueue会把所有的消息通过链表的形式按照时间顺序存储起来
//mMessages是这个链表的头节点,执行时间越早的msg在这条链表上会被放在越前面的位置
//所以下面的这些逻辑就是找到链表的头节点,然后按照时间顺序把消息插入到这个链表里面去
Message p = mMessages;
//如果链表里面还没有节点 || 这个消息需要立即执行 || 消息需要执行的之间比链表第一个节点的时间还要小
//如果满足以上三个条件就直接让这条消息当头节点
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
//如果不满足的话,就到链表里去找到执行时间比这条消息还要靠后的节点,然后插入在它的前面
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
}
}
return true;
}
(链表示意图)
插入消息的流程图

现在我们已经知道了我们发送的消息最终会被存放到MessageQueue这样一个以时间顺序存储消息的数据结构里面,那么有存就有取嘛
是谁负责从MessageQueue里面取消息然后去执行的呢?是Looper
Looper是个啥玩意?理论上来说如果需要使用Handler传递某个消息到某个线程的话,这个线程是必须要有一个Looper的
假如我们直接在子线程中使用在子线程构造的handler发送消息的话,就会报错
thread {
val handler = Handler();
handler.sendEmptyMessage(1);
}
E/AndroidRuntime: FATAL EXCEPTION: Thread-3
Process: com.redrock.handler, PID: 4685
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-3,5,main] that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:227)
at android.os.Handler.<init>(Handler.java:129)
at com.redrock.handler.MainActivity$onResume$2.invoke(MainActivity.kt:40)
at com.redrock.handler.MainActivity$onResume$2.invoke(MainActivity.kt:39)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
要去修改为这样才不会报错
thread {
Looper.prepare()
val handler = Handler(Looper.myLooper()!!);
Looper.loop()
handler.sendEmptyMessage(1);
}
那在这里我需要传递一个消息到主线程并且没有在主线程里去调用Looper.prepare() Looper.loop()
为什么没有报错呢?其实在app启动的时候Android已经帮我们在主线程中调用了这俩个函数
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> textview.text = msg.obj as String
}
}
}
说了这么多Looper的注意事项,那Looper到底是干嘛的呢?
Looper会不停的从MessageQueue中查看是否有需要执行的消息,如果有就拿出来分发给对应的Handler处理
在Looper里面,有一个next函数,这个函数中有一个死循环会不断的尝试从MessageQueue中取消息
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
// 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);
me.mSlowDeliveryDetected = false;
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
// 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 (me.mSlowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
me.mSlowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
me.mSlowDeliveryDetected = 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();
return true;
}
我们只看关键代码
public static void loop() {
//首先拿到当前线程的Looper
final Looper me = myLooper();
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
//死循环不断从MessageQueue中取消息,如果取不到消息就阻塞住一直到取到了消息
//这个next函数是怎么从MessageQueue中取消息的我们等下分析
Message msg = me.mQueue.next();
//还记得我们在发送消息时会把发送这个消息的Handler给保存在msg的target字段中吗?
//接下来会调这个handler的dispatchMessage函数并且把消息分发给它
//dispatchMessage函数是不是很熟悉?没错,这就是我们重写Handler时的那个函数
msg.target.dispatchMessage(msg);
return true;
}
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> textview.text = msg.obj as String
}
}
}
至此,皆大欢喜,我们终于把主线流程给走完了,知道了从一个消息被发送到这个消息被分发给Handler的流程
我们来捋一下这个流程
首先,我们通过在子线程中使用Handler发送了一条消息,然后这条消息会被插入在MessageQueue中,并且这条消息会有一个target字段指明发送这条消息的Handler,然后looper会不断的从这个messageQueue中取消息再分发给对应的handler从而实现了跨线程通信。
是在什么时候实现的跨线程呢?每一个线程如果要处理跨线程的消息就要有一个自己的Looper和MessageQueue,以主线程为例,Android底层已经帮我们创建好了主线程的Looper和MessageQueue,我们要从子线程发送一条消息给主线程,就是把这条消息给插入到了主线程的MessageQueue中,然后主线程的Looper再从MessageQueue中取出消息分发给对应的Handler去处理。
我们接下看一下Looper是怎么从MessageQueue中取到消息的,看到MessageQueue的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;
}
}
主流程代码:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
//如果暂时没有消息,就先阻塞住,会等到插入新消息的时候唤醒
//或者有消息,但是链表的头节点的那条消息还要等待一段时间才要执行
//那就计算出这个时间,然后阻塞这么长一个时间
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
//最近的那条要执行的消息的执行时间都要晚于当前的时间,那就计算出这个时间差值,然后阻塞
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//不是的话,就取出消息并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
}
}
boolean enqueueMessage(Message msg, long when) {
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//如果没有被阻塞住,或者消息的targer Handler为null 以及消息是异步消息就不需要被唤醒
//关于如果没有被阻塞住就不需要就唤醒很容易理解
//至于后面俩个我们后面再来解释
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);
}
}
}
细节流程原理
讲完了主流程,我们接下来讲分支流程
Message.obtain
不知道大家注意到没,我在获取一个消息实例的时候,并不是去new 而是通过Message.obtain()去获取消息,这样做有什么好处呢?
因为新建和销毁一个Message对象存在性能损耗,所以Handler就希望一个消息实例在new出来后,使用完后把里面的相关信息全部清除掉,然后可以复用这个消息实例
当我们使用Message.obtain()时,会先去尝试是否能复用一个已经创建的消息实例,如果获取不到,才会去new一个,然后在每个消息执行完后,就会把这个消息的相关信息清除然后缓存起来
我们来看看相关源代码
private static Message sPool;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可以看到,这里会先拿到一个sPool,然后从sPoll中拿到一个消息实例,然后再把spoll给指到下一个
那这个spoll是个什么东西呢?
它就是一个消息实例,其实这里就是把所有等待复用的消息实例给通过链表的形式组织了起来,然后sPoll就是这个链表的头节点
在Looper的LoopOnce里面
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
return true;
}
在消息执行完后,会调用msg.recycleUnchecked();
会在这里面清除掉msg的详细信息然后插入到sPoll中
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
ThreadLocal
我们之前说了,如果在子线程中要去使用handler是要去给子线程手动配置Looper的
thread {
Looper.prepare()
val handler = Handler(Looper.myLooper()!!);
//开启Looper的消息循环,开始从这个线程的MessageQueue中取数据分发到对应的Handler
Looper.loop()
handler.sendEmptyMessage(1);
}
我们来看看Looper.prepare()干了些什么
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//一个线程只能有一个Looper,否则就会报错
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
可以看到把new 出来的Looper放到了ThreadLocal类型的静态变量sThreadLocal里面
在继续跟进ThreadLocal的源码前,我们首先大概介绍下ThreadLocal,防止在看ThreadLocal源码时迷了路
ThreadLocal是一个以线程为作用域存储数据的一个类,什么叫以线程为作用域?就是说对于同一个ThreadLocal变量,不同的线程从里面去取数据能取到不同的值,就比如A线程在这个sThreadLocal里面存了A线程的Looper,B线程在这个sThreadLocal里面也存了B线程的Looper,然后A线程去和这个sThreadLocal说,我要取Looper出来,sThreadLocal会把A线程的Looper取出来,而不会取到B线程的Looper,B线程同理.构造这个ThreadLocal里面的泛型的实际类型就表明了这个ThreadLocal希望为线程存储什么类型的数据。
好的,接下来我们来看看原理
/**
* 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的这样一个变量
//每一个线程都会有一个ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
//如果Thread里面的ThreadLocalMap还没有初始化,就先初始化再存放值,否则就直接存放值
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
总结一下:
ThreadLocal中有一个ThreadLocalMap变量,这个变量存储着键值对形式的数据。
- key为this,也就是当前ThreadLocal变量。
- value为T,也就是要存储的值。
每个线程中都有一个ThreadLocalMap,这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能获取到的值却不一样。
quit
在分析Looper和MessageQueue时说道,Looper一但开始消息循环就会不断死循环从MessageQueue里面去取消息,如果取不到消息就暂时阻塞住直到可以取到消息为止,那么有没有办法停止这个死循环呢?有的,可以调用Looper的quit方法停止消息循环
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
/**
* Quits the looper safely.
* <p>
* Causes the {@link #loop} method to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* However pending delayed messages with due times in the future will not be
* delivered before the loop terminates.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
public void quitSafely() {
mQueue.quit(true);
}
可以看到有俩种方法,一种是直接quit,不管有没有正在执行的消息都直接退出,还有一种是有正在执行等待分发的任务先给分发完,延时的消息就不处理了,我们选其中一种进行分析
可以看到最终都是调用到了MessageQueue的quit方法
void quit(boolean safe) {
if (!mQuitAllowed) {
//如果退出主线程的Looper直接抛出异常
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
//如果正在退出,返回
if (mQuitting) {
return;
}
//将正在退出的标志位设置为true
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
可以看到这里就是拿到了消息队列的头节点,然后一个一个调用去回收这个消息
大家可以尝试自己去分析一下removeAllFutureMessagesLocked
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
再来说说把这个mQuitting标志位设置为true会造成什么连锁反应
1.如果正在退出,尝试往MessageQueue中插入数据会失败
boolean enqueueMessage(Message msg, long when) {
//如果mQuitting为true 会直接返回false
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) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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;
}
2.Looper中的死循环会退出
public static void loop() {
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next();
//可以看到Looper中的死循环会在MessageQueue返回msg为null时就退出
//正常情况下如果不去调用quit方法,Looper是一定可以从MessageQueue中取到消息的,就算暂时取不到消息,也会阻塞住等待有消息再取出来并返回
//但是调用了quit方法后,MessageQueue就会返回null
if (msg == null) {
return false;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
return true;
}
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int nextPollTimeoutMillis = 0;
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
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 {
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 {
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
//看这里
if (mQuitting) {
dispose();
return null;
}
}
}
异步消息和同步屏障
啥子是异步消息和同步屏障?
在Handler中,有三种消息类型:
同步消息。也就是普通的消息。
异步消息。通过setAsynchronous(true)设置的消息(API >= 22)或者是async参数设置为true的handler发送的消息
同步屏障消息。通过postSyncBarrier()方法添加的消息,特点是target为空,也就是没有对应的handler。
这三者之间的关系如何呢?
- 正常情况下,同步消息和异步消息都是正常被处理,也就是根据时间when来取消息,处理消息。
- 当遇到同步屏障消息的时候,就开始从消息队列里面去找异步消息,找到了再根据时间决定阻塞还是返回消息。
也就是说同步屏障消息不会被返回,他只是一个标志,一个工具,遇到它就代表要去先行处理异步消息了。
所以同步屏障和异步消息的存在的意义就在于有些消息需要“加急处理”。
我们来看看相关的源码
首先看看同步屏障
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
这里面的逻辑没啥好说的,就是和之前在MessageQueue中去按照时间顺序去插入消息是一样的
接下来看到从MessageQueue中取消息时遇到了同步屏障消息和异步消失是怎么处理的
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//这个if判断表明遇到了同步屏障消息,因为同步屏障消息的target字段为空
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//这一段循环结束的条件是 msg == null(表明找不到异步消息) 或者是msg是异步消息
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;
}
}
可以看到,如果当前Message是没有target的,就代表达到了当前的MessageQueue的同步消息屏障,队列会不断向后查询,同步消息将会被掠过,直到队列中出现了一个非同步消息或者消息队列已经为空,即将next的逻辑变为:弹出下一个异步消息。整体逻辑可以描述为:如果队列没有遍历到同步消息屏障,则正常按照时间弹出队首,如果遇到同步屏障,则只处理异步消息。就如同下图所示
那么怎样清除同步消息队列呢?调用 removeSyncBarrier() 即可
IdleHandler
最后来看看IdleHandler
首先来看看IdleHandler是啥?
我们之前说当MessageQueue没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue还会做一件事,就是检查是否存在IdleHandler,如果有,就会去执行它的queueIdle方法。
因此IdleHandler就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。
我们可以使用IdleHandler去执行一些当系统空闲时才需要去执行的低优先级任务
可以通过如下方式加入一个IdleHandler
Looper.myQueue().addIdleHandler {
...
...
false
}
返回false,表示执行完成后从MessageQueue中移除掉自己
来看看相关源码
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;
}
//IdleHandler的数量 初始化值为0
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.
//pendingIdleHandlerCount只有在初始化的时候会是负数,那么当此时没有消息可以处理的时候,就会去初始化
//pendingIdleHandlerCount
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.
//遍历执行IdleHandler
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;
}
}
至此 MessageQueue 中 next方法里的每一行代码我们都解释清楚了!
Handler内存泄漏问题
最开始的时候说了,我这样子去用Handler是有问题的
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> textview.text = msg.obj as String
}
}
}
这样子写有内存泄漏的风险,建议还是这样写
private class MyHandler() : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
...
...
}
}
这俩个有啥子区别呢?区别就在于一个是内部类,一个是静态内部类,内部类会持有外部类的引用,而静态内部类则不会
我们可以看下KT字节码反编译成java代码后的代码
private final <undefinedtype> handler = new Handler(Looper.getMainLooper()) {
public void handleMessage(@NotNull Message msg) {
Intrinsics.checkNotNullParameter(msg, "msg");
switch(msg.what) {
case 1:
TextView var10000 = (TextView)MainActivity.this._$_findCachedViewById(id.textview);
Intrinsics.checkNotNullExpressionValue(var10000, "textview");
Object var10001 = msg.obj;
if (var10001 == null) {
throw new NullPointerException("null cannot be cast to non-null type kotlin.String");
} else {
var10000.setText((CharSequence)((String)var10001));
}
default:
}
}
};
private static final class MyHandler extends Handler {
public void handleMessage(@NotNull Message msg) {
Intrinsics.checkNotNullParameter(msg, "msg");
super.handleMessage(msg);
}
public MyHandler() {
super(Looper.getMainLooper());
}
}
那什么是内存泄漏呢?这个和JVM的相关知识有关,通俗的话来说,就是当长生命周期的对象持有了短生命周期的引用,就会导致内存泄漏,有兴趣的可以自己去了解。
AQ
MessageQueue是干嘛呢?用的什么数据结构来存储数据?
MessageQueue就是一个用于存储消息、用链表实现的特殊队列结构。
Handler中延时消息是怎么实现的?
MessageQueue是一个按照消息时间排列的一个链表结构,根据消息的when字段插入即可。
MessageQueue的消息怎么被取出来的?
通过Looper的next方法取消息,里面是一个死循环,保证一定可以取到一条消息,如果没有可用消息,那么就阻塞在这里,一直到有新消息的到来。
阻塞的情况有俩种 没有消息 和当前消息还没有到要发送的时间
ThreadLocal运行机制?这种机制设计的好处?
ThreadLocal中有一个ThreadLocalMap变量,这个变量存储着键值对形式的数据。
- key为this,也就是当前ThreadLocal变量。
- value为T,也就是要存储的值。
每个线程中都有一个ThreadLocalMap,这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能获取到的值却不一样。
为什么ThreadLocalMap要弱引用ThreadLocal?(需要懂内存泄漏的相关知识)
因为ThreadLocalMap如果是强引用ThreadLocal的话,假如我们将
ThreadLocal置为null,会因为ThreadLocalMap持有了ThreadLocal的引用而无法被GC
为什么不能在子线程中更新UI?
因为Android中的UI控件不是线程安全的。
如果通过加锁来实现UI控件的线程安全会导致UI访问的效率降低影响用户体验。
Looper中的quitAllowed字段是啥?有什么用?
是否允许退出的标志字段。在quit方法中有被用到,如果这个字段为false,代表不允许退出,就会报错。
quit方法就是退出消息队列,终止消息循环。
- 首先设置了mQuitting字段为true。
- 然后判断是否安全退出,如果安全退出,就清空所有的延迟消息,之前没处理的非延迟消息还是需要处理
- 如果不是安全退出,就直接清空所有的消息
当调用了quit方法之后,mQuitting为true,enqueuemessage方法中消息就发不出去了,会报错。next方法返回null,那么loop方法中就会退出死循环。
Handler、Looper、MessageQueue、线程是一一对应关系吗?
一个线程只会有一个Looper对象,所以线程和Looper是一一对应的。
MessageQueue对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的。
Handler的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue。
总结:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的。
Looper.loop方法是死循环,为什么不会卡死(ANR)?
1.主线程需要这样的死循环来处理View界面的变化
2.而且没有消息的时候,handler会阻塞,主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。
3.在收到跨进程消息后,会交给主线程的Hanlder再进行消息分发。所以Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施,比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终执行到onCreate方法。
4.真正导致ANR的原因不是死循环,而是因为在某个消息处理的时候操作时间过长
HandlerThread和IntentService的原理
HandlerThread就是一个封装了Looper的Thread类
通过获取 HandlerThread 的 looper 对象传递给 Handler 对象,可以在 handleMessage()方法中执行异步任务。
HandlerThread 与线程池不同,HandlerThread 背后只有一个线程,多任务时需要等待处理
IntentService 是一个继承了 Service 的抽象类,它封装了HandlerThread 和 Handler,当 IntentService 被第一次启动时,它的 onCreate()方法会被调用,onCreat()方法会创建一个HandlerThread,然后使用它的 Looper 来构造一个 Handler 对象,这样通过 handler 发送的消息最终都会在HandlerThread 中执行。
Handler内存泄漏的原理
内存泄漏的本质是因为长生命周期的对象持有了短生命周期对象的引用导致短生命周期的对象无法被正确回收。
Handler如果是activity的内部类,会导致handler持有activity的引用,而handler在发送message时,message会持有handler的引用,而message又被messageQueue引用,messagequeue又被looper引用,looper又被threadlocal引用,threadlocal又被主线程引用,从而导致handler内存泄漏。
在子线程中更新UI一定会报错吗?
(这个问题很难,设计到的Android FrameWork层源码较多,提示,需要弄清楚这个问题,跟ActivityThread的handleResumeActivity有关,View的绘制流程就是从这个方法开始的)
标题:Android中的Handler机制详解
作者:OkAndGreat
地址:http://zhongtai521.wang/articles/2022/04/24/1650812885567.html