zoukankan      html  css  js  c++  java
  • Android

    Handler的主要作用是收发消息和切线程

    功能一:收发消息

    简单流程介绍

    希望你看完这篇文章后也可以把流程自己讲出来,并且每个环节还可以讲出很多细节

    他的消息机制离不开Looper、MessageQueue

    • 其中 Looper 每个线程只能持有一个,主要负责循环查看 MessageQueue 里面是否有 msg 需要处理,并将需要处理的消息取出,交给 Handler
    • MessageQueue 是负责存放消息的,数据结构是一个单链表,这样就可以方便地插入或删除 msg

    具体流程一般是:

    1. Handler 发送一条msg => 本质是向MessageQueue里插入一条msg,插入时候的依据是msg.when => SystemClock.uptimeMillis() + delayMillis

    2. 这条msgMessageQueue.next()返回并交给Handler去处理
      next()会在有同步屏障(msg.target==null)的时候遍历查找并返回最早的异步消息,并在移除屏障后,从头取出并返回消息

    3. Handler.dispatchMessage(msg)会优先处理msg.callback,如果msg.callback为空,就处理Handler.mCallback,然后处理是msg本身
      msg.callback是在调用Handler.post(Runnable)时,里面的Runnable(runOnUIThreadview.post(Runnable)也用的是Handler.post(Runnable)Runnable是一样的)

      这是在不新增Handler的情况下,另一种调用Handler的方式(如下)

    class MyHandlerCallBack: Handler.Callback {
      override fun handleMessage(msg: Message?): Boolean {
        TODO("Not yet implemented")
      }
    }
    

    可以看到他也有handleMessage这个方法

    Looper是个死循环

    (1)死循环的目的

    目的就是让主线程一直卡在这个死循环里面

    因为Looper的作用就是在这个死循环里面取出消息,然后交给Handler处理

    Android的生命周期,你了解的onCreate,onStop,onStart...... 等等都是由Handler来处理的,都是在这个死循环里面运行的

    所以什么Looper死循环卡死主线程怎么办???

    必须给我卡住!!!不卡住的话,消息就没法整了!!!

    看下Android启动的时候的源码
    Activitythread.java >> main()

    public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
            ...
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            ...
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    想想写java的时候,main最后一行执行完了,不就彻底玩完了嘛!!!

    (2)死循环里干了啥

    其实想都不用想,一直在看MessageQueue里面有没有消息呗,太简单了!调用的就是MessageQueue.next()

    看下源码 MessageQueue.java >> loop()

           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);
                    if (observer != null) {
                        observer.messageDispatched(token, msg);
                    }
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } catch (Exception exception) {
                    if (observer != null) {
                        observer.dispatchingThrewException(token, msg, exception);
                    }
                    throw exception;
                } finally {
                    ThreadLocalWorkSource.restore(origWorkSource);
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                ...
            }
    

    很简单,next()返回Messagemsg.target.dispatchMessage() 处理Message

    但是队列里没消息就会返回null,这是错误的!!!具体往下看

    MessageQueue是个单链表

    1.插队

    Handler发消息的时候,目的就是对msg经过一系列操作,最终也只是调用enqueueMessage插入队列而已

    看下源码 Handler>>enqueueMessage()

        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);
        }
    

    return直接调用Message的插入队列方法

    2.出队

    出队就是next()方法,之前已经见过了

    (1)时间顺序

    Message是按时间排序的,也就是msg.when => SystemClock.uptimeMillis() + delayMillis

    msg.whenMessage期望被处理的时间

    SystemClock.uptimeMillis()是开机到现在的时间,delayMills是延迟时间,这个在sendMessageDelayed方法里直接可以直接传参

    next()就是按照时间顺序处理MessageQueue里面的消息的

    但是next()里有个概念叫 同步屏障

    (2)同步屏障

    同步屏障,就是说,平时MessageQueue都是处理同步消息,也就是按顺序来,一个个出队

    同步屏障就是阻挡同步消息的意思

    就是msg.target == null 的时候,MessageQueue就会去找msg.isAsynchronous()返回truemsg

    isAsynchronous,没错 ! 这是异步消息,就是优先级很高,需要立刻执行的消息,比如:更新View

    (3)阻塞

    值得注意的是,讲Looper的时候,源码next()后面官方给我们注释了 // might block可能阻塞,也就是说可能这个next()也许会执行好久

    next()会阻塞?,什么时候阻塞?

    now < msg.when也就是时间还没到,期望时间大于现在的时间

    (4)退出

    另外看第一行,只有ptr == 0,才会返回null

    所以上面才说next()不会因为没消息而返回null,原来返回null的时候在这呢!

    看下源码,MessageQueue.java >> next()

     @UnsupportedAppUsage
        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);
    
                synchronized (this) {
                    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 {
                        ...
                            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;
                    }
                    ...
                }
                ...
            }
        }
    

    代码简略了还是有点多,别着急,慢慢看

    pre什么时候就是0了呢?

    答:quit()了之后

    看下源码,Looper.java

       public void quit() {
            mQueue.quit(false);
        }
    
        public void quitSafely() {
            mQueue.quit(true);
        }
    

    可以看到只是一个传参不同而已,下面看看这个参数是干嘛的

    看下源码,MessageQueue.java >> quit()

     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);
            }
        }
    

    可以看到,safe == true,就移除未来的Message
    safe == false,就移除所有的Message

    mQuiting变成了true,记住他我们一会儿会用到

    而改变ptr的地方在这里

    next()里面

    里面有个dispose,找不到可以ctrl+F找一下

    这里只有在mQuiting == true的时候,才会调用

    这就是改mPtr的地方,然后下次next()的时候就会返回null

    Handler流程

    (1)post过来的msg

    我们已经知道了在Looper的死循环里面,会将next()返回的msg交给Handler,调用dispatchMessage()

    dispatchMessage()里面会先判断msg是不是被post过来的,因为post要执行的逻辑在msg.callback里面,callback是一个Runnable,这可能不是很好理解

    你可以想想runOnUIThread(Runnable),这里的Runnable就是上面的callback
    他们都是调用了Handler.post(Runnable)

    至于为啥起个名叫callback,我也纳闷儿

    (2)send过来的msg

    这些msg是会的逻辑是你重写的handleMessage那里的逻辑

    如果实现了Handler.Callback这个Interface,就会处理mCallbackhandleMessage
    而不是Handler自己的handleMessage

    这是一个优先级策略,没什么好奇怪的

    我们看下源码 => Handler.java >> dispatchMessage()

        public void dispatchMessage(@NonNull Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    这就是Handler的消息机制了

    接下来我们讲讲Handler的另一个功能,切线程

    功能二:切线程

    Handler切线程使用的是ThreadLocal

    (1)ThreadLocal

    ThreadLocal是线程里面的一个数据储存类,用法类似mapkey就是thread

    但是他没有提供,根据key来找ThreadLocalValues的方法,所以暴露的api就只能让你去get当前线程的ThreadLocalValues对象而已,就是key——你自己没法作为参数传进去,只能是currentThread

    如果你没用过ThreadLocal,我给你举个例子

    fun main() {
        val booleanThreadLocal = ThreadLocal<Boolean>()
        booleanThreadLocal.set(true)
    
        println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
    
        thread(name = "thread#001") {
            booleanThreadLocal.set(false)
            println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
        }
    
        thread(name = "thread#002") {
            println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
        }
    }
    

    结果是这样的:你可以自己运行看看

    in Thread[main] booleanThreadLocal value = true
    in Thread[thread#001] booleanThreadLocal value = false
    in Thread[thread#002] booleanThreadLocal value = null
    
    (2)切线程的细节

    话说回来,Handler怎么通过ThreadLocal切线程的呢?

    答案是:Looper是放在ThreadLocal里的

    回顾片头的流程,Handler将消息插入MessageQueue,然后Looper取出来,再还给Handler,这种设计不止是为了让msg可以按顺序处理,还可以让外部接口只有Handler

    最关键的是,LooperHandler的触发关系只有Looper触发HandlerHandler不会触发Looper

    因此Handler把消息放在MessageQueue之后,就在等着Looper来给自己派发任务(msg

    举个例子:

    线程A调用主线程Handler发一个消息

    Handler将这个消息插入MessageQueue,此时其实还在线程A

    只有Loopernext()调用msg.target.dispatchMessage()时,就变成了主线程

    仅仅是因为Looper主线程 而已

    OVER

  • 相关阅读:
    第十二章学习笔记
    UVa OJ 107 The Cat in the Hat (戴帽子的猫)
    UVa OJ 123 Searching Quickly (快速查找)
    UVa OJ 119 Greedy Gift Givers (贪婪的送礼者)
    UVa OJ 113 Power of Cryptography (密文的乘方)
    UVa OJ 112 Tree Summing (树的求和)
    UVa OJ 641 Do the Untwist (解密工作)
    UVa OJ 105 The Skyline Problem (地平线问题)
    UVa OJ 100 The 3n + 1 problem (3n + 1问题)
    UVa OJ 121 Pipe Fitters (装管子)
  • 原文地址:https://www.cnblogs.com/hairless/p/handler.html
Copyright © 2011-2022 走看看