前言
本文是自己学习Handler时的一些感受和心得,供本人复习使用,因本人技术实力有限,会出现知识错误或缺漏的问题,请大家多多谅解。本文仅涉及线程间的通信,并不讲解进程间Handler机制的作用。
什么是Handler
Handler是处理Android(进程中)线程间通信的通信机制,通过Handler我们可以实现子线程和主线程之间的通信。那么Handler到底是怎么工作的呢。在此之前,那我们先明确几个Handler的相关类
Handler:实现消息发送和接受消息
Loop:消息轮询器
Message:消息实体
MessageQueue:用于储存和管理消息的消息队列
下面我们一一进行讲解
Loop的相关说明
我们都知道mian方法是整个Java程序的入口,那么Android程序的main方法在哪呢,答案是ActivityThread的main方法
ActivityThread.java
public static void main(String[] args) {
...
//创建Loop
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//循环loop
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在main方法中我们看到了其中调用了Looper.prepareMainLooper()和Loop.loop()方法,先看Looper.prepareMainLooper()方法。
public static void prepareMainLooper() {
//创建Loop
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//获取创建的Loop对象
sMainLooper = myLooper();
}
}
首先调用了prepare方法,prepare主要是创建Loop对象,并由ThreadLoacal保证线程内仅有一份Looper实例,如果当前线程中已经持有了Looper的对象则会抛出异常,myLooper()则返回当前进程中持有的Looper对象。prepare和myLooper的代码如下:
//quitAllowed表示是否允许退出looper死循环
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
需要说明的是prepare的quitAllowed参数表示是在没有消息时是否可以退出Looper循环,主线程默认不允许退出。现在再来看一下Looper的构造方法
private Looper(boolean quitAllowed) {
//创建消息队列
mQueue = new MessageQueue(quitAllowed);
//保存当前的线程
mThread = Thread.currentThread();
}
Looper的构造方法创建了用于保存和管理消息的队列mQueue和持有了当前线程的引用。需要注意的是Looper的构造方法是私有的,只能通过调用prepareMainLooper(主线程自己调用)或者Looper.prepare(子线程调用)进行Looper的创建。每个线程只能持有一个Looper对象(由ThreadLocal保证)。至此Looper的prepareMainLooper方法调用说明结束。现在看一下Looper.loop()方法
public static void loop() {
//获取Looper对象
final Looper me = myLooper();
//获取Looper失败,抛出异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
me.mInLoop = true;
//获取Looper中的消息列表
final MessageQueue queue = me.mQueue;
....
//请注意这是一个死循环
for (;;) {
//获取消息队列中的消息
Message msg = queue.next(); // might block
//消息为null时退出循环
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
....
try {
//分发消息,会调用handler的dispatchMessage,该方法最终会调用到Handler的handleMesaage方法
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);
}
}
....
}
}
可以看到Looper的loop中会调用一个死循环,在循环体内部会不断获取消息队列mQueue中的消息,并对消息进行分发。如果从消息队列中获取不到消息,则退出循环
这也就是说在ActivityThread的main方法中会出现死循环,其实这也正是App主线程为什么不会结束的原因,是因为其内部存在死循环。为什么主线程内部会有一个死循环呢?:对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。但我们从Looper.loop的方法中看到,当消息队列中没有消息时,会退出这个死循环,那么为什么我们从来没有看到过主线程的退出呢,这是因为主线程在消息列表中没有消息时会进行阻塞,等到消息到来时进行唤醒取出消息。根本不会返回null,自然不会退出死循环了。关于消息的阻塞和唤醒会在消息队列中进行介绍。至此,Loop的说明完毕,现在看一下消息队列MessageQueue的相关源码。
MessageQueue的说明
MessageQueue是保存和管理消息的消息队列,其组织结构是具有优先级的单链表实现的队列。调用enqueueMessage进行消息的入队,调用next实现队列的出队。首先从enqueueMessage开始吧。
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.");
}
//当前Loop循环退出(调用Loop的quit方法),抛出异常
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;
//如果当前队列为空,或者当前消息延迟时间为0,或当前消息的延迟时间比对头的消息少
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//设置新的对头,如果当前队列处于阻塞状态还需要进行唤醒(mBlocked=true表明列表已经进行了阻塞)
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;
}
从这里可以看出enqueueMessage充当了一个生产者,当调用该方法时就像队列中添加一个消息,并通过延迟时间找到插入位置进行插入,插入的操作也是典型的单链表插入(只维护节点的后继关系),当前队列如果没有消息,且是阻塞状态的话,则唤醒队列
在Looper.loop()方法中我们看到调用了MessageQueue的next方法,那我们就从该方法入手吧。
public static void loop() {
//获取Looper对象
final Looper me = myLooper();
//获取Looper失败,抛出异常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
me.mInLoop = true;
//获取Looper中的消息列表
final MessageQueue queue = me.mQueue;
....
//请注意这是一个死循环
for (;;) {
//获取消息队列中的消息
Message msg = queue.next(); // might block
.....
....
}
}
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();
}
//根据nextPollTimeoutMillis进行阻塞
//-1一直阻塞直到enqueueMessage有新的消息到来进行唤醒
// 0 不进行阻塞
//大于0阻塞多少毫秒后唤醒
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 {
//获取message
mMessages = msg.next;
}
//message后继置为null
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.
//没有执行quit方法(主线程不能执行quit方法,否则会导致主线程退出)
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;
}
}
如果当前队列里有消息,则计算队头消息的执行时间是否大于当前时间,如果大于则计算需要阻塞的时间(nextPollTimeoutMillis),对队列进行阻塞(这种阻塞会自动进行唤醒)特定的时间,然后唤醒继续执行。小于的话则执行从队列中出队,并返回该message。如果当前对列中没有消息则会将nextPollTimeoutMillis置为-1,mBlocked =true,继续进行循环,再次进入循环就会调用nativePollOnce进行阻塞(-1时的阻塞无法自动唤醒,必须等到enqueueMessage添加入消息才能再次唤醒)在这里不难发现enqueueMessage不断的往消息列表中发送数据,next不断从消息列表中取出数据,符合生产者消费者模式。有关消息队列的说明结束,现在我们看一下Message的相关内容
Message
可以直接new Message 但是有更好的方式 Message.obtain。因为可以检查是否有可以复用的Message,用过复用避免过多的创建、销毁Message对象达到优化内存和性能的目的
public static Message obtain(Handler h) {
Message m = obtain();//调用重载的obtain方法
m.target = h;//并绑定的创建Message对象的handler
return m;
}
public static Message obtain() {
synchronized (sPoolSync) {
//sPoolSync是一个Object对象,用来同步保证线程安全
if (sPool != null) {
//sPool是就是handler dispatchMessage 后 通过recycleUnchecked 回 收用以复用的MessageMessage
m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
那么Message是如何进入到消息队列中进行分发的呢的呢?当然是通过Handler进行的。
Handler
Hander的简单使用如下:
1.创建Handler
2.发送消息
3.接受消息
class MainActivity : AppCompatActivity() {
//1.创建mandler并指定Loop和消息处理的回调函数
private val mHandler=Handler(Looper.getMainLooper()){
//4处理消息
when(it.what){
110 -> {
Log.e("handler", "${it.obj.toString()}", )
}
}
true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//2.创建消息
val obtain = Message.obtain(mHandler)
obtain.obj="啦啦啦德玛西亚"
obtain.what=110
//3.发送消息
mHandler.sendMessage(obtain)
}
}
结果打印如下:
2021-09-06 09:27:36.206 24557-24557/com.zxl.handlerdemo E/handler: 啦啦啦德玛西亚
首先看一Handler的sendMessage的方法源码
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
sendMessage方法调用了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);
}
sendMessageAtTime会将消息添加到消息队列queue中,那么消息队列从哪里获取呢,其实是从我们创建Handler时传入的Loop中获取(Loop的prepare方法中创建的消息队列)。方法最后调用了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);
}
在这个方法中将消息的target与handler进行绑定,消息的处理方法回调就需要调用handler.dispatchMessage进行调用。最后将消息加入到消息队列中。现在我们知道了Handler如何发送数据的再来看一下handler是如何接受消息的。
首先我们知道在Looper的loop方法中会将消息从队列中进行取出,并调用message.target.dispatchMessage()方法,而message中的target正是发送该消息的handler。现在我们看一下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);
}
}
如果msg自身存在回调方法则调用message的自身的回调方法,如果不存在的话如果Handler设置了消息回调的方法(创建Handler时传入的回调方法)则调用该方法,否则调用Handler默认的handleMessage方法,该方法是一个空方法实现。至此整个Handler的发送消息和消息处理的流程说明完毕。总结一下流程:
1.在主线程创建时会在Main方法中创建Looper(Loop.prepareMainLoop)并执行Loop.loop方法实现死循环,在该循环中会不断调用next方法从消息队列中取出消息并调用相关的handler的dispatchMessage方法进行消息处理(消息列表由Loop创建并由ThreadLocal保证线程内唯一)
2.调用Handler的sendMessage方法(或其他发送消息的方法),最终会调用到sendMessageAtTime方法,该方法会将需要发送的消息发送到消息队列中(消息队列从创建Handler的Loop提供)。
handler机制就是一个传送带的运转机制。
1)MessageQueue就像履带。
2)Thread就像背后的动力,就是我们通信都是基于线程而来的。
3)传送带的滚动需要一个开关给电机通电,那么就相当于我们的loop函数,而这个loop里面的for循环就会带着不断
的滚动,去轮询messageQueue
4)Message就是 我们的货物了。
以上就是Handler机制的基本内容,其他相关内容(内存共享,消息屏障,线程同步等)会在后面专门写一篇文章进行介绍。