一、四大组件
-
message:消息。
-
MessageQueue:消息队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
-
Looper:消息循环器,负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个 MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。
-
Handler:消息处理器,负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节
二、消息的循环流程
- Handler通过sendMessage()发送消息Message到消息队列MessageQueue。
- Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
- target handler调用自身的handleMessage()方法来处理Message。
三、一个线程有几个Looper?如何保证?
一个线程只有一个Looper,Looper.loop()是由线程启动的
主线程中Looper是在AMS中ActivityThread的main函数中创建的,调用Looper.prepareMainLooper(); 然后调用 prepare(),里面有一个static final的sThreadLocal去set一个Looper(Looper对象的构造函数是一个私有的,只能通过内部来创建),ThreadLocal的ThreadLocalMap,将当前线程作为key,创建的Looper作为value的键值对存储起来,并且sThreadLocal在set之前会做个检验,ThreadLocalMap有值就会抛异常,这样就保证了一个线程中只有一个Looper
1.主线程中Looper是在AMS中ActivityThread的main函数中创建的
public static void main(String[] args) { …… Looper.prepareMainLooper(); …… Looper.loop(); }
2.Looper.prepareMainLooper()中调用 prepare()
3.prepare方法中去new一个私有的Looper构造函数来创建一个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)); }
4.通过set方法将Looper作为value,当前线程作为key设置到ThreadLocalMap中
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
5.由于是主线程中调用的,通过ThreadLocalMap键值对的关系,将主线程和Looper关联起来
6.sThreadLocal是由static final修饰的,在整个app里面是唯一的,并且在prepare方法中检验sThreadLocal是否为空,不为空直接抛出异常,一个Looper只能和一个线程创建一次
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(); }
7.这样就保证了线程和Looper的 一一对应关系
四、一个线程有几个handler?
可以创建无数个Handler,但是他们使用的消息队列都是同一个,也就是同一个Looper
五、Handler内存泄漏原因?为什么其他内部类没有这个问题
创建一个内部类的handler,相当于匿名内部类,handler通过send或者post将message通过enqueueMessage()传给了MessageQueue,MessageQueue就会持有message,而在enqueueMessage方法中会将this赋值给msg的target,msg就持有了handler,而handler是一个匿名内部类,持有了外部类的对象,也就是activity,msg没有执行完,一直存在在MessageQueue里面,导致activity不能被销毁,里面的对象都不会被处理,引起了内存泄漏
解决办法:
- 将handler声明为静态类,用弱引用来持有handler (可能在用的时候activity找不到了)
- 在activity的destory里面销毁所有message
六、为何主线程中可以new handler,子线程中怎么操作
因为在ActivityThread的main函数中已经调用了Looper.prepareMainLooper();完成了对Looper的初始化,所以主线程中可以直接new handler。
七、子线程中维护的looper,消息队列无消息的时候处理方案是什么?有什么用?主线程呢?
子线程在for死循环中,调用nativePollOnce方法处于block状态,意味着Looper.loop()处于blcok状态,一直在执行,导致run方法也一直执行,也就是说子线程也就一直在执行,线程相关的内存就得不到释放,导致内存泄漏
处理方案:
调用 looper.quitSafely() -> MessageQueue.quit -> MessageQueue#removeAllMessagesLocked:把所有的消息都移除掉,并且调用nativeWake唤醒for死循环,执行到msg == null,然后退出for循环,线程也就退出了
主线程不允许退出
MessageQueue#enqueueMessage 存消息
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { 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; }
MessageQueue#quit 移除message,唤醒
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
MessageQueue#next关键代码 取消息
Message next() { …… 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; } …… }
八、既然可以存多个handler往MessageQueue中添加数据,发消息时各个handler可能处于不同线程,那它内部如何确保线程安全的?取消息呢?
MessageQueue使用final初始化的,初始化之后就不能被修改,MessageQueue只能有Looper来创建的,线程与Looper一一对应,Looper与MessageQueue一一对应,synchronized (this) 对唯一的MessageQueue进行加锁, 来保证线程安全,取消息也是一样,防止在取消息的时候添加消息。对消息的访问保证唯一性
九、使用message时如何创建?
message量大, 直接new message会导致频繁的创建和销毁, 频繁的导致GC,每一次GC都会带来STW(让所有的线程停止工作 )问题,导致卡顿,而且会产生内存碎片,导致OOM
采用obtain()来创建,采用享元模式,复用 Message ,减少内存消耗:
通过 Message 的静态方法 Message.obtain();
通过 Handler 的公有方法 handler.obtainMessage()。
private static int sPoolSize = 0; Message next; private static final Object sPoolSync = new Object(); private static Message sPool; 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(); }
十、handler的消息阻塞是怎么实现的,为什么主线程不会阻塞?
涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
每个事件都是一个message,包括service、privode、input,按键、点击事件、广播等,最终都是由handler来管理,handler没有消息处理就会block,主线程block的时候,主线程没事做了,休眠,当产生一个message时,就往MessageQueue里enqueueMessage一个Message,然后就会调用nativeWake唤醒等待的Looper,唤醒之后,queue.next这个时候就醒来
而ANR是一个消息发送出去之后,消息没有得到及时的处理,耗时超过限制(5s内没有响应输入事件,比如按键、屏幕触摸等;广播10s内没有执行完毕)才会出现ANR,报一个ANR,封装成一个message,丢给handler进行处理,触发 一个dialog
AMS管理机制:
每一个应用都存在于自己的虚拟机中,也就是说都有一个自己的main函数,
启动流程:launcher -> zygote -> art application -> activityThread -> main
所有应用的所有生命周期的函数(包括activity、service)都运行在这个Looper里面,都以message的方式存在
总结:
应用卡死ANR压根与这个Looper没有关系,应用在没有消息处理的时候,它在睡眠,释放线程;卡死是ANR,消息阻塞是睡眠
十一、子线程里弹 Toast 的正确姿势
本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可,同理的还有 Dialog。
十二、handler postDelay这个延迟是怎么实现的?
handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。