Handler 有两个主要用途:
- 安排 Message 和 runnables 在将来的某个时刻执行
- 将要在不同于自己的线程上执行的操作排入队列。(在多个线程并发更新 UI 的同时保证线程安全。)
Android 规定访问 UI 只能在主线程中进行,因为 Android 的 UI 控件不是线程安全的,多线程并发访问会导致 UI 控件处于不可预期的状态。为什么系统不对 UI 控件的访问加上锁机制?缺点有两个:
- 加锁会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率。
- 如果子线程访问 UI,那么程序就会抛出异常
另外三个重要对象
Message:Handler 接收和处理的消息对象
MessageQueue:Message 的队列,先进先出,每一个线程最多可以拥有一个
Looper:消息泵,是 MessageQueue 的管理者,会不断从 MessageQueue 中取出消
息,并将消息分给对应的 Handler 处理,每个线程只有一个 Looper
Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统,需要注意的是,线程默认是没有 Looper 的,直接使用 Handler 会报错,如果需要使用 Handler 就必须为线程创建 Looper,因为默认的 UI 主线程,也就是 ActivityThread,ActivityThread 被创建的时候就会初始化 Looper,这也是在主线程中默认可以使用 Handler 的原因
源码分析
Handler
Handler()构造函数
public Handler() {
this(null, false);
}
点击this,发现调用的是另一个构造函数
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
上面代码中间部分,调用了 mLooper = Looper.myLooper()方法获取一个 Looper对象,若此时 Looper 对象为 null,则会直接抛出一个“Can't create handler inside thread that has not called Looper.prepare()”异常,那什么时候造成 mLooper 是为空呢?那就接着分析myLooper()
myLooper()方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这个方法在 sThreadLocal 变量中直接get()取出 Looper 对象,若 sThreadLocal 变量
中存在 Looper 对象,则直接返回,若不存在,则直接返回 null,而 sThreadLocal
变量是什么呢?
sThreadLocal变量
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,其他线程则无法获取。Looper、ActivityThread 以及 AMS 中都用到了ThreadLocal。它是本地线程变量,存放 Looper 对象,由这也可看出,每个线程只有存有一个 Looper 对象。再来看看同样被调用的get()方法
get()方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
当不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLcoal的索引去查找对应的value
可以看到该方法通过ThreadLocal来拿到当前线程绑定的Looper。那么 ThreadLocal在哪里和Looper绑定线程呢?通过前面的试验,我们不难猜到,应该是在 Looper.prepare()方法中,现在来看看它的源码:
prepare()方法
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));
}
由此看到,我们的判断是正确的,在 Looper.prepare()方法中给 sThreadLocal变量set()设置 Looper 对象,这样也就理解了为什么要先调用 Looper.prepare()方法,才能创建 Handler 对象,才不会导致崩溃。但是,仔细想想,为什么主线程就不用调用呢?不要急,我们接着分析一下主线程,我们查看一下 ActivityThread中的 main()方法,代码如下
main()方法
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
// Call per-process mainline module initialization.
initializeMainlineModules();
Process.setArgV0("<pre-initialized>");
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);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
上面代码第1个if语句上2行处调用了 Looper.prepareMainLooper()方法,而这个方法又会继续调用了mylooper(),走的和上述一样的流程最后到Looper.prepare()方法,代码如下:
prepareMainLooper()方法
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
分析到这里已经真相大白,主线程中 google 工程师已经自动帮我们创建了一个Looper 对象了,因而我们不再需要手动再调用 Looper.prepare()再创建,而子线程中,因为没有自动帮我们创建 Looper 对象,因此需要我们手动添加,调用方法是 Looper.prepare(),这样,我们才能正确地创建 Handler 对象。
Message
当我们正确的创建 Handler 对象后,接下来我们来了解一下怎么发送消息。具体是先创建出一个 Message对象,然后可以利用一些方法,如 setData()或者使用 arg 参数等方式来存放数据于消息中,再借助 Handler 对象将消息发送出去就可以了。
通过 Message 对象进行传递消息,在消息中添加各种数据,之后再消息通过mHandler() 进行传递,之后我们再利用 Handler 中的 handleMessage()方法将此时传递的 Message 进行捕获出来,再分析得到存储在 msg 中的数据。但是,这个流程到底是怎么样的呢?具体我们还是来分析一下源码。首先分析一下发送sendMessage()方法
sendMessage()方法
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
代码里调用 sendMessageDelayed()方法
sendMessageDelayed()方法
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
再调用sendMessageAtTime(),方法中第一个参数是指发送的消息 msg,第二个参数是指延迟多少毫秒发送
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);
}
代码第1行可以分析得出,原来消息 Message 对象是建立一个消息队列 MessageQueue, 这个对象 MessageQueue 由 mQueue 赋值,而在一开始的Handler()构造函数里我们知道 mQueue = mLooper.mQueue,而 mLooper 则是 Looper 对象,而每个线程只有一个 Looper,因此,一个 Looper 也就对应了一个 MessageQueue 对象,之后调用 enqueueMessage()直接入队操作
Handler的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);
}
方法最后return了MessageQueue的enqueueMessage()方法
MessageQueue的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.");
}
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;
}
首先要知道,源码中用 mMessages 代表当前等待处理的消息,MessageQueue 也没有使用一个集合保存所有的消息。观察中间的代码部分,队列中根据时间 when来时间排序,这个时间也就是我们传进来延迟的时间 uptimeMills 参数,之后再根据时间的顺序调用 msg.next,从而指定下一个将要处理的消息是什么。如果只是通过 sendMessageAtFrontOfQueue()方法来发送消息
sendMessageAtFrontOfQueue()方法
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
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, 0);
}
它也是直接调用 enqueueMessage()进行入队,但没有延迟时间,此时会将传递的此消息直接添加到队头处,入队操作介绍到这
接下来应该来了解一下出队操作,MessageQueue 对象是在 Looper 中赋值,因此我们可以在 Looper 类中找,来看一看 Looper.loop()
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.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
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();
}
}
代码比较多,我们只挑重要的分析一下。我们可以看到下面的代码用 for(;;)进入了一个死循环,之后不断的从 MessageQueue 对象 queue 中next()取出消息 msg,而我们不难知道,此时的 next()就是进行队列的出队方法,next()方法代码有点长,主要逻辑是判断当前的 MessageQueue 是否存在待处理的 mMessages 消息
如果有,则将这个消息出队,然后让下一个消息成为 mMessages
如果没有就进入一个阻塞状态,一直等到有新的消息入队唤醒
回看 loop()方法,在第一个try/catch/finally块中可以发现当执行 next()方法后会执行msg.target.dispatchMessage(msg)方法,而不难看出,此时 msg.target 就是Handler 对象,继续看一下 dispatchMessage()方法
dispatchMessage()方法
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
先进行判断 mCallback 是否为空,若不为空则调用 mCallback 的 handleMessage()方法,否则直接调用 handleMessage()方法,并将消息作为参数传出去。这样我们就完全一目了然,为什么我们要使用 handleMessage()来捕获我们之前传递过去的信息。
总结:Handler声明了一个Looper和ThreadLocal,Looper里实例化了一个MessageQueue。又因为Handler会调用线程绑定Looper,所以实例化Handler之前要调用Looper的prepare()方法创建Looper,通过ThreadLocal将Looper与当前线程绑定,Looper里的loop()方法就开始从MessageQueue里不断获取Message,然后调用Message的target执行dispatchMessage()方法,将消息回调到handleMessage中,完成更新UI。
初始化:
prepare()——MessageQueue——Handler()——myLooper()——sThreadLocal变量——get()
发送消息:
sendMessage()——sendMessageDelayed()——sendMessageAtTime()——Handler的enqueueMessage()——MessageQueue的enqueueMessage()
接收消息:
loop()——MessageQueue.next()——dispatchMessage()
补充贴一张图:
image.png
此篇Handler记录为个人学习路程总结,不以盈利为目的,借鉴了多篇文章及素材,在此感谢
2021.4.6更新
关于looper.loop()为什么不会造成主线程卡死
由于今日面试腾讯在此问题上没有回答到位,面后进行资料总结得出如下结论:
looper.loop()死循环进行消息的传递,当有消息的时候分发消息,没有消息的时候会进入休眠状态,与底层的Linux有关,此时会释放CPU,大部分时候都是休眠的,首先死循环是为了程序一直在执行而不退出,其次死循环里只要有消息从底层过来就可以执行,阻塞是在Linux层进行的,真正会让程序卡死的其实是looper调用Handler在主线程进行耗时操作
关于MessageQueue的Message排序顺序
其实这个在上面代码里也提到过,是根据when这个表示时间的变量来决定的,代表Message期望被分发的时间,越小就越在队列靠前
关于Handler是怎么做到进程之间切换的
Handler做线程切换是无声的,当你在线程A中创建handler的时候,同时创建了MessageQueue与Looper,Looper在A线程中调用loop()方法从MessageQueue中获取消息,当B线程调用handler发送一个message的时候,会通过dispatchMessage(msg)操作将message插入到handler对应的MessageQueue中,Looper发现有message插入到MessageQueue中,便取出message执行相应的逻辑,因为Looper.loop()是在A线程中启动的,所以则回到了A线程,达到了从B线程切换到A线程的目的。一句话总结就是两个线程共同操作MessageQueue这个共享变量达到线程间切换目的
关于为什么不建议在子线程更新UI
子线程更新UI是不安全的,多线程访问UI控件会导致界面错乱,但是子线程是可以更新UI的,可以在ViewRootImpl创建之前进行子线程UI的更新,比如在onCreate()里进行子线程更新UI
2021.4.23更新
关于Handler退出后消息队列多余消息的处理
结束循环消息队列时,需要调用Loooper的quitSafely()或quit()方法,即非安全退出和安全退出。
非安全退出其实很简单,在removeAllMessagesLocked()方法里执行,就是遍历消息队列中的消息(消息队列内部结构是链表)将所有消息队列中的消息全部回收。同时将MessageQueue中的mMessages (消息队列中的消息)通过遍历p.next的方式一个一个置为null,此时只是消息的回收。之后在Message中的next()方法中,如果mQuitting为true,该方法会直接就返回并退出。那么什么时候这个值为true呢?答案是我们调用Loooper的quitSafely()或quit()方法(也就是调用MessageQueue.quit(boolean safe))时,会将mQuitting置为true
安全退出在removeAllFutureMessagesLocked()方法里,该方法中,会判断当前消息队列中的头消息的时间是否大于当前时间,如果大于当前时间就会removeAllMessagesLocked()方法(也就是回收全部消息),反之,则回收部分消息,同时没有被回收的消息任然可以被取出执行
安全退出和非安全退出最后走进mQuitting中会调用dispose方法处理退出消息,使用native层的nativeDestroy方法
此外,如果Handler已经退出,继续发送消息依旧会进入enqueueMessage()方法,该方法内部会判断当前循环消息队里是否退出,如果已经结束,那么会将Handler发送的消息回收,同时会返回该消息没有加入消息队列的标志,即return false
关于如何在子线程创建Handler
子线程创建Handler是要先进行Looper的prepare()方法,才可以new Handler,主线程的Looper是在prepareMainLooper()方法里提供的,整个程序的入口ActivityThread里的main()方法创建了主线程的Looper
关于多个线程如何在发消息时保证线程安全
原因是在MessageQueue里加了synchronized锁
2021.5.31更新
Handler的next()方法除了会在空闲时阻塞队列还会做什么?
今天又去深究了一下Handler源码,起因是上次被问到这个问题没有答出来,只知道Handler会在没有消息时底层使用Linux的阻塞机制,除此之外还会有一个IdleHanlder,即闲时Handler机制,这是一个接口,有一个queueIdle()方法,如果返回false就代表只执行一次,如果返回true就代表可以重复执行。MessageQueue中会调用addIdleHanlder()方法将一个IdleHandler插入mIdleHandlers里,很明显这是一个ArrayList集合。总结一下就是只要消息队列为空,阻塞队列的同时也会遍历mPendingIdleHandlers数组,调用每一个IdleHandler实例的queueIdle()方法,根据返回值确定如何处理
2021.6.6更新
Message有哪几种?
同步消息,异步消息,屏障消息
其中屏障消息与同步消息的区别在于屏障消息没有target属性,用于阻挡同步消息,使得异步消息优先级更高。当然屏障是可以被移除的
Handler的内存泄漏问题
匿名内部类默认持有this(指Activity),在Message的enqueueMessage方法调用的时候,有一行msg.target = handler,所以就会有:Msg == >通过target属性持有 Handler 对象 ==> 持有Activity 对象。如果这个消息一个小时候后执行,那么msg 会一直持有Activity对象,不能被JVM回收
内存泄漏如何解决?
使用static修饰,静态内部类默认不持有外部类。或者也可以软引用和虚引用
消息过多为何不会造成内存爆满
消息池默认最大50个,并且消息在回收时会抹除details。
另外,最好的取消息方式是obtain而不是new,因为其内部用到了享元设计模式
post和send的区别
post发送Runnable对象,send发送Message
欢迎指正。
网友评论