Android面试(五):异步消息机制之Handler面试你所需
1. 什么是Handler,为什么要有Handler?
Android中主线程也叫UI线程,主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,文件处理、多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢失帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱(1.锁机制会让UI处理逻辑变得混乱;2.锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行),Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。
2. Handler的使用方法:
- post(runnable)
- sendMessage(message)
其实post(runnable)和sendMessage(message)最终底层都是调用了sendMessageAtTime方法。
3. Handler消息机制的原理:
在主线程中Android默认已经调用了Looper.preperMainLooper方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中(ThreadLocal)。当调用Handler的sendMessage方法的时候,就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。主线程默认执行了Looper.looper()方法,该方法从Looper的成员变量MessageQueue队列中调用Message的target对象的dispatchMessage方法(也就是msg.target.dispatchMessage方法)取出Message,然后在dispatchMessage方法中调用Message的target对象的handleMessage()方法(也就是msg.target.handleMessag),这样就完成了整个消息机制。
我们从源码的角度来分析上述原理,首先我们来看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());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
我们发现Looper.myLooper()内部是通过sThreadLocal.get()获得Looper的,(关于ThreadLocal:不同的线程访问同一个ThreadLocal,不管是get方法还是set方法对其所做的操作,仅限于各自线程内部。这就是为什么用ThreadLocal来保存Looper,因为这样才能使每一个线程有单独唯一的Looper。)我们可能会想,这是通过get方法获得Looper,那么何时set的呢?
当我们观察Looper这个类,发现有一个方法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();
}
}
在该方法的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));
}
哦,原来set方法是在这里调用的哈,接下来我们还剩下一个疑问,那就是prepareMainLooper是在哪里调用的呢?其实Android主线程对应一个类ActivityThread,而每个Android应用程序都是从该类的main函数开始的:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
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");
}
我们可以看到,Looper.prepareMainLooper就是在这里调用的,首先程序是从这个main开始的,依次调用了prepareMainLooper ——> prepare ——> sThreadLocal.set,是不是有种茅塞顿开的感觉呢?我们继续看这个main函数,内部调用了Looper.loop,这是Handler机制很重要的一个方法,这也是为什么我们经常说Android主线程默认给我们调用了Looper.prepare和Looper.looper的原因。
接下来我们再来看我们手动调用了handler.sendMessage或者handler.postRunnable方法,默认底层都是调用handler.sendMessageAtTime,该方法内部调用了enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我们可以看到这里给msg.target指定了一个this对象,其实这个this就是Handler对象(因为这是在Handler类中啊,又不是内部类其它的),我们继续看到queue.enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) {
...
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//插入消息到链表的头部:如果当前MessageQueue消息队列为空,或者插入的消息触发时间为0,
//亦或是插入消息的触发时间小于现有头结点的触发时间
// 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;
}
通过调用此方法将消息发送到MessageQueue消息队列中,(这里我一直存在一个问题,这里明明做了触发时间相关的排序,并不符合队列的先进先出的特点,可为什么一直叫做消息队列,就连用的类名翻译过来也是如此,而不是链表呢?还是说这是广义上的队列?如果有知道的大牛,可以跟我说说!!)
刚刚说过Android主线程,也就是ActivityThread的main函数会调用Looper.loop方法:
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
...
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
loop方法中调用了queue.next()方法:next()方法是一个无线循环的方法,如果消息队列中没有消息,那么next()方法会一直阻塞,当有新消息到来时,next()方法会将这条消息返回同时也将这条消息从链表中删除。我们主要再来看msg.target.dispatchMessage方法,从上面的分析可以知道msg.target其实就是Handler对象,我们找到dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其中调用了handleMessage,这也就是我们创建Handler类或其子类,所需要重写的handleMessage方法。至此,整个Handler消息机制就走通了,面试的时候,我们只需要说上面的原理,看源码主要是为了更深入的了解,而不是简单的记忆、背诵,要在理解的基础上去记。
4. Handler引起的内存泄漏以及解决办法
原因:
非静态内部类持有外部类的强引用,导致外部Activity无法释放。
解决办法:
1.handler内部持有外部activity的弱引用
2.把handler改为静态内部类
3.mHandler.removeCallbacksAndMessage(尤其针对延时消息)
5. Handler相关的问题:
(1) Looper死循环为什么不会导致应用卡死?
对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生 ANR,looper.loop 本身不会导致应用卡死。
(2) 主线程的死循环一直运行是不是特别消耗 CPU 资源呢?
其实不然,这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
(3) 子线程中Toast、showDialog问题
Toast 本质是通过 window 显示和绘制的(操作的是 window),而子线程不能更新UI 是因为 ViewRootImpl 的 checkThread方法 在 Activity 维护 View树 的行为。
Dialog 亦是如此。
参考链接:Android 消息机制——你真的了解Handler?
喜欢本篇博客的简友们,就请来一波点赞,您的每一次关注,将成为我前进的动力,谢谢!