美文网首页Android 面试专辑面试汇总Android开发
Android消息机制之Message解析(面试)

Android消息机制之Message解析(面试)

作者: 沈敏杰 | 来源:发表于2016-10-26 14:18 被阅读1552次

    在android的消息机制中,Message其充当着信息载体的一个角色,通俗的来说,我们看作消息机制就是个工厂的流水线,message就是流水线上的产品,messageQueue就是流水线的传送带。之前做面试官的时候,经常会问面试者关于message的问题,如:

    1.聊一下你对Message的了解。
    2.如何获取message对象
    3.message的复用(如果以上问题能答对,加分)

    在下面我带着这三个问题,从这段代码开始逐一解析。

    /**
     * 创建一个handler
     */
    Handler handler = new Handler();
    
    /**
     * 模拟开始
     */
    private void doSth() {
        //开启个线程,处理复杂的业务业务
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟很复杂的业务,需要1000ms进行操作的业务
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //在这里可以更新ui
                        mTv.setText("在这个点我更新了:" + System.currentTimeMillis());
                    }
                });        
            }
        }).start();
    }
    

    我们创建了一个handler,在doSth()中开启线程模拟处理复杂业务,最后通过handler的post返回结果进行UI操作(子线程不能进行操作UI,后话),我们先从handler的post开始看起,

    Handler.java:

    /**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is
     * attached.
     *
     * @param r The Runnable that will be executed.
     * @return Returns true if the Runnable was successfully placed in to the
     * message queue.  Returns false on failure, usually because the
     * looper processing the message queue is exiting.
     */
    public final boolean post(Runnable r) {   
         //通过getPostMessage获取了message,再往下看
        return sendMessageDelayed(getPostMessage(r), 0);
    }
    

    在post中,我们传进一个Runnable参数,我们发现有一个getPostMessage(r)函数,我们先从getPostMessage()下手。

    Handler.java:

    private static Message getPostMessage(Runnable r) {
        //在这里,获取一个message,把我们的任务封装进message
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    

    从getPostMessage函数可得,我们把参数Runnable封装进去message的callback变量中,在这里埋伏一个很重要的概念,在Handler的源码中,是如何获取message对象的。顾名思义,在getPostMessage中,我们就是为了获取把runnable封装好的message。这样,我们可以返回上一层,继续看函数sendMessageDelayed(Message,long)。

    Handler.java:

    /**
     * Enqueue a message into the message queue after all pending messages
     * before (current time + delayMillis). You will receive it in
     * {@link #handleMessage}, in the thread attached to this handler.
     *
     * @return Returns true if the message was successfully placed in to the
     * message queue.  Returns false on failure, usually because the
     * looper processing the message queue is exiting.  Note that a
     * result of true does not mean the message will be processed -- if
     * the looper is quit before the delivery time of the message
     * occurs then the message will be dropped.
     */
    public final boolean sendMessageDelayed(Message msg, long delayMillis) {   
        //顾名思义的delay,也就是延迟,在上一层我们看到了post里传参是0,继续往下看
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
        
        
    /**
     * Enqueue a message into the message queue after all pending messages
     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     *
     * @param uptimeMillis The absolute time at which the message should be
     *                     delivered, using the
     *                     {@link android.os.SystemClock#uptimeMillis} time-base.
     * @return Returns true if the message was successfully placed in to the
     * message queue.  Returns false on failure, usually because the
     * looper processing the message queue is exiting.  Note that a
     * result of true does not mean the message will be processed -- if
     * the looper is quit before the delivery time of the message
     * occurs then the message will be dropped.
     */
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //在这里判断handler里的队列是否为空,如果为空,handler则不能进行消息传递,因为生产线的传送带都没有的话,还怎么进行传送
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    

    在handler中,存在着sendMessageDelayed最终会用sendMessageAtTime,只是sendMessageDelayed中传参为0,使得sendMessageAtTime这函数最大程度能复用,我们继续往enqueueMessage函数看去。

    Handler.java

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //在message中放一个标记
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //在这里把消息放到队列里面去
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    在enqueueMessage函数中,我们发现有个入参queue,这个入参就是消息队列,也就是之前我所说的流水线的传送带,message需要通过传messagequeue进行传递,我们继续往下探索。

    MessageQueue.java:

    
    boolean enqueueMessage(Message msg, long when) {
        //这里通过之前的判断,之前放的目标,还有这个消息是否已经在使用了,都需要判断
        //还记得之前我们看到的Message是怎么获取的吗?Message.obtain(),这里需要判断msg.isInUse,是否已经在使用这个消息
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        //这里就是真正把message放到队列里面去,并且循环复用。
        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) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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);
            }
        }
        return true;
    }
    

    enqueueMessage先判断之前的target是否为空,以及这个message是否已使用,后面的代码则是把message放进队列中,往下我们就不探究了,我们看到最终返回的结果return true.

    我们看回来此段代码:

    /**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is
     * attached.
     *
     * @param r The Runnable that will be executed.
     * @return Returns true if the Runnable was successfully placed in to the
     * message queue.  Returns false on failure, usually because the
     * looper processing the message queue is exiting.
     */
    public final boolean post(Runnable r) {
        //通过getPostMessage获取了message,再往下看
        return sendMessageDelayed(getPostMessage(r), 0);
    }
    

    @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting.

    我们一层一层往下探索,无非就是把这个这个执行UI操作的Runnable封装成message,再将这个message放进我们的消息队列messagequeue中。在post如果返回true则成功添加进去消息队列,如果返回false则代表失败。

    这个流程相信大家也清晰了吧,现在我之前所说的问题,handler中如何获取message对象的。

    Handler.java:

    private static Message getPostMessage(Runnable r) {
        //在这里,获取一个message,把我们的任务封装进message
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    

    在这里,为什么Message不是通过new一个对象,而是通过其静态函数obtain进行获取?
    我们通过其源码继续探索:

    Message.java:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    

    Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases.

    我们从注释中看到pool这个词,这个就是池,大家应该也听过过线程池,对象池,没错,我们获取的message对象优先在这个message池里获取,如果池里没有再new一个新的Message.

    我们先了解一下,这里面的sPool、next、sPoolSize到底是什么东西。
    Message.java:

    //池里的第一个对象
    private static Message sPool;
    
    //对象池的长度
    private static int sPoolSize = 0;
    
    //连接下一个message的成员变量
    // sometimes we store linked lists of these things
    /*package*/ Message next;
    

    在Message这个类中,存在着一个全局变量sPool,sPoolSize则是对象池中的数量,还有一个成员变量next.我们得理清一下sPool跟next到底存在着什么关系。在这先提出一个问题,我们看了那么久的池,怎么没看到类似Map这样的容器呢?Message对象池其实是通过链表的结构组合起来的池。

    Paste_Image.png

    上面有三个message,分别为message1、message2、message3
    他们的连接关系分别通过其成员变量next进行衔接,举个例子:

    message1.next=message2
    message2.next=message3
    ......
    

    以此类推,那么我们了解了message的next有什么作用,那么sPool呢?
    我们注意到sPool是全局变量,我们又看回obtain函数中,是怎么样获取的。

    Message.java:

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                //在池中获取message也是从表头获取,sPool赋值给message,
               //同时把其连接的next赋值给sPool(这样,连接起来的message从第二个位置放到表头上了),赋值后设置next为空
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    

    我们看到源码中,先判断sPool为不为空,为空代码这个池的数量为0,不能从池里获取到message.那如果不空,先将sPool赋值给message,再将这个message的下一个next赋值给sPool,赋值完后将message的next设为空,这不就是从表头里获取数据,sPool就是表头的第一个message。如:
    message1是表头第一个元素,sPool也是表头,指向message1。当message1从池中取出来时候,message1连接的message2(通过next),成为了表头,同时sPool也指向新的表头,sPoolSize的数量也相应的需要减少。

    通过以上例子,我们了解message的结构,也明白了message如何获取,别忘了我们的message除了在池里获取,还能通过创建一个新的实例,那么,新的实例是怎么放进池的,下面开始看看message的回收。

    Message.java:

    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        //如果还在使用这个消息,不能进行回收--通过flag进行标示
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
    

    Recycle()函数是怎样调用的,暂且先不讨论,我们先看看其回收的机制,先判断这个message是否使用状态,再调用recycleUnchecked(),我们重点看看这个函数。

    Message.java

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        //这里才是真正回收message的代码,把message中的状态还原
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
        //如果message池的数量未超过最大容纳量(默认最大50个message容量),将此message回收,以便后期复用(通过obtain)
        //在代码中可知,message在recycle()中进行回收的
        //假设池中的message数量为0时,sPool全局变量为null
        //当我们把第一个message放进去池的时候,sPool(这个时候还是null)赋值给next,而message本身赋值给全局变量sPool,也就是每次回收的message都会插入表头
        //这样一来就形成了链表的结构,也就是我们所说的对象池
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
    

    我们看到源码中的最后几行,如果池中现有的数量少于最大容纳量,则可将message放进池中,我们又看到了头疼的next跟sPool,我先举个例子,脑补一下:

    1.我有一个message1,我用完了,系统回收这个message1
    2.现有的池,表头是message2。

    结合以上两个条件再根据源码能得出:
    sPool跟message2都指向同一个地址,因为message2是表头,那么message1回收的时候,sPool赋值给了message1的next. 也就是说,message1成了新的表头,同时池的数量sPoolSize相应的增加。

    message的回收就是将其放到池的表头,包括获取message也是从表头上获取。

    总结:
    Android的消息机制都通过message这个载体进行传递消息,如果每次我们都通过new这样的方式获取对象,那么必然会造成内存占用率高,降低性能。而通过对其源码的学习,了解message的缓存回收机制,同时也学习其设计模式,这就是我们所说的享元模式,避免创建过多的对象,提高性能。

    相关文章

      网友评论

      本文标题:Android消息机制之Message解析(面试)

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