美文网首页
Handler源码分析

Handler源码分析

作者: NengLee | 来源:发表于2022-11-17 16:50 被阅读0次

    HandlerAndroid os 体系占举头轻重地位,关于一些用法,我想闭着眼睛各位都会写,那么关于源码是要分析的重点,Handler、Looper、MessageQueue、Message同步消息异步消息

    Handler采用生产者-消费者模型,Handler就是生产者,Looper则是消费者具体实现给予dispatchMessage ,当然还少不了队列MessageQueue ,队列中的具体对象就是Message

    handler流程图.png

    发送消息

    handler发送消息有俩种sendMessagexxx()postxxx() ,但是都会调用enqueueMessage

    Handler.java
    
    //post...
    public final boolean post(@NonNull Runnable r) {
         return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
         return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
    //....
    
    //sendMessage...
    public final boolean sendMessage(@NonNull Message msg) {
         return sendMessageDelayed(msg, 0);
    }
        
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
          if (delayMillis < 0) {
              delayMillis = 0;
          }
          return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        //..
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    
    
    //重点❤ 队列、消息体、插入延迟时间毫秒
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        //默认变量 在handler() 产生 基础创建默认为 false
        if (mAsynchronous) {
           //重要❤ 重要的标识, 区分同步/异步
            msg.setAsynchronous(true);
        }
        //重要❤,入队
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    可以看到最终都会调用enqueueMessage

    MessageQueue queue: 通过链表形式对Message 进行存储,并通过when 的大小对 Message 进行排序。

    Message msg:具体消息对象,long when时间、Handler target发送者、Message next单链表指针

    long uptimeMillis: 插入延迟时间、默认0当前

    重点❤mAsynchronous

    标识区分当前发送的消息是属于什么类型,同步消息 True 或者 异步消息 False

    下面在looper具体分析....

    消息插入

    MessageQueue.java
        
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //保证多线程入队先加锁
        synchronized (this) {
            //....
            msg.markInUse(); //标记正在使用
            msg.when = when; // when 属性
            Message p = mMessages; //拿到链表头部的消息
            boolean needWake;
            //重点❤ 满足以下3种情况之一就把msg插入到链表头部
            //1.队列为null
            //2.当前时间没有延迟0
            //3.插入的时间比链表的对象时间早
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; //如果处于阻塞状态,需要唤醒
            } else {
                //重点❤ 唤醒标识 ,
                //4.如果p != null且msg并不是最早触发的,就在链表中找一个位置把msg插进去
                //5.如果处于阻塞状态,并且链表头部是一个同步屏障(target为null的Message),并且插入消息是最早的异步消息,需要唤醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    //当遍历到队尾、或者是  msg的时间比当前时间更早 
                    if (p == null || when < p.when) {
                        break;
                    }
                    //发现了异步消息的存在,不需要唤醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //单链表、插入msg信息
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            // 唤醒 Native 中的 MessageQueue线程
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    

    总结

    enqueueMessage有返回值,插入是否成功,true表示插入成功,false表示插入失败

    同步屏障就是 msg.target=null 即 handler=nullMessage

    1. 插入链表的头部

      • Message触发根据时间when判断,越早触发越会向前排列,当生产者存入消息时,message没有携带hander 或者是 异步消息时间最早就会优先排列
    2. 插入链表中部

      • 如果插入的消息不是空,且插入的消息不是最早有一定的延迟,就会遍历MessageQueue队列找到合适的位置,保证队列的执行顺序有序
    3. 判断是否调用nativeWake方法

      根据状态值是否为true,来决定是否调用nativeWake方法唤醒当前的线程的MessageQueue

      • 如果插入的消息在链表的头部,needWake = true 表示此时nativePollOnce方法进入阻塞状态,等待被唤醒返回
      • 如果插入的消息在链表的中部,如果链表头是一个消息屏障,同时插入的是一个最早的异步消息,需要唤醒。

    消息轮动

    1、Looper的创建

    在Android中应用的程序入口是**ActivityThread.java -- main **方法,而应用消息的循环也是在这里创建,参考源码

    public static void main(String[] args) {
           
        //创建消息循环Looper
        Looper.prepareMainLooper();
        //....
        //执行消息循环
        Looper.loop();
        
    }
    
    Looper.java 
    
    @Deprecated
    public static void prepareMainLooper() {
        //创建不允许退出Looper
        prepare(false);
        //同步锁保证只有一个线程
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //将创建的looper实例赋值给sMainLooper,其专门为UI线程保留,只要某个线程调用了prepareMainLooper方法
            sMainLooper = myLooper();
        }
    }
    
    
    /**
    *quitAllowed 表示是否允许Looper运行时退出
    **/
    private static void prepare(boolean quitAllowed) {
        //Looper只能执行一次
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //把创建Looper实例,并且把实例looper存放到TLS中
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    
    // 获取当前线程TLS区域的Looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    可以看到,线程调用了prepareMainLooper,就代表它成了一个UI线程,其内部调用的prepare()来创建Looper实例,当要获取创建Looper实例时,是通过myLooper拿到,也是就get - ThreadLocal

    2、ThreadLocal创建

    线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有本地存储区域,不同线程之间彼此不能访问对方的TLS区域

    public class ThreadLocal<T> {
        //有省略...
        
        //获取当前线程TLS区域的数据
        public T get() {
            Thread t = Thread.currentThread();
            //用了Map存放
            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();
        }
    
        //将value存储到当前线程的TLS区域。
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
        
        
        //获取内部内ThreadLocalMap
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
       //Map集合
        static class ThreadLocalMap {
         
            private static final int INITIAL_CAPACITY = 16;
    
            private Entry[] table;
            
            //有省略..
            
            private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                // Android-changed: Use refersTo()
                if (e != null && e.refersTo(key))
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    
    
            private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
            
        }
    }
    

    可以看到类型T泛型,所以这里存储是Looper泛型,sThreadLocalget() / set()操作的类型都是Looper类型

    ThreadLocal的作用:每个线程只能保存一个Looper,不同的线程Looper是不同的,这样通过调用Looper.perpare(false),UI线程中就保存了它对应的唯一的Looper实例

    当在UI线程中调用Looper.myLooper就返回了UI线程关联的Looper实例

    子线程中如果想获取UI线程关联的Looper实例,就需要调用getMainLooper方法

    区别:ui线程的looper是不允许退出,而我们一般创建的looper是允许退出

    Looper.java
    
    public static void prepareMainLooper() {
        //不允許
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
        
    public static void prepare() {
        //允许
        prepare(true);
    }
    
    

    3、MessageQueue创建

    在调用Looperprepare方法就会创建Looper的实例,同时会把Looper实例通过ThreadLoca保存到线程中,在创建Looper时还会同时创建MessageQueue

    Looper.java
        
    private Looper(boolean quitAllowed) {
        //创建MessageQueue对象, * 是否退出传递进去
        mQueue = new MessageQueue(quitAllowed);
        //获取当前线程
        mThread = Thread.currentThread();
    }
    
    
    MessageQueue.java
     
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        //native层init,返回mPtr指针通信
        mPtr = nativeInit();
    }
    

    MessageQueue的构造中通过Native方法在native层创建一个属于NativeMeesageQueue的消息队列,然后把NativMessageQueue的地方返回到Java层保存在mPtr中,java层与native层之间通信就是通过mPtr指针

    Looper.java
        
    //获取meesageQueue对象
    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }
    

    通过LoopermyQueue方法就能获取到它关联的MessageQueue实例

    4、消息轮询

    可以看到在UI线程中,在ActivityThread的mian方法在创建Looper后,通过Looper.loop方法就启动了消息循环,这个函数会不断的从MessageQueue中取出消息、处理消息

    Looper.java
    
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    public static void loop() {
        //获取looper
        final Looper me = myLooper();
        //...
        for (;;) {
            //返回false 退出循环
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
    
    
    
    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {
        //重点❤ 从Looper中取出MessageQueue
        Message msg = me.mQueue.next(); 
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }
        //....
        try {
            //重点❤   Message.target 就是对应的Handler.dispatchMessage回调消息
            msg.target.dispatchMessage(msg);
        }
        //....
        //重点❤,回收消息
        msg.recycleUnchecked();
    
        return true;
    }
    

    可以看出在Loop()中是一个死循环,通过looper.mQueue.next()来获取出最新的消息,当没有消息返回false进行跳出循环条件

    Looper.java
    
    public void quit() {
        mQueue.quit(false);
    }
    
    //quitSafely和quit方法的区别是,quitSafely方法会等MessageQueue中所有的消息处理完后才退出,而quit会直接退出
    public void quitSafely() {
        mQueue.quit(true);
    }
    

    Looperquit()quitSafely()被调用,从而调用MessageQueuequit()来通知消息队列退出

    5、出队列

    MessageQueue的 next()是最关键的函数

    @UnsupportedAppUsage
    Message next() {
        //mPtr是在构造中被赋值,是指向native层的MessageQueue
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
    
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        /**
        *重点❤  死循环,里面分为两部分:
        * 1、处理java层消息
        * 2、如果没有消息处理,执行IdleHandler
        **/
        for (;;) {
            
            //处理native层消息,是一个阻塞操作  等待nextPollTimeoutMillis时间后唤醒, messageQueue被唤醒
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //mMessages表示取出java层队列的第一个消息
                Message msg = mMessages;
                //遇到同步屏障  msg.target为null的消息
                if (msg != null && msg.target == null) {
                    //在do-while中找到异步消息,优先处理异步消息,其中异步消息时候,isAsynchronous = true
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                //有消息
                if (msg != null) {
                    //消息还没到触发时间
                    if (now < msg.when) {
                        //设置下一次轮询的超时时长(等待时长)
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //消息到达触发时间
                        mBlocked = false;
                        //从mMessages的头部获取一条消息并返回 
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        //设置消息的使用状态,即flags |= FLAG_IN_US
                        msg.markInUse();
                         //返回这个消息
                        return msg;
                    }
                } else {
                //msg == null,没有消息
                    //设置nextPollTimeoutMillis为-1,准备进入阻塞,等待MessageQueue被唤醒
                    nextPollTimeoutMillis = -1;
                }
    
               //调用了quit方法
                if (mQuitting) {
                    dispose();
                    return null;
                }
    
                
                if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    //没有IdleHandler需要处理,可直接进入阻塞
                    mBlocked = true;
                    continue;
                }
                
                //有IdleHandler需要处理
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                
                //把mIdleHandlers列表转成mPendingIdleHandlers数组
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
    
    
            //遍历mPendingIdleHandlers数组
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                //取出IdleHandler
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                boolean keep = false;
                try {
                    //执行IdleHandler的queueIdle方法,通过返回值由自己决定是否保持存活状态
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
    
                if (!keep) {
                     // 不需要存活,移除
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
    
            //重置pendingIdleHandlerCount和nextPollTimeoutMillis为0
            pendingIdleHandlerCount = 0;
            //当=0:表示下一次循环会马上从Messages中取出一个Message判断是否到达执行时间
            nextPollTimeoutMillis = 0;
        }
    }
    
    消息处理整理:
    • Part1先执行nativePollOnce() ,组赛队列,其中的nextPollTimeoutMillis表示下一次等待的超时时长,当nextPollTimeoutMillis = 0或者达到对应的时间值,那么它就会立即返回
    • nextPollTimeoutMillis = -1 表示在队列MessageQueue中没有消息,会一直等待下去,直到Hanlder发送消息到队列中,执行nativeWake()后,MessageQueue被唤醒,nativePollOnce就会返回,但它此时并不是立即返回,会先处理native层的消息后,再返回,然后获取java层的消息处理
    • nex()会从mMessages链表的表头中获取一个消息,首先判断它是否同步屏障,区分条件为msg.target = null 的 Message

      • 同步屏障:优先处理异步消息,异步消息在执行顺序上会比同步消息优先执行Message.setAsynchronous=true 设置异步消息,常用的消息为同步消息
    • 执行时间if(now == msg.when) 如果达到执行数据,next()就会返回这条消息给Looper处理,并且从链表中删除,如果没有到执行时间就设置nextPollTimeoutMillis 时间等待时长

    总结

    1. 创建Looper时候,只能通过Looperprepare()方法创建,在创建Looper时会内部创建一个MessageQueue,并且把Looper保存在线程的ThreadLocal中对应(Map),一个线程只能对应一个Looper,一个Looper只能对应一个MessageQueue
    1. 在创建MessageQueue时候,MessageQueueNativeMessageQueue建立连接,NativeMessageQueue存储地址位于MessageQueue的mPtr字段中,java层与native通过mPtr字段进行通信。
    1. ThradLocal的作用,Looper属于某个线程,而MessageQueue存储在Looper中,所以MessageQueue通过Looper特定的线程上关联,而Handler在构造中又与LooperMessageQueue相互关联,通过Handler发送消息的时候,消息就会被插入到Handler关联的MessageQueue中,而Looper会不断的轮询消息,从MessageQueue中取出消息给相应的Handler处理,所有最终通过Handler发送的消息就会被执行到Looper所在的线程上,这就是 Handler线程切换的原理,无论发送消息Handler对象处于什么线程,最终处理消息都是在Looper所在的线程。
    1. LooperMessageQueue中取出消息后,会交给当前消息的msg.target(Hanlder)处理,在Handler中处理消息的回调优先级为:Message的CallBack > Handler的CallBack > Handler的handleMessage方法
    1. APP在启动的时候ActivityThread.main()方法中的Looper.prepareMainLooper()中已经调用Looper.prepare(false),所以在主线创建Handler无需我们手动调用Looper.prepare(),而在子线程中,如果我们不传递UI线程所属的Looper去创建Handler,那么就需要调用Looper.prepare()后再创建Handler传递消息,主要是因为 Handler 要和某个线程中的MessageQueue 和 Looper 关联,只有调用 Looper.prepare()方法,Looper和MessageQueue才属于某个线程。
    1. 消息池是一个单链表,复用池Message时候,从头部取出,如果取不到,则新建返回,回收Message时候,也是从头插入。

    相关文章

      网友评论

          本文标题:Handler源码分析

          本文链接:https://www.haomeiwen.com/subject/xyppxdtx.html