不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
Handler机制
参考博客:
Android消息机制学习笔记
Android Handler消息机制原理最全解读(持续补充中)
Android消息机制全面解析
作用
主要是用于不同线程之间的通信。通常在子线程做完耗时操作后,通过handler切换为主线程执行UI操作。
对象
消息的发送和处理对象的控制器
消息的载体
存储消息的优先级队列
控制消息队列的循环
工作流程
- app启动时,在ActivityThread的main方法中会调用Looper.prepare()方法,初始化looper并绑定主线程。looper对象内部还维护一个MessageQueue。
- 刚开始,通过handler的sendMessage方法发送消息,然后调用MessageQueue.enqueueMessage方法向消息队列中加入消息。
- 主线程调用Looper.loop方法遍历队列,用MessageQueue.next()取出消息。
- 取出的消息不为空,那么调用msg.target.dispatchMessage()来分发消息。目标handler收到消息后,通过handler.handlerMessage()处理消息。
源码分析
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(3000);
handler.sendMessage(new Message());
}
}).start();
Handler中的方法
- 后面会发现,最终都调用了sendMessageDelayed方法
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
发现其调用了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);
}
拿到与handler关联的Looper中的消息队列,然后调用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);
}
将消息与它的目标handler关联,然后执行MessageQueue中的enqueueMessage方法,将消息插入队列。
MessageQueue中的方法
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) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
在消息队列中加入消息前,需要判断:
- 这条消息是否有关联的handler。
- 这条消息是否已经被使用。
- 这条消息所关联的handler所在的线程是否存活。
当这三个条件都满足的时候,才可以插入消息,否则报错。
插入消息也有两种情况:
- 在表头插入消息
当这个消息队列为空;或者when等于0,意味着立刻执行;或者需要插入的消息的when小于表头消息的when,那么就在表头插入这条消息。
- 在适当位置插入消息
适当位置插入,那么就得先找到这个适当位置。从头开始,死循环遍历消息队列,当到了消息队列的最后或者找到了一个消息的when大于这条消息的when,那么就在这个消息的前面插入即可。
过渡
通过handler发送了消息,并且也通过MessageQueue插入消息到消息队列中了,接下来,就该从队列中取出消息进行处理了,所以需要循环消息队列,需要Looper的loop方法。
Looper中的方法
- Looper的作用就是循环消息队列,看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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
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();
}private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
textView.setText("对UI进行操作");
}
};
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)) {
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
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();
}
}
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;
......
for (;;) {
Message msg = queue.next();
if (msg == null) {
return;
}
......
try {
msg.target.dispatchMessage(msg);
......
msg.recycleUnchecked();
}
}
回到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);
}
}
从源码中可以看出,若是有Callback接口,那么就会直接回调handleCallback方法。没有的话,只能调用自定义的handleMessage方法去处理消息了。
补充
Handler中的内存泄露问题
出现原因
- 非静态内部类导致的内存泄漏。
- MessageQueue中还有没有处理完的message,message的target即handler对Activity仍然持有引用,会导致内存泄漏。
解决方案
- 使用静态内部类 + 弱引用解决。
- 在销毁Activity时,清除其绑定的looper中维护的MessageQueue中的所有消息。
Handler中的Message对象的获取问题
问题:在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?
答:使用obtain创建对象是从消息池中取出Message对象,而new一个新对象,需要为其单独分配一块新的内存,这样可以节省内存的开销。
注意点
-
一个线程只能有一个Looper,内部维护着一个唯一的MessageQueue,但是可以有多个Handler。 -
创建的Looper都会保存在ThreadLocal(线程本地存储区)中。它不是线程,是一个内部存储类。作用是获取当前线程的looper。它可以在不同线程间互不干扰的存储和提供数据。 -
MessageQueue是一个时间优先级队列,但其内部是使用单链表的形式存储,根据时间,将消息插入队列中,表头的先出。 -
通过Looper取出消息,然后通过handler分发消息到handler所在的线程,即进行了不同的线程间通信。
面试问题
-
为什么一个线程只有一个Looper、只有一个MessageQueue? -
如何获取当前线程的Looper?是怎么实现的?(理解ThreadLocal) -
是不是任何线程都可以实例化Handler?有没有什么约束条件? -
Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR? -
消息循环(死循环)的必要性: -
looper.looper()阻塞会不会消耗大量的cpu资源 -
Handler.sendMessageDelayed()怎么实现延迟的?结合Looper.loop()循环中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。 -
sendMessageDelayed是如何实现延时发送消息的? -
sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?
|