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

  • 相关阅读:
    Mysql 常用小技巧
    【JS学习】require('fs')(fs模块用于对系统文件及目录进行读写操作。)
    【JS学习】js中forEach与for循环
    【JS学习】ES6之async和await
    【JS学习】关于Vue.use()详解
    【Npm学习】npm run dev 和 npm run serve
    【Jenkins学习】修改插件下载源地址
    【JS学习】js中const,var,let区别
    【Go学习】知识分享之Golang——go mod时使用代理模式goproxy和私有模式GOPRIVATE
    【JS学习】export 和 export default 的区别
  • 原文地址:https://www.cnblogs.com/hairless/p/handler.html
Copyright © 2011-2022 走看看