Handler 机制分析

108 阅读7分钟

首先列出几个Handler 相关的问题,如果你都知道答案,就可以不用跳过这篇文章。当然,如果文章对你有帮助,还希望给个小小的赞,鼓励下。

说明,源码分析基于andoird 9.0

  1. Looper 使用了死循环,为什么没有阻塞线程?
  2. 子线程中能不能直接new一个Handler,为什么主线程可以?
  3. 如何在子线程中使用Handler?
  4. 一个线程可以有多少个handler,多少个looper,多少个MessageQueue?
  5. MessageQueue的数据结构是怎样的,当发送延迟消息的时候,MessageQueue有什么变化?
  6. 一个线程创建了多个多个handler 时,looper 如何给到指定的handler 处理消息
  7. handler 如何进行线程切换的

第一个问题

    public static void main(String[] args) {
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
        
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
    // 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.");
        }
        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;
            }
        }       

再看下queue.next();

    
  private native void nativePollOnce(long ptr, int timeoutMillis);
  
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);   //调用ndk方法
            #省略多余代码
        }
    }  

关于第一点为什么for循环没有阻塞主线程,是因为Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

子线程中能不能直接new一个Handler,为什么主线程可以? 如果在子线程中直接new Handler(),会发生什么呢,看源码

   public Handler() {
        this(null, false);
    }
    
    public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    //再看下Looper.myLooper();
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();    //如果没有调用Looper.prepare()的话,此处会为null
    }
    
    
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");  //如果多次prepare会抛异常
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

所以第二个问题的原因就是直接new Handler()时,由于没有准备looper 所以会抛异常,那为什么主线程直接new Handler()不会抛异常呢,是因为在ActivityThread的main方法中就已经创建了主线程的looper

Looper.prepareMainLooper();

第三个问题在子线程中使用Handler,由第二点我们可以知道第一步需要准备子线程的Looper,然后才能new 子线程的Handler,最后开启Looper.loop();循环取MessageQueue中的消息就可以了

首先准备好Looper
Looper.prepare();
val handler=Handler ();
Looper.loop();
这样就能在子线程中使用Handler

第四个问题:一个线程可以有多少个handler,多少个looper,多少个MessageQueue? 我们知道android 系统中已经存在主线程的handler,来分发Activity、fraggment的生命周期调度,我们也有在主线程中new Handler(),来做一些操作,比如果hanlder做线程切换。所以一个线程中,肯定不止一个Handler. 由下面的源码分析,一个线程,Looper也只能有一个。否则会抛异常

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");  //如果多次prepare会抛异常
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

并且由于MessageQueue是在Looper中初始化的,所以MessageQueue也只能有一个

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

第四个问题,还可以延伸出,如何保证Handler怎么做到的一个线程对应一个Looper? 由上面Looper.prepare()可知,looper 是被保存在ThreadLocal中

什么是ThreadLocal? 设计的初衷是为了解决多线程编程中的资源共享问题, synchronized采取的是“以时间换空间”的策略,本质上是对关键资源上锁,让大家排队操作。 而ThreadLocal采取的是“以空间换时间”的思路, 它一个线程内部的数据存储类,通过它可以在制定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据, 对于其他线程就获取不到数据,可以保证本线程任何时间操纵的都是同一个对象。比如对于Handler,它要获取当前线程的Looper,很显然Looper的作用域就是线程,并且不同线程具有不同的Looper。 ThreadLocal本质是操作线程中ThreadLocalMap来实现本地线程变量的存储的 ThreadLocalMap是采用数组的方式来存储数据,其中key(弱引用)指向当前ThreadLocal对象,value为设的值 通过ThreadLocal计算出Hash key,通过这个ThreadLocal对象,value为设的值

MessageQueue的数据结构是怎样的,当发送延迟消息的时候,MessageQueue有什么变化

拿到问题,我们第一步想下入口是哪里入口,我们可以从handler.sendMessage()方法进行分析

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  // 如果是延迟消息,都会加上系统时间,也就是最后需要发送消息的时间
    }
    
    
    public boolean sendMessageAtTime(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);    //到了这里就是消息加入的方法了,继续往下看
    }
    
    //最终会调用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) {        //这里的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) {  //找到最后一个为null,或者当前when小于遍历的message就退出
                        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 其实不是一个队列,只是根据时间顺序组成的链表结构。 而发送延迟消息的时候,如果没有消息,则直接放入到队列,如果队列有消息,则去遍历,根据显示时间,插入该位置

关于第六点,可以看源码,handler 下的enqueueMessage,会将Massge与Handler绑定。 msg.targe=this;

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    
    Looper.class
    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(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);     //最终调用handleMessage()  ,这里就不贴了
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        }
    }            

最后一个问题,我们知道Handler经常被用来做子线程切换到主线程,然后进行Ui操作。那他是如何做操作的呢,其实很简单, 子线程通过使用主线程的Handler,发送Message 到MessageQueue中,Handler是主线程的,那发送的消息,最终处理也肯定是在主线程上,这样就达到了线程切换的目的。

总结: Handler 机制(Handler ,Message,MessageQueue,Looper)通过hander 发送消息,然后给到messageQueue。MessageQueue是一个单链表的存储结构,每发送一条消息会加入队列中。 Looper通过looper的 死循环,从MessageQueue中获取消息,并最终通过handler 的handleMessage方法进行处理

最后附上一张Handler机制的图:

image.png