Android开发中要求:UI操作在主线程中进行,耗时操作尽量在子线程中进行。
项目中我们经常会遇到处理IO流数据、网络请求等耗时操作。处理结果如果需要在UI中显示,那么就与主线程UI操作,耗时操作子线程进行冲突了。消息处理机制(Handler)就出现了,它的作用就是将数据在不同线程中进行转化。
Android中大多数切换进程框架都能发现handler踪迹。例如常用的框架:AsyncTask EventBus Rxjava 等等。可见,Handler机制是很多线程切换框架的基石。虽然Android开发由于Kotlin语言以及Compose的出现越来越简便,但是Handler在Android大厦中的地位仍然不可撼动。
1.Handler机制原理
Handler将Message内容添加到对应线程Looper的MessageQueue中,Looper.loop()不断轮询将Message消息转化到对Looper.loop()所在线程通过handlMessage处理。
2.Handler机制解析
2.1 初识ThreadLocal
val threadLocal = ThreadLocal<Message>()
Thread({
threadLocal.set(Message())
Log.d("MainActivity", "同线程 ${Thread.currentThread().name} threadLocal = ${threadLocal.get()}")
Thread({
Log.d("MainActivity", "其他线程 ${Thread.currentThread().name} threadLocal = ${threadLocal.get()}")
}).start()
}).start()
日志:
Thread run: Thread-2 threadLocal = { when=-5h19m58s195ms barrier=0 }
Thread run: Thread-3 threadLocal = null
如上,可以看到ThreadLocal对象在Thread-2线程中set的Message对象,只能在同一线程中获取。
这就保证了主线程Looper对象不会被其他子线程破坏,每个线程对应一个Looper对象,Handler消息队列中具体使用下面会进行分析。
2.2 Looper简单使用
一个简单例子
override fun onResume() {
super.onResume()
BackgroundThread().start()
}
class BackgroundThread : Thread() {
override fun run() {
Log.d("MainActivity", "外层线程: ${currentThread().name} ")
Looper.prepare()
//子线程中操作
Looper.myLooper()?.let {
Thread{
Log.d("MainActivity", "handleMessage: 内层线程 = ${currentThread().name} ")
Handler(it).post {
Log.d("MainActivity", "handleMessage:thread = ${currentThread().name} ")
}
}.start()
}
Looper.loop()
}
}
看下日志:
2026-01-15 10:26:14.083 12406-12443 MainActivity com.sun.test D 外层线程: Thread-2
2026-01-15 10:26:14.083 12406-12444 MainActivity com.sun.test D handleMessage: 内层线程 = Thread-3
2026-01-15 10:26:14.084 12406-12443 MainActivity com.sun.test D handleMessage:thread = Thread-2
该例子中Thread-3线程中的Handler将Thread-3线程中的消息转化为了外层线程:Thread-2中。
主要的关键就是Looper.myLooper(),由于myLooper() 是在线程:Thread-2中创建,所以此时创建的Handler绑定的为线程:Thread-2中的Looper对象。所以,会通过post方法将Message添加到线程:Thread-2持有的MessageQueue中的队列中。
注意:Handle消息队列最终是将消息转化到Handler(it) 中的it(Looper)也就是Looper.myLooper() 返回的Looper对象所创建的线程中。
2.3 Looper处理消息
接下来,咱们根据上面2.2节中例子进行分析。先看Looper.prepare()操作。
@UnsupportedAppUsage
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));
}
从代码中可以看到prepare中通过ThreadLocal对象创建了一个Looper对象,在2.1 初识ThreadLocal说过用法,所以Looper对象直接与该子线程进行了绑定。继续跟踪源码可以看到Looper构造器中存在一个MessageQueue对象。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
通过构造函数就保证了不同线程中Looper对象都会存在与MessageQueue对象一一对应的关系。所以,每个子线程MessageQueue对象仅仅处理自己的Message消息。
分析下myLooper()源码。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在上面讲过,ThreadLocal对象中设置的Looper对象只有在同一个线程中才能获取到。
所以,此时就获取到了同一Thread-2线程中prepare() 时通过ThreadLocal对象设置的那个Looper对象。
接下来就是重点方法loop()源码如下:
public static void loop() {
final Looper me = myLooper();
......
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
上面这段代码就是整个Looper的核心,loop()中存在一个 for (;;) 循环不断进行轮循。里面具体实现为loopOnce(me, ident, thresholdOverride) 方法源码(不同AndroidSDK版本实现基本一致)。此处传入me对象就是与线程绑定的Looper对象。
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;
}
......
msg.target.dispatchMessage(msg);
return true;
}
先思考下for (;;)。这个里面的内容启动以后会一直进行遍历操作,此时如果loopOnce(me, ident, thresholdOverride) 返回为false时才会return退出遍历操作,如果返回为true就会一直遍历。从上面代码可以看到正常情况下都会返回true。
在非主线程中:msg == null情况:
Looper.quit() 时,消息队列会被标记为退出状态,next() 方法会返回 null,导致 loopOnce 返回 false,最终使整个消息循环终止。
主线程调用Looper.quit()会报错。所以,正常运行过程时主线程不会出现msg == null情况。
Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
代码中最核心内容msg.target.dispatchMessage(msg); 这就很明显了。msg.target就是咱们绑定到Messag对象的Handler对象,最终通过Handler对象的dispatchMessage(msg)方法对事件进行下发。
由于 msg.target.dispatchMessage(msg);根据Looper.loop()推动,所以最终就将Message对象在创建Looper对象的线程中进行执行。
2.4 Handler处理和发送消息
跟踪Handler对象,查看dispatchMessage(msg)方法做了什么?
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public void handleMessage(@NonNull Message msg) {
}
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
首先,如果Handler对象通过post类型的方法传递Message对象,最终会通过handleCallback(Message message) 处理轮循消息。
其次,上面else分支有两个handleMessage(@NonNull Message msg) 需要注意下。
下面那个是Hander自己的方法,多数情况我们会通过重写这个方法,在handleMessage(@NonNull Message msg) 中处理传递过来的消息。
上面那个是在Handler接口Callback中定义的handleMessage(@NonNull Message msg)。这个方法有个返回值,可以根据返回值对Handler对象中重写的handleMessage(@NonNull Message msg)方法进行拦截。
上面是对Looper中代码走向分析,解析来看看Hanlder对象怎么将消息发送到MessageQueue
再次看下上面的例子(为了方便查看,我这里又复制粘贴一份)
override fun onResume() {
super.onResume()
BackgroundThread().start()
}
class BackgroundThread : Thread() {
override fun run() {
Log.d("MainActivity", "外层线程: ${currentThread().name} ")
Looper.prepare()
//子线程中操作
Looper.myLooper()?.let {
Thread{
Log.d("MainActivity", "handleMessage: 内层线程 = ${currentThread().name} ")
Handler(it).post {
Log.d("MainActivity", "handleMessage:thread = ${currentThread().name} ")
}
}.start()
}
Looper.loop()
}
}
看下日志:
2026-01-15 10:26:14.083 12406-12443 MainActivity com.sun.test D 外层线程: Thread-2
2026-01-15 10:26:14.083 12406-12444 MainActivity com.sun.test D handleMessage: 内层线程 = Thread-3
2026-01-15 10:26:14.084 12406-12443 MainActivity com.sun.test D handleMessage:thread = Thread-2
上述代码可以看到,创建了一个Handler(it)对象。根据上面对于Looper对象分析,可以知道在这里it(Looper.myLooper())可以获取到当前线程的Looper对象
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
this(looper, callback, async, /* shared= */ false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async,
boolean shared) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
mIsShared = shared;
}
看下构造器中mQueue = looper.mQueue ,这里handler对象就持有了与Thread-2子线程Looper对象中的MessageQueue对象。并且通过post执行了一个方法块。下面一起看下具体做了哪些操作。接下来跟踪 post(@NonNull Runnable r) 看下消息是怎么放入MessageQueue对象。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
仔细观察上面这个方法m.callback = r;,将post方法块Runnable对象交由Message对象的callback变量。
Hander机制中消息发送方法好几种,最终都是将内容保存进入Message向MessageQueue中的链表中添加。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
上面代码参数SystemClock.uptimeMillis() + delayMillis需要注意。这个参数会获取到当前时间并且加上延迟持时间,不做延迟操作延迟时间为0,这个时间点是维护MessageQueue对象中Message链表的插入关键。
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);
}
上面构造器已经进行了赋值 MessageQueue queue = mQueue 操作 。此时,queue就是就是与Looper对象进行绑定的MessageQueue对象。
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);
}
上面就是Handler的核心操作。2.3节中msg.target.dispatchMessage(msg) 中 target对象 在此处msg.target = this进行赋值操作。
看下 enqueueMessage(Message msg, long when) 第二个参数 uptimeMillis ,这个参数就是就是获取的当前时间和延迟时间之和,最终将 msg.when = when 进行赋值,整个 Message链表 就是根据 when 进行的查找和插入。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
......
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;
if (p == null) {
mLast = mMessages;
}
} else {
......
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;
mLast = null;
}
......
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
大致流程如上面代码中所示。
情况一:p == null || when == 0 || when < p.when情况中只有p==null为正常情况,when == 0与when < p.when为特殊情况这里不考虑。从代码里面可以看到,该情况直接将传入的Message对象作为链表第一个Message对象。
情况二:else分支 内部使用 when < p.when 作为条件进行链表的查找和插入,便可以根据 when 保证 message 链表有序取出。延迟时间越大,when值就越大,执行顺序就越靠后。由于此处 MessageQueue对象已经进行了排序,所以直接获取链表当前一个Message对象就行。
4.注意事项
- Looper.loop()中for (;;)会导致主线程阻塞吗?
答案:会的。
《Java编程思想》对于堵塞的定义
堵塞(Blocked):线程可以运行,但有某种东西阻碍了它。若线程处于堵塞状态,调度机制可以简单地跳过它,不给它分配任何CPU时间。除非线程再次进入“可运行”状态,否则不会采取任何操作。
虽然,loop()是个死循环,但是在MessageQueue的next()方法中存在一个nativePollOnce(ptr, nextPollTimeoutMillis); 方法。通过 native 层的 epoll 机制实现线程阻塞,当消息队列为空或无到期消息时进入休眠状态。休眠后会根据MessageQueue的enqueueMessage方法中的nativeWake(mPtr); 进行唤醒。