美文网首页
Handler 源码学习记录

Handler 源码学习记录

作者: younger_lei | 来源:发表于2021-11-07 12:36 被阅读0次

    此为原创,转载请注明地址

    基于Jdk1.8

    刚从事Android就开始学习handler,当时学习了原理,觉得好精妙,但是细节并没有很好的掌握,所以回过头来再次学习、总结一下。

    一、原理

    1、Looper.java里面维护了一个单链表MessageQueue,然后for循环从MessageQueue里面读取Message执行,而我们要执行的任务就存放在Message里面。
    2、有任务就取出来执行,没有任务就会阻塞,阻塞是通过调用natave方法netavePollOnce()来实现的,唤醒通过nataveWeak()实现。
    3、Handler 提交的任务都会以Message的形式存入MessageQueue,是按照执行时间顺序存入链表的,执行时间timeMillis = SystemClock.uptimeMillis() + delayMillis。

    二、学习之后的疑惑

    1、Looper、MessageQueue关系?
    2、任务延时是如何实现的
    3、handler 如何获取looper?
    4、loop()中是个死循环,主线程为什么不会卡死
    5、如何用handler检测卡顿
    6、已经有一个延时20s的任务在等待,此时再次提交一个延时5s的任务,内部流程?
    7、handler 中循环阻塞、唤醒分别是怎么实现的。
    8、主线程为什么可以直接创建handler,而其它线程却不可以呢
    9、ThreadLocal工作原理
    10、boolean handleMessage() 返回值分别代表什么

    三、详细解析

    1、 Looper初始化的时候会创建单链表MessageQueue

        // Looper.java
        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");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    
    

    2、提交延时任务后调用顺序如下,最后会调用enqueueMessage, 其中when = System.currentMills() + delayTime,将时间设置到message,并将message插入到messageQueue。

    
        public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
            return sendMessageDelayed(getPostMessage(r), delayMillis);
        }
    
    
        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;
            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);
        }; 
    
         private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    插入消息时最终会调用如下方法,有2种情况,一种是插入messageQueue第一个位置(注释1,消息队列为空、任务无延时、延时小于队列里头消息的延时), 此时会唤醒阻塞继续执行loop()中的循环;另外一种是插入messageQueue其它位置(注释2),不会唤醒阻塞。

    // MessageQueue.java
        boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
    
            synchronized (this) {
                if (msg.isInUse()) {
                    throw new IllegalStateException(msg + " This message is already in use.");
                }
    
                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) {//注释1 会唤醒阻塞
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {//注释2  不会唤醒阻塞
                    // 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;
        }
    
    

    唤醒阻塞时会走如下方法,注释3 的方法将不再阻塞。然后会将目标时间与当前时间求差值(注释4),然后继续阻塞在注释3,阻塞时间为差值,这样就实现了延时功能。

    // MessageQueue.java
    
    Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
            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);//注释3
    
                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) {//注释4
                            // 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;
                    }
    
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    
    

    3、直接调用handler.geLooper()就可以获得,mLooper在初始化handler的时候通过Looper.myLooper()赋值。

        //Handler.java
    
        public final Looper getLooper() {
            return mLooper;
        }
    
        public Handler(@Nullable Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
    
            mLooper = Looper.myLooper(); //注释1 赋值
            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;
        }  
    

    那handler中为何可以通过Looper.myLooper()来获取mLooper呢,到Looper.java类里面看一下

    
      /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    

    looper()是在初始化的时候创建MessageQueue,并且将自己存到sThreadLocal里

      //Looper.java
        @UnsupportedAppUsage
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        
        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");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        } 
    
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    
    

    4、Loop的循环和导致ANR的本质不同。
    Loop()循环:当没有消息处理时,loop()会阻塞在queue.next()方法,内部阻塞在nativePollOnce()方法,从而让线程进入休眠状态,但可以随时唤醒执行任务。
    ANR:本质是对一个事件进行计时,超时则ANR。
    输入事件没有响应:当输入时间超过5s没有响应
    广播10s:广播10s没有处理完

    5、用handler检测卡顿有2种方式,本质原理都一样,就是检测UI线程处理一个任务的时间是否超过一个阀值,当超过这个阀值就是肉眼可见的卡顿了。
    (1)Looper.loop()在处理消息前、后均会打印log,利用这个可以检测执行前后是否超过了设定的阀值,超过了就是卡顿。

    //Looper.loop()
    
     // This must be in a local variable, in case a UI event sets the logger
          final Printer logging = me.mLogging;
          if (logging != null) {
              logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
          }
    
         //to do
          msg.target.dispatchMessage(msg);//处理消息任务
    
          //to do
    
          if (logging != null) {//
              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
           }
    
    

    上面logging默认为null,需要自己设置一个printer进行打印、检测

             h.getLooper().setMessageLogging(new Printer() {
                @Override
                public void println(String x) {
                    
                }
            });
    

    (2)新开启一个线程,设置一个内部变量。循环向主线程设置一个值,循环过程中如果发现上一次没有设置成功,说明期间发生了卡顿。

    // UI thread
    int flag = 0;
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.v(TAG,"开始检测卡顿:" + Thread.currentThread().getName());
            int cur;
            while (true){
                if (flag >= Integer.MAX_VALUE)flag &= 0;
                cur = flag;
                h.post(new Runnable() {
                    @Override
                    public void run() {
                        flag += 1;
                    }
                });
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (cur == flag)Log.e(TAG,"UI发生了卡顿");
    
            }
        }
    },"observerThread").start();
         
    
    
    

    6、因为5s早于20s, 这个任务会被放入messageQueue的第一个位置,并且唤醒阻塞,阻塞被唤醒后会重新计算新的阻塞时间并再次进行阻塞。到达唤醒时间后,取出message执行。

    7、插入任务后,当满足唤醒阻塞条件时,会调用MessageQueue 中nativeWake(mPtr);
    当需要阻塞时,在messageQueue类的next()方法中会调用nativePollOnce(ptr, nextPollTimeoutMillis), nextPollTimeoutMillis阻塞时长,注释1是计算过程

    //MessageQueue.java
    Message next() {
            // Return here if the message loop has already quit and been disposed.
            // This can happen if the application tries to restart a looper after quit
            // which is not supported.
           
            // to do others
            for (;;) {
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                // to do others
    
                // 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);//注释1
                        
    
                //to do others
            }
           // to do others
    }
    
    

    8、线程里创建handler必须首先通过Looper.prepare()来创建Looper,原因如下,否则会在注释2处报错

        public Handler(@Nullable Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
    
            mLooper = Looper.myLooper();
            if (mLooper == null) { // 注释2
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    而主线程创建handler之所以没报错,是因为在线程创建之初已经创建了looper,

    //ActivityThread.java
    public static void main(String[] args) {
    
        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
    
        Looper.loop();
    
    }
    

    9、Thread.java有个变量threadLocals,即ThreadLocalMap,是ThreadLocal的一个内部类,数组结构。
    Looper.prepare()时会调用threadLocal.set()来存looper,键值为threadLocal, value为looper。在ThreadLocal.java内部会通过当前现场获取threadLocalMap,当threadLocalMap为null时则进行初始化,并将初始化后的threadLocalMap赋值到当前Thread的变量threadLocals。
    通过Looper.myLooper()获取looper时,会调用threadLocal.get()获取looper()。
    内部通过thread.threadLocals即ThreadLocalMap来取值。
    源码如下:

    //Thread.java
        ThreadLocal.ThreadLocalMap threadLocals = null;
    

    Looper有内部变量sThreadLocal, 并在初始化时将自己(Looper)存入sThreadLocal.

     //Looper.java
        @UnsupportedAppUsage
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
        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");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
    

    看一下set()方法,最终将Looper通过map.set()存入了ThreadLocalMap

    //ThreadLocal.java
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    看一下map.set()方法

    static class ThreadLocalMap {
    
          private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);//注释1  计算储存位置
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {//注释3 如果key相同,直接替换
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);//注释2  没有值的话直接存入
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    }
    
    

    存看完了,看一下怎么取Looper()

    class Looper{
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    } 
    
    class ThreadLocal{
        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();
        }
    
       ThreadLocalMap getMap(Thread t) {//直接通过thread来获取map
            return t.threadLocals;
        }
    
    }
    

    当getMap()获取到的map为null时,get()方法中会调用setInitialValue(),然后再调用createMap(),并将value设为Null;如果是set()则会直接调用createMap(t, value);

    class  ThreadLocal{
    
        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
         }
    
       void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    }
    
    

    ThreadLocalMap初始化

    class ThreadLocalMap{
          ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];//注释1 长度为16的entry数组
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
           }
        //todo others
    
    }
    
    

    2021年11月 younger

    相关文章

      网友评论

          本文标题:Handler 源码学习记录

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