浅析Android消息机制 handler之原理分析
好文推荐:
作者:月下西楼
转载地址:https://juejin.cn/post/6895995577105678343
前言
在日常的开发过程中我们经常会用到Android消息机制,其中Handler尤为常见。比如当我们执行一些耗时操作,例如读写文件,网络IO时是不建议在主线程也就是UI线程上直接进行,而是重新开一个子线程去完成这些耗时操作,而有一些耗时操作需要更改UI,比如我们需要从网络上下载一些图面显示在界面上,当图片下载完成时,由于Android的机制,子线程是无法更改UI的,所以这时候就需要切换到主线程去更改UI,而这个过程就是由Handler配合完成的。当然更新UI只是Handler的一个功能,它还有更多有趣的功能,对于Handler的实际应用我们在下一篇文章会具体展示。
ThradLocal
在正式介绍Handler之前我们先了解一下ThreadLocal,ThreadLocal是一个线程内部存储类,通过它可以在指定的线程存储数据,数据存储以后,只有在指定的线程才能获取到数据,是不是很有意思,同一个ThreadLocal在不同的线程可以取到不同的数据,它是用于获取我们后面提到的Looper,因为每一个线程都可以有一个Looper。我们以下面这个例子来说明这个过程。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBooleanThreadLocal.set(true);
Log.d("HandlerDemo", "MainThread: " + mBooleanThreadLocal.get());
new Thread("Thread1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d("HandlerDemo", "Thread1: " + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread2") {
@Override
public void run() {
Log.d("HandlerDemo", "Thread2: " + mBooleanThreadLocal.get());
}
}.start();
}
}
![](https://img.haomeiwen.com/i25222288/8278dbe1457460cc.png)
我们可以看到我们声明了一个ThreadLocal,我们在主线程中设为true,获取主线程的ThreadLocal为true,在Thread1中设为false,获取ThreadLocal为false,Thread2没有设置则为空。我们可以把线程联想成一个小区,ThreadLocal是门牌号,虽然门牌号相同,但是小区不同所住的人肯定不一样。
Android消息机制原理
Android消息机制的原理如下图所示。![](https://img.haomeiwen.com/i25222288/49a817ae8385ebcf.png)
我们以一个例子来说明这个过程,假如我们需要下载一个文件,这是一个耗时操作,我们需要重新开一个线程去下,下载后Toast完成下载,如果直接在子线程中使用Toast会报错,所以就需要使用Handler来完成这个过程。代码如下
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Success", Toast.LENGTH_LONG).show();
}
});
}
}).start();
}
}
我们首先可以看到handler调用了post方法。post方法源码如下。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
我们可以看到post方法调用了sendMessageDelayed方法,从名字可以看出这是延迟发送消息,post是它的一个特殊情况。sendMessageDelayed的源码如下
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
而sendMessageDelayed方法调用了方法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);
}
这里很关键,我们看到了Android 消息机制的第一个幕后英雄MessageQueue,我们现在捋一下思路,当我们在子线程经过耗时操作得到结果,然后通过handler的post来发送消息,post的方法经过一系列的调用来到这里,首先获取一个MessageQueue,如果MessageQueue不存在则报错,如果存在则调用了enqueueMessage方法,源码如下。
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);
}
从名字我们看出这个函数主要作用是把Message插入消息队列MessageQueue,MessageQueue虽然名字叫队列,可是有些人表面上说是队列,实际上却是个链表。我们可以看到在这里将msg的target设置为this,也就是这个handler,这是一个伏笔,之后将会了解它的作用。现在我们还是按照这个流程走下去,最后调用了MessageQueue的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插入了MessageQueue,我们还可以看出发送的时间when决定了在Message的位置。之后可以通过Looper.loop来获取MessageQueue的消息,关键代码如下。这里就可以看到之前的伏笔msg.target,也就是最初的那个handler,调用了dispatchMessage。
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;
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;
}
msg.target.dispatchMessage(msg);
}
Handler的dispatchMessage源码如下:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们可以看到Message从Handler到MessageQueue,再到Looper,最后又回到了Handler,而Handler实在主线程,就可以操作UI了。现在再看看之前那张图是不是更加清晰了呢。
![](https://img.haomeiwen.com/i25222288/04faa6a623e47625.png)
大家如果还想了解Android 相关的更多知识点可以点进我的【GitHub】项目中,里面记录了许多的Android 知识点。
![](https://img.haomeiwen.com/i25222288/188472d8be8324c6.png)