Android - Handler原理

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

功能一:收发消息

简单流程介绍

但愿你看完这篇文章后也能够把流程本身讲出来,而且每一个环节还能够讲出不少细节java

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

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

具体流程通常是:api

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

  2. 这条msgMessageQueue.next()返回并交给Handler去处理数据结构

next()会在有同步屏障(msg.target==null)的时候遍历查找并返回最先的异步消息,并在移除屏障后,从头取出并返回消息异步

  1. Handler.dispatchMessage(msg)会优先处理msg.callback,若是msg.callback为空,就处理Handler.mCallback,而后处理是msg自己 msg.callback是在调用Handler.post(Runnable)时,里面的Runnable(runOnUIThreadview.post(Runnable)也用的是Handler.post(Runnable)Runnable是同样的)async

    这是在不新增Handler的状况下,另外一种调用Handler的方式(以下)ide

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

能够看到他也有handleMessage这个方法oop

Looper是个死循环

(1)死循环的目的

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

由于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()

看下源码 Looper.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

相关文章
相关标签/搜索