Handler的消息运行处理机制

来源:互联网 发布:网络安全教育手抄报 编辑:程序博客网 时间:2024/06/16 01:55

一、Handler运行机制的总体流程

Handler总体运行流程图
由上图可以看出,对于handler处理消息的流程是:

  ①:产生消息:消息队列中产生了新的消息  ②:获取消息:创建handler对象,获取到要发送的消息  ③:发送消息:通过handler机制,将消息发送到looper的队列中  ④:处理消息:looper的轮循机制,不断的扫描查看是否有新的消息产生,如果发现有新的消息产生,那么就调用该handler对应的handler处理方法,进行消息处理。

对于handler处理消息的机制,大致就是以上几个过程,当然在执行流程之前,我们已经建立了Handler和Looper的对象。下面我们就从源码的角度详细的分析整个执行过程。

二、Handler关联Looper对象

单链表结构是怎样形成的呢?这个就是由我们的消息的创建机制来完成的。主要是实现把消息池里的第一条数据取出,当消息池中有消息时,这时我们就需要调用handler对象来进行获取消息和发送消息,那么hanlder的对象究竟是怎样获取和发送消息的呢?这时我们就需要查看源码了。

/**     * Default constructor associates this handler with the queue for the     * current thread.     *     * If there isn't one, this handler won't be able to receive messages.     */    public Handler() {        if (FIND_POTENTIAL_LEAKS) {            final Class<? extends Handler> klass = getClass();            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&                    (klass.getModifiers() & Modifier.STATIC) == 0) {                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +                    klass.getCanonicalName());            }        }        //对于上面的条件判断,对于我们分析运行机制没有多少帮助,这里我们可以暂时忽视,着重分析下面的代码。        mLooper = Looper.myLooper();//调用myLooper()方法获取looper对象        if (mLooper == null) {//如果mLooper为空,就会报运行时异常,导致程序崩掉。            throw new RuntimeException(                "Can't create handler inside thread that has not called Looper.prepare()");        }        mQueue = mLooper.mQueue;//拿到消息队列        mCallback = null;//将回调对象设置为空。    }

通过上面的源码分析,我们可以看出,Handler主要是完成了获取looper对象和拿到消息队列。那么究竟是怎样获取到这两个对象的呢?我们还是要从源码角度分析:

 /**     * Return the Looper object associated with the current thread.  Returns     * null if the calling thread is not associated with a Looper.     */     public static final Looper myLooper() {         return (Looper)sThreadLocal.get();//通过sThreadLocal的get方法获取到looper对象     }

通过上面的源码分析,我们可以看出静态方法myLooper()主要是通过get方法获取到looper对象,既然有get方法,那么肯定会存在一个set方法,将looper对象设置进去,继续跟踪源码。

 /** Initialize the current thread as a looper.      * This gives you a chance to create handlers that then reference      * this looper, before actually starting the loop. Be sure to call      * {@link #loop()} after calling this method, and end it by calling      * {@link #quit()}.      */    public static final void prepare() {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper());    }从上面的源码中我们可以看出Looper是直接new出来的,并且继续跟踪源码我们发现在Looper的构造方法中,new出了消息队列对象private Looper() {        mQueue = new MessageQueue();//创建消息队列        mRun = true;        mThread = Thread.currentThread();//获取到当前线程对象    }

既然ThreadLocal.set(new Looper())方法是Looper.prepare()调用的,那么,prepare()又是什么时候调用的呢?继续查看源码,会发现是prepareMainLooper()调用的,至此我们便查看到了整个looper的创建和调用机制,如果继续在Looper类中查找我们,很难发现什么时候调用prepareMainLooper()方法,这个时候我们需要查看ActivityThread类,在其main()方法中我们会发现该方法,下面我们对该方法继续分析:

public static final void main(String[] args) {    ...    //创建Looper和MessageQueue    Looper.prepareMainLooper();    ...    //轮询器开始轮询    Looper.loop();    ...///如果上面的looper结束,那么在此立刻就会抛异常,程序停止}

三、Looper的轮循机制

细心的哥们会发现,对于Looper的轮循是通过调用Looper.loop()实现的,该方法中具体是实现那些事情呢?我们查看源码一起分析一下。

  /**     *  Run the message queue in this thread. Be sure to call     * {@link #quit()} to end the loop.     */    public static final void loop() {        Looper me = myLooper();//获取looper对象        MessageQueue queue = me.mQueue;//获取队列        while (true) {//死循环,不断的轮循            Message msg = queue.next(); // 取出下一个消息            //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.dispatchMessage(msg);//分发消息                if (me.mLogging!= null) me.mLogging.println(                        "<<<<< Finished to    " + msg.target + " "                        + msg.callback);                msg.recycle();//回收消息            }        }    }

这里面通过一个死循环来不断的轮序消息队列,查看是否有消息的产生,如果有消息产生,就分发消息,然后轮循消息。有哥们会问“这个处理是在主线程中,不会出现ANR吗?”,针对于这一点我们详细分析一下。

  • 为什么采用死循环?
    我们都知道hanlder的处理机制中一个重要的环节就是通过建立Looper的机制,不断的轮循消息队列,查看是否有消息产生,如果有,就将消息去除,交给对应的handler方法处理(message对象的target属性,用于记录该消息由哪个Handler创建,在obtain方法中赋值),如果不是采用死循环的形式,我们无法保证looper循环的连贯性。

  • ANR产生的原因
    在主线程中做耗时操作,肯定会产生”ANR”,在我们该方法中,主要可能产生“ANR”的是msg.target.dispatchMessage(msg),因为对于消息的分发处理,如果某个消息是一个耗时的操作,那么就有可能产生“ANR”,导致阻塞对于下一个消息的处理。

  • 怎样解决ANR
    假如我在oncreate()开启一个耗时操作,如果是在里面直接开启耗时,那么就会阻塞其他方法的执行(onstart()),其他的消息就无法正常的执行和处理,但是如果我们在此时开启一个子线程去处理oncreate()中的方法,那么oncreate()就会立即结束,主线程就会执行其他的方法中,不会产生ANR,当oncreate()的耗时操作,执行结束后,通过sendMessage()方法,将消息发送给主线程,主线程收到消息后再处理。这样整个流程就会正常执行。

  • 解决死循环的机制
    Linux的一个进程间通信机制:管道(pipe)。原理:在内存中有一个特殊的文件,这个文件有两个句柄(引用),一个是读取句柄,一个是写入句柄,主线程Looper从消息队列读取消息,当读完所有消息时,进入睡眠,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。

四、Message的队列维护和发送

我们提到了Message的是单链表结构,那么这种结构究竟是怎样产生的呢?我们继续跟踪源码去查看一下。Handler发送消息,sendMessage的所有重载,实际最终都调用了sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis)    {        boolean sent = false;        MessageQueue queue = mQueue;        if (queue != null) {            msg.target = this;            sent = queue.enqueueMessage(msg, uptimeMillis);        }        else {            RuntimeException e = new RuntimeException(                this + " sendMessageAtTime() called with no mQueue");            Log.w("Looper", e.getMessage(), e);        }        return sent;    }

分析源码可以看出,对于消息的队列维护的处理,主要是通过sent = queue.enqueueMessage(msg, uptimeMillis)方法完成的,其中两个参数分别表示的是消息对象和时间戳。下面仔细分析一下这个加载维护过程。

4.1消息入列

注意观察msg.when = when;其中when非常重要,它是消息的取出和回收,以及判断消息是否处理过的重要标志。

synchronized (this) {       ...        //对消息的重新排序,通过判断消息队列里是否有消息以及消息的时间对比        msg.when = when;//这个when非常重要,它是消息的取出和回收,以及判断消息是否处理过的重要标志        Message p = mMessages;        //把放入消息队列的消息置为消息队列第一条消息        if (p == null || when == 0 || when < p.when) {//注意when,时间早的放在最前面            msg.next = p;//mMessages指向该消息对象            mMessages = msg;//指向新的消息            needWake = mBlocked; // new head, might need to wake up//是否换到主线程        } else {//下个消息....            //判断时间顺序,为刚放进来的消息寻找合适的位置            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        }    }

这里写图片描述

  • 第一个消息进入
    如图所示,当我们要加载一个消息(每个消息都包含when 和next属性)进入队列时,mMessages指向该消息,直接加入到队列中。
  • 再次有消息进入
    再次有消息进入时,会将mMessages赋值给临时变量p,那么p就指向了队列中的消息对象。此时就开始比较when的值(假如新加入的消息when小于队列中的,走条件一),注意下面的赋值变换:
    • msg.next = p;//p指向新来的消息的next
    • mMessages = msg;//新来的消息指向队列中的message。
      同理走else条件时,仍然是根据when条件进行判断,从而形成了单链表结构。

4.2 回收消息

  /**     * Return a Message instance to the global pool.  You MUST NOT touch     * the Message after calling this function -- it has effectively been     * freed.     */    public void recycle() {        synchronized (mPoolSync) {            if (mPoolSize < MAX_POOL_SIZE) {                clearForRecycle();//调用消息的方法                next = mPool;//原先指向队列中消息的mpool指向新进入的消息的next                mPool = this;//将当前对象赋值给mpool            }        }    }

实现原理:如果当前队列中的mPoolSize 小于队列能承受的最大值,就将消息添加到message的队列中,否则就将消息丢掉,交给垃圾回收机制处理。
如果符合条件,就将原来指向队列中的mPool赋值刚进入的next,新加入的消息对象赋值给mPool。

4.2获取消息

获取消息的机制如下图所示:
这里写图片描述
源码分析

  /**     * Return a new Message instance from the global pool. Allows us to     * avoid allocating new objects in many cases.     */    public static Message obtain() {        synchronized (mPoolSync) {            if (mPool != null) {                Message m = mPool;                mPool = m.next;                m.next = null;                return m;            }        }        return new Message();    }

obtain()方法中mPool(临时的Message对象)判断是否为空,如果不为空,则进入内部的判断。具体实现原理解析:
- Message m = mPool;//临时变量m指向第一个消息对象
- mPool = m.next;//mPool 指向下一个对象
- m.next = null;//m的next为空,就是将其断开
这样就完成了消息队列的取出机制。

至此我们就完成了整个的handler消息产生、发送、looper的轮循处理、交给对应handler方法处理等过程。

总结:主线程中有一个Looper机制里面包含一个while死循环,这个死循环运行的原理以及解决ANR的方法;创建Handler怎样和Looper进行关联的。其次讲解了从Message队列中怎样获取到Message消息,获取到消息后是怎样通过handler的机制加入到Looper的可执行队列中的(按when排序)。最后我们是怎样调用对应的Handler方法执行的。

备注:码农小白,水平有限,如有错误欢迎指正。

1 0
原创粉丝点击