Handler消息通信

作者: Davisxy | 来源:发表于2019-10-09 16:22 被阅读0次

    主线程

    在程序启动的时候,就调用Looper.prepareMainLooper方法

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    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();
    }
    
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    可以看到上面的代码主要做了这么几件事情:

    • 初始化了一个Looper对象,在构造方法中初始化了一个MessageQueue,同时获取了当前线程并赋值给变量mThread ;用于后期使用时比较当前执行线程是否为Looper初始化线程
    • 主线程中的Looper初始化传入了一个quitAllowed=false,这说明主线程是不可以随便中断的;
    • 将初始化的Looper对象存入到线程本地变量sThreadLocal中;如果本地已经存在则说明初始化过了一次,则抛出运行时异常;说明主线程的Looper只能prepare一次;
    • 因为只能prepare一次,所以说明构造方法只能执行一次,则对应的MessageQueue也只有一个;
    • 然后执行loop;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        ...
        msg.recycleUnchecked();
    }
    

    从MessageQueue中获取消息queue.next();没有消息则循环中断;
    有消息则通过msg.target.dispatchMessage(msg)转发出去;最后释放消息msg.recycleUnchecked()

    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        ...
        //挂起条件
        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) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        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 {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
    
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ...
            }
            ...
            nextPollTimeoutMillis = 0;
        }
    }
    

    在MessageQueue中获取消息也就几个关键点:

    • for循环遍历消息队列
    • 判断当前是否没有消息或者没有需要立刻执行的消息,则nextPollTimeoutMillis=-1或者>0;然后挂起,CPU暂时释放资源(Linux的epoll管道机制,不是很懂,就知道此时作用就是CPU暂时释放资源):nativePollOnce(ptr, nextPollTimeoutMillis);
    • 因为队列里的同步消息是按照执行时间有序排列,所以如果要唤醒CPU,则除非第一个Message的执行时间到了或者突然有消息插入且是立刻执行的;

    消息遍历已经有了,开始发送消息,让消息队列里面有消息,到了Handler;

    三种方式发消息,对应着三种方式接收消息

    第一种:new Handler(Callback)
    第二种:new Handler();handler.sendXXXMessage()
    第三种:Handler().post(new Runnable())
    
    public void dispatchMessage(Message msg) {
        //第三种
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //第一种
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //第二种
            handleMessage(msg);
        }
    }
    

    其中第一种的返回值还影响到第二种的是否执行;

    发送消息最终走queue.enqueueMessage(msg, uptimeMillis),

    在里面主要对消息做了检测:

    • 是否是同步消息
    • 该消息是否已经使用过一次
    • 是否消息队列已经结束循环并退出
    • 判断该消息的执行时间是否需要唤醒CPU(假设当前挂起,没挂起则正常流程走)

    这样从消息队列的创建、循环检测、消息入队、的流程都有了;

    子线程

    子线程和主线程的区别在于主线程已经通过Looper.prepareMainLooper()初始化过了Looper,而且调用loop开始循环消息队列;而子线程需要自己创建一个绑定当前子线程的Looper,需要prepare,然后loop;剩下的都一样。

    经典问题
    Handler 是何时调度线程的:这个在了解之后其实已经不是问题了,因为Handler是依附于Looper创建,Looper在哪个线程创建,则Handler就在哪个线程;也就是按照我们的使用惯例,在主线程中初始化一个Handler,然后再子线程中发消息,因为是在主线程中创建的handler,所以消息是发送到了主线程的MessageQueue中,然后分发也是到主线程中对应的handler回调;
    另一种使用方法,在子线程中创建Looper,然后创建基于子线程的Handler,那么在主线程中使用Handler发消息,则收到的消息就在子线程中的Handler的回调中。

    最后借用一张图:

    TIM截图20191009155937.png

    相关文章

      网友评论

        本文标题:Handler消息通信

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