Android的Handle消息机制|SquirrelNote

作者: 跳动的松鼠 | 来源:发表于2017-11-15 14:17 被阅读58次

    前言

    Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层的MessageQueue消息队列和Looper消息循环的支撑。简单来说,Android的消息机制就是:Handler给MessageQueue添加消息,然后Looper无限循环读取消息,再调用Handler处理消息。下面将从细节方面进行详细描述:

    1. 为什么会有消息机制
    2. 消息机制概述
    3. 消息机制源码分析

    一、为什么会有消息机制

    在Android的UI主线程中,不能执行超出5秒的耗时任务,并且所有View和ViewGroup都只能在UI主线程中运行。如果View或者ViewGroup在工作线程中运行,将会抛出【Only the original thread that created a view hierarchy can touch its views】异常,所以在android中会通过消息机制来解决线程和线程之间的通信问题。

    二、消息机制概述

    消息机制由以下四个部分组成:

    • Message消息(数据载体)
    • Handler消息处理器
      发送消息
      处理消息
    • MessageQueue消息队列(存储消息)
    • Looper轮循器
      去MessageQueue取消息
      分发给Handler处理

    Message、MessageQueue、Looper、Handler之间的关系(即Handler机制原理)

    Activity只要一创建Thread里面就多了一个looper(消息轮循器),在主线程里面要定义一个内部类handler,handler的初始化在主线程,有很多子线程要更新UI,Thread拿到主线程的handler,调用sendMessage方法,发送消息,把消息放到消息队列里面,只要消息进到消息队列,looper就会把消息取出来,调用里面的loop方法,取出来的消息就交给handler里面的handlerMessage方法,处理消息.
    之所以这样做的原因是因为避免多线程并发更新UI线程所产生的问题的,如果我们允许其他子线程都可以更新界面,那么势必会造成界面的错乱(因为没有加锁机制),如果我们加锁,又会影响速度,而使用Handler机制,所有更新UI的操作,都是在主线程消息队列中轮询去处理的。

    Message

    Message消息,又叫task任务。封装了任务携带的信息和处理该任务的Handler。
    Message的用法:

    • 可以通过Message.obtain()来从消息池中获得空消息对象
    • 如果message只需要携带简单的int信息,使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
    • 用message.what来标识信息,以便用不同方式处理message。
    Handler

    什么是Handler?Handler扮演了往MessageQueue中添加消息和处理消息的角色(只处理由自己发出的消息),即通知MessageQueue它要执行的任务(sendMessage),并在loop到自己的时候执行该任务(handMessage),整个过程是异步的。Handler创建时会关联一个looper,默认的构造方法会关联当前线程的looper。
    handler必须关联一个looper才会起作用,Android UI主线程关联了一个Loop线程,但是我们自定义的线程必须开启Loop。

    Handler发送消息
    Handler能发送两种消息:一种是Runnable对象,一种是Message对象。但其实post发出的Runnable对象最后都被封装成message对象了。
    具体:Handler创建完毕后,其内部的Looper以及MessageQueue就可以和Handler一起协同工作了,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,这个消息也会在Looper中去处理。其实post方法最终也是通过send方法来完成的。

    1. send方法发送消息(需要回调才能接收消息)
      sendMessage()立即发送Message到消息队列
      sendMessageAtFrontOfQueue() 立即发送Message到队列,而且是放在队列的最前面
      sendMessageAtTime() 设置时间,发送Message到队列
      sendMessageDelayed() 在延时若干毫秒后,发送Message到队列

    send方法的工作过程:当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放到消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用。注意:Looper是运行在创建Handler所在的线程中的,这样Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。

    1. post方法发送消息(直接绑定handler当前线程执行,需要Runnable对象)
      post() 立即发送Message到消息队列
      postAtFrontOfQueue() 立即发送Message到队列,而且是放在队列的最前面
      postAtTime() 设置时间,发送Message到队列
      postDelayed() 在延时若干毫秒后,发送Message到队列

    Activity中有一个方法runOnUiTheread()实际上就是Handler的post方法。

    MessageQueue

    消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。

    Looper

    消息循环。由于MessageQueue只是一个消息存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。

    Looper中有一个特殊的概念:ThreadLocal,它的作用是可以在每个线程中存储数据。Handler创建的时候采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLoacal了,ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLoacal可以轻松获取每个线程的Looper。

    注意:线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,所以在主线程中默认可以使用Handler。

    三、消息机制源码分析

    Message消息
    两种创建方式:

    Message msg1=Message.obtain();
    Message msg2=new Message();
    

    从源码上看:

    /** 
    *空的构造方法
    */
    public Message() {
    }
    

    Handler
    伪代码如下:

    new Handler(){
      handleMessage(Message msg){
        //处理消息
      }
    };
    

    下面从源码角度看,new Handler做了哪些操作:
    Handler的构造方法

    public Handler() {
    ...
        //获取Looper(是在ActivityThread里面设置的Looper),只要一new Handler就会从当前主线程去拿Looper,在子线程去new会报错
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //设置了一个队列
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
    

    主线程设置Looper,在ActivityThread类里面

    public static final void main(String[] args) {
    ...
        //第一步:主线程创建Looper
        //点进去,ActivityThread是运行在主线程的,调用了prepareMainLooper()这里面的方法,给当前线程set一个Looper,我们在
        //new Handler的时候,去Looper.myLooper,拿到的是主线程的Looper,这就是我们主线程的Looper何时去创建的。
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
    
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        ////主线程创建Looper完成以后,就调用Looper的loop方法
        Looper.loop();
    ...
    

    Looper

     /** 
      *既然知道它是从prepare方法里面放的,就要知道哪个地方调用prepare方法
      */
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //第三步:在主线程中设置Looper。new Looper()里面同时创建了一个MessageQueue,这样就知道消息队列在哪new出来的
        sThreadLocal.set(new Looper());
    }
    
    public static final void prepareMainLooper() {
        //第二步: 调用prepare()方法
        prepare();
        setMainLooper(myLooper());
        if (Process.supportsProcesses()) {
            myLooper().mQueue.mQuitAllowed = false;
        }
    }
    

    主线程调用Looper.loop()方法,主线程就会阻塞,是一个死循环,使用管道(Pipe),管道是Linux中的一种进程间通信的方式。管道的原理:使用了特殊的文件,文件里面有两个文件描述符(一个是读取,一个是写入)。
    应用场景:当Linux下有两个进程需要通信时,主进程拿到读取的描述符等待读取,没有内容就阻塞,然后另一个进程拿到写入描述符去写内容,唤醒主进程,主进程拿到读取描述符读取到的内容,继续执行。

    管道在Handler的应用场景:Handler在主线程中创建,Looper会在死循环里等待取消息,一种是没取到消息,就阻塞;一种是主线程一旦被子线程唤醒,取到消息,就把Message交给Handler去处理。子线程是用Handler去发送消息,拿写入描述符去写消息,写完之后就唤醒主线程。

    public static final void loop() {
    ...
        //死循环
        while (true) {
            //每次从队列中去取下一个数据(消息),要看什么时候给队列赋值,(在Handler创建的时候,就已经给它设置了队列)
            Message msg = queue.next(); // might block,取消息,如果没有消息,会阻塞
            //if (!me.mRun) {
            //    break;
            //}
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                //msg.target其实就是一个Handler,Handler就会调用dispatchMessage方法。
                msg.target.dispatchMessage(msg);
                ...
            }
        }
    }
    
    

    Handler发送消息代码:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
    ...
            if (queue != null) {
            //this是当前的Handler类,这样每个Message里面有一个target,target值就是当前的Handler
            //把message的target置为当前发送的Handler,以便Looper取到message后根据target把message分发给相对应的Handler
            msg.target = this;
            //往队列里面添加Message
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
    ...
        return sent;
    

    MessageQueue.enqueueMessage代码

    final boolean enqueueMessage(Message msg, long when) {
    ...
            //when代表发送的时间
            msg.when = when;
            //Log.d("MessageQueue", "Enqueing: " + msg);
            Message p = mMessages;
            //when=0,不延迟,当前发送的message消息需要马上处理(当前只有一个Message),needWake置为true
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                //需要唤醒
                needWake = mBlocked; // new head, might need to wake up
            } else {//当前有很多Message,如果当前Message排在其他message后面,当前Message不用优先处理,不用唤醒主线程,needWake置为false
                Message prev = null;
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
        }
        //是否唤醒主线程,如果要唤醒,调用底层的jni方法去唤醒
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }
    
    

    Handler.dispatchMessage()方法:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //如果有runnable就调用这个
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //如果没有runnable就调用handleMessage方法,把Message交给Handle处理
            handleMessage(msg);
        }
    }
    

    提问:Handler与Looper的对应关系,是多对一。那么每个handler发送的消息,怎么保证不会处理别的handler发送的消息呢?

    Handler底层用到了管道的通信方式,Handler在sendMessage的时候,会把Message的target置为Handler,在调用Looper的loop方法的时候,找到每个message,调用message的target,然后去dispatchMessage,这样Looper就会根据每个message找到需要相对应处理message的handler,因为在一个应用里面可能用到多个Handler,所以这个地方通过message的target去区分每个handler需要处理它自己发送的message。

    小结:

    大致流程:
    在主线程创建Handler的时候,就可以拿到主线程的Looper,主线程创建完Looper之后,就会执行Looper中的loop方法,loop方法就会从MessageQueue消息队列中一个一个去拿Message消息,它是一个死循环。死循环里面使用了管道的通信方式,管道里面就会拿到文件描述符,一个是往里面存,一个是往里面写。存的时候,是通过上层的Handler,去sendMessage发送消息,调用MessageQueue的enqueueMessage方法,这样就拿到写入的描述符往里面写了,在loop方法里面有queue.next()方法,这个方法会拿到读取的描述符,当它没有读到消息时就阻塞,读到有消息的时候就调用dispatchMessage方法,dispatchMessage方法就会调用handleMessage方法处理消息

    下面是Handler的流程图,描述了Handler消息机制:


    image.png

    最后

    我们可以把Handler消息机制理解为生产消费模型:

    1. Handler是生产者,主要往MessageQueue中存放消息;
    2. Looper为消费者,主要从MessageQueue中消费消息;

    以上是根据我的一些理解,做的总结分享,旨在抛砖引玉,希望有更多的志同道合的朋友一起讨论学习,共同进步!

    相关文章

      网友评论

        本文标题:Android的Handle消息机制|SquirrelNote

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