> 文章列表 > 源码分析:Handler机制

源码分析:Handler机制

源码分析:Handler机制

一、四大组件

  • 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,并隐藏背后实现的细节

二、消息的循环流程

  1.  Handler通过sendMessage()发送消息Message到消息队列MessageQueue。
  2. Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。
  3. 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);elsecreateMap(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.nextprev.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 flagsPoolSize--;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的时间顺序排列和唤醒的方式结合实现的。