源码分析: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);
        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的时间顺序排列和唤醒的方式结合实现的。

猜你喜欢

转载自blog.csdn.net/weixin_42277946/article/details/130308898