深入理解Handler

Handler介绍

Handler机制包含消息机制和线程切换机制

Handler

Handler是整个Handler线程切换机制的入口,核心构造方法如下:

public Handler(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());
}
}
//获取创建Handle线程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//获取Looper持有的MessageQueue
mQueue = mLooper.mQueue;
//Handler内部定义的Callback
mCallback = callback;
//是否异步
mAsynchronous = async;
}

作为Handler机制的入口,Handler的构造方法里涉及了几个关键类:

Callback

image-20200423143150550

定义在Handler内部的接口,创建Handler时可以通过实现此接口来处理Message,优先级高于Handler的handleMessage

Looper

Looper用于不断的读取消息并发送给线程处理,直到消息被处理完毕。先来看看内部成员变量:

可以看到,Looper内部需要持有一个Thread和MessageQueue的引用,还有一个静态Looper对象sMainLooper以及静态ThreadLoacl对象sThreadLocal,用于线程存储与获取Looper.再看Looper的一些重要方法:

//prepare方法用于为当前线程初始化一个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));//存储Looper进sThreadLocal
}
//为主线成prepare一个Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//sMainLooper就是主线程的Looper
}
}

//获取sMainLooper
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

MessageQueue

单链表实现的消息队列,用于管理和分发Message,底层由native实现:

MessageQueue

Message

单链表实现的Message有以下几个比较关键的变量

Message
  • when处理message的时间 主要用于处理延时消息
  • target对象就是发送Message的Handler
  • callback对象是Message的一个构造方法传入的
  • next是链表头的下一个Message对象
  • sPoolSync对象用于同步锁操作Message回收池
  • sPool是Message回收池单链表队头指针(Message Pool也是使用单链表来实现的)
  • MAX_POOL_SIZE为Message Pool的最大容量

Message的创建:

通常我们都会推荐一下两种方式来获取一个Message对象

Message msg1 = Message.obtain();
//或
Message msg2 = Handler.obtainMessage();

实际上以上两种方式最终调用到了这里:

//方式1
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();
}
//方式2
//Handler.class
public final Message obtainMessage()
{
return Message.obtain(this);
}
//Message.class
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;

return m;
}

两种方式最终都是使用Message的obtain方法来获取一个Message对象:会先从回收池表头获取一个Message否则会new一个;方式2只是会将创建Message的Handler赋给获取的Message对象

Message的回收

Message.class

Message的回收是由recycleUnchecked方法来实现的,回收逻辑是当Message Pool容量未满时将待回收的Message放在表头,同时更新回收池内容量

消息机制

消息机制是指Looper不断的从MessageQueue中获取消息并分发给相应的执行线程去处理的机制,我们的Android应用的运行就需要消息机制的支持,其中还会涉及到线程阻塞和线程唤醒等问题的处理

消息机制的动力来源

MessageQueue的Message不断的被读取分发处理是由Looper的loop方法来完成的:

public static void loop() {
final Looper me = myLooper();
//Looper不能为空 需要调用prepare方法创建Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//获取Looper持有的MessageQueue

// 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;
//动力来源:死循环 不断读取消息队列的message
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);
}

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;
//调用Handler的dispatchMessage方法
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
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();
}
}

这一长段代码总结三点:

  • Looper.loop会开启死循环
  • MessageQueue#next方法获取消息,消息为空时,跳出死循环
  • Message会由创建message的Handler调用dispatchMessage方法

Handler#dispatchMessage:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

对与Message的处理优先级是Message设置的Runnable > Handler的Callback > Handler#handleMessage

loop方法是一个for死循环,在获取MessageQueue里的Message时,注释写着might block,看看该方法实现:

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;
}
}

面试题

Android消息机制

是什么

Android 的消息机制主要是指 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工作流程。消息的处理流程是这样的:

通过 HandlersendMessagepostMessage 系列方法发送一条消息
这条消息插入到 MessageQueue 中的指定位置
Looper.loop() 不断的调用 MessageQueue.next() 取出当前需要处理的消息,若当前无消息则阻塞到有新消息
取出来的消息,通过 Handler.dispatchMessage 进行分发处理
处理线程为Handler实例化时所在的线程;也可以通过构造函数传入一个 Looper ,则处理线程为 Looper 所在的线程
通常使用Looper.getMainLooper()获取主线程的Looper对象,以实现子线程到UI线程的切换

延时消息

原理是什么

发送的消息 Message 有一个属性 when 用来记录这个消息被处理的时间,when 的值:普通消息的处理时间是当前时间;而延时消息的处理时间是当前时间 + delay 的时间。Message 会按 Message.when 递增插入到 MessageQueue ,也就是越早时间的排在越前面。

使用单链表原因

结构简单易于维护,单链表在插入和删除上比较有优势

有没有问题 如何解决

会造成内存泄漏,具体原因是因为,Thread持有Looper的引用,Looper持有MessageQueue的引用,MessageQueue中含有Message,则MessageQueue持有Message的引用,Message持有Handler的引用,若此Handler以匿名内部类或非静态内部类的方式实现,则这个Handler会持有Activity(外部类)的引用。当我们的Activity要被销毁,但此时MessageQueue中还有之前添加的延迟消息还没有被执行,这样就会形成一个引用链,当这个Thread没有被销毁,或这个Thread为主线程的时候,那么Activity就会被这个引用链持有不能被销毁释放内存,造成内存泄露
(匿名)内部类为什么会持有外部类的引用
编译器会为内部类自动生成一个类,类名为外部类名加内部类名,中间用“$”连接。这个生成类的构造函数会传入一个外部类的引用,所以(匿名)内部类会持有外部类的引用

内存泄漏如何解决

处理Handler与Activity的引用
将(匿名)内部类改为静态内部类
将外部类与(静态)内部类使用弱引用连接,在内存不足时可以被回收
处理MessageQueue与Message的引用
ActivityonDestory方法中清空持有的HandlerMessageQueue中的未执行的消息。可以调用Handler.removeCallbacksAndMessages(Object token)方法,如果传入的tokennull,则会清除所有的callbackMessage(包括执行中的)
为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用方案1解决内存泄露问题,即静态内部类 + 弱引用的方式

消息分发

Looper.loop() 不断调用 MessageQueue.next() 取出当前需要处理的消息,而 next() 方法会判断队头消息的 Message.when ,如果时间还没到,就休眠到指定时间;如果当前时间已经到了,就返回这个消息,交给 Handler调用dispatchMessage方法去分发。
消息分发是如何执行的
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// callback 的值为通过 Handler.post(Runnable) 方法发送消息时传入的 Runnable
message.callback.run();
} else {
if (mCallback != null) {
// 这个 mCallback 是调用在 Handler 构造函数时可选传入的
// 传入 CallBack 就省得为了重载 handleMessage 而新写一个 Handler 的子类
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后就是交给 Handler 自己处理 Message 啦
handleMessage(msg);
}
}
如果Messagecallback不为null的话,当且仅当执行callback中的内容;否则HandlermCallback不为空的,会执行mCallbackhandleMessage方法,否则执行Handler.handleMessage方法。另外如果mCallback.handleMessage方法返回false的话,也会执行HandlerhandleMessage方法。所以这边可以通过反射去 hook 一个 Handler ,可以监听 Handler 处理的每个消息,也可以改 msg 里面的值。

同步屏障

「同步屏障」机制实现「异步消息优先执行」的功能
同步屏障也是Message,但target等于null,因为直接在MessageQueue内部操作链表,没有执行Handler.enqueueMessage()方法,target字段不会被赋值
MessageQueuepostSyncBarrier()方法用于创建一个同步屏障,removeSyncBarrier()方法用于移除同步屏障
同步屏障会按照when的值插入到合适的位置,当同步屏障处于表头的时候,会遍历并处理其后的所有异步消息,以达到过滤同步消息,优先处理异步消息的作用

异步消息

Handler构造方法中传入async参数,设置为true,使用此Handler添加的Message都是异步的(该构造函数被@hide标记)
创建Message对象时,直接调用setAsynchronous(true)
异步消息在同步屏障的配合下能够优先被执行

主线程死循环 ANR问题

什么是ANR及其产生

主线程为什么不会ANR

为什么使用epoll

IdleHandler

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类, 通过他可以在指定的线程中存储数据. 数据存储以后, 只能在指定线程中可以获取到存储的数据. 而其他线程无法获取.
Author: zhf
Link: http://yoursite.com/2019/07/30/深入理解Handler/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶