Handler详细流程

作者: space0o0 | 来源:发表于2019-08-24 16:49 被阅读0次

接上文Handler简单回顾流程

handler搬运消息给MessageQueue都是在Java中做简单的调用

需要更加深入理解handler的原理,我们需要把目光再拉远,看得更多,细节也会更多。

首先,UI线程是用来更新界面的一个特殊存在,ActivityThread就是activity存在的线程,android只允许UI线程更新界面的目的就是为了保证线程的安全。

Looper的初始化

ActivityThread就是一个简单的java类,他有一个main函数作为主入口。

首先在main函数中初始化一个looper,并且记为mainLooper(因为他是main中的looper,也是更新ui的主要looper)

Looper.prepareMainLooper();//初始化一个Looper

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        ......
        sMainLooper = myLooper();//记录mainLooper
        }
}

重点关注prepare函数

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
          //已经存在looper,throw Exception
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

唉,好像看到一个熟悉的错误Only one Looper may be created per thread
在此处判断,如果存在了looper,就抛出异常

不过sThreadLocal是什么?ThreadLocal有什么用?(拓展知识点

在上述代码中,sThreadLocal用到了get 和 set 方法,我们可以把sThreadLocal想象成map结构,key就是当前Thread,value就是想要set的对象。set时自动识别到当前线程。因此在map中保证了只有唯一的key(也就是Thread),那么从sThreadLocal中取出的looper也就保证了唯一,而且在使用的过程中,通过sThreadLocal就进行了线程的切换。

ThreadLocal确保了每个线程都有唯一一个looper,这也解释了通过looper可以达到切换线程的目的。

main函数的其他初始化操作都完成了后,就通知开启loop,准备从消息队列中获取消息,再把消息分发出去 dispatchMessage()

Looper.loop();//开启loop

MessageQueue的初始化

MessageQueue的初始化就在Looper的构造函数中

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

MessageQueue构造函数中做了两件事,暂时只关注nativeInit方法

native的Looper和MessageQueue

nativeInit是一个native端的方法(底层方法),他主要干了两件事,创建native端的MessageQueue(这个MessageQueue不是java层的,后面用nativeMessageQueue表示);返回nativeMessageQueue的地址;
mPtr就是nativeMessageQueue的地址

对应的,native端也有一个Looper(我们后面也把他叫做nativeLooper)。

nativeLooper也干了许多事,不过我们不用太关注细节,只要知道nativeLooper有一套机制(epoll):想象每个通知路径就是一条水管,数据在水管中传输,哪个数据发生了变化,epoll会观察到并进行通知。

emmm,涉及到native方法了。
需要交代一个底层的原理:epoll (拓展知识点

我们平时发送网络请求,传输文件,pipe(ipc机制)这些都是数据的传输,我们把它叫做(想象java的输入输出流)。流的一端为写入(write),另一端为读取(read)。

如果我需要读取数据(read),可是另外一端暂时没有写入(write),是不是就的干等着?

这就相当于是阻塞,我啥事都不能干,只能这么等待,这效率也太低了吧。

所以,epoll的作用就来了,它会帮你观察何时数据被写入(write),当数据来临时,通知你进行读取(read)。有epoll的存在,我就不用这么累了,可以去休息。

相对于线程来说,就不会存在线程阻塞问题,线程不阻塞,cpu就不会瞎忙活。平时总是被问到looper为什么不会被阻塞,原因就在此。

Handler的搬运消息

了解了epoll机制后,再来看看handler的搬运消息

//MessageQueue的enqueueMessage()
synchronized (this) {
    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) {
        // 插入队列头部,创建新的消息队列。
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
    } else {
        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);
    }
}

如果:
消息队列为空;
新消息处理时间==0(立即处理);
新消息处理时间小于队列头的处理时间;

我们就把新消息插入队列头,同时现场时休眠状态,就需要把它唤醒(nativewake),处理一下新消息。

其余情况:
我们就把新消息按照处理时间插入队列中

Looper.Loop()

底层溜达了一圈,最终还是得回到java层。

Looper.loop();

loop函数开启了消息的接收,只要存在消息,就对消息进行分发

再次回顾下如何从messageQueue中获取消息

//MessageQueue的next()方法
for (;;) {
    if (nextPollTimeoutMillis != 0) {
        Binder.flushPendingCommands();
    }
    //从队列中拉取消息,nextPollTimeoutMillis=0表示立即拉取
    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) {
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());
        }
        if (msg != null) {
            if (now < msg.when) {
                // now:当前时间     msg.when:消息的通知时间
                // 消息的通知时间还不到,计算下次拉取message的时间
                nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
            } else {
                // 消息现在可以执行了,把当前消息从队列中拿出来
                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 {
            nextPollTimeoutMillis = -1;
        }

        if (mQuitting) {
            dispose();
            return null;
        }
}

nativePollOnce(ptr, nextPollTimeoutMillis);

首次调用就先调用nativeLooper去请求看有没有message,没有新消息就尝试进行休眠(不阻塞)。再也不用担心线程傻等了。

Handler.dispatchMessage()

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

分3种情况处理消息:
给消息添加callBack回调
handler自带的callBack
handleMessage方法

总结:

  • 线程中调用prepare()函数新建Looper,ThreadLocal保证了线程的唯一性;

  • 创建looper时,同时也创建了MessageQueue

  • MessageQueue的构造函数中又创建了nativeMessageQueue,mPtr则记录了nativeMessageQueue的地址。java层就变相的应用了底层epoll机制,再也不怕阻塞了。

  • Handler的创建跟随线程,当handler把消息插入MessageQueue时,按照处理时间先后顺序排序,如需唤醒线程,就调用nativeWake()

  • Looper.loop()则不断从MessageQueue中获取,如果处理时间没到,则继续休眠。

  • Looper获取到新消息后,message持有handler的应用,使用dispatchMessage分发消息,不同的callback,处理不同的消息。

相关文章

网友评论

    本文标题:Handler详细流程

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