Android Handler机制之Message及Message回收机制

小松鼠.jpg

该文章属于《Android Handler机制之》系列文章,若是想了解更多,请点击 《Android Handler机制之总目录》安全

前言

在前面的文章中咱们讲解了Handler、Looper、MessageQueue的具体关系,了解了具体的消息循环的流程。下面将一块儿来探讨最为整个消息循环的消息载体Message。bash

Message中能够携带的信息

Message中能够携带的数据比较丰富,下面对一些经常使用的数据进行了分析。oop

/**
 * 用户定义的消息代码,以便当接受到消息是关于什么的。其中每一个Hanler都有本身的命名控件,不用担忧会冲突
 */	
 public int what;
/**
 * 若是你只想存不多的整形数据,那么能够考虑使用arg1与arg2,
 * 若是须要传输不少数据可使用Message中的setData(Bundle bundle)
 */
 public int arg1;
/**
 * 若是你只想存不多的整形数据,那么能够考虑使用arg1与arg2,
 * 若是须要传输不少数据可使用Message中的setData(Bundle bundle)
 */
 public int arg2;
/**
 * 发送给接受方的任意对象,在使用跨进程的时候要注意obj不能为null
 */
 public Object obj;
/**
 * 在使用跨进程通讯Messenger时,能够肯定须要谁来接收
 */
 public Messenger replyTo;
/**
 * 在使用跨进程通讯Messenger时,能够肯定须要发消息的uid
 */
 public int sendingUid = -1;
/**
 * 若是数据比较多,能够直接使用Bundle进行数据的传递
 */
 Bundle data;
复制代码

其中关于what的值为何不会冲突的缘由是,以前咱们讲过的handler是与线程进行绑定的。也就是说不一样消息循环消息的发送,处理的线程是不同的。固然是不会冲突的。对于Messenger,由于涉及到Binder机制,这里就不过多的描述了,有兴趣的小伙伴能够自行查询相关资料学习。post

建立消息的方式

官方建议使用Message.obtain()系列方法来获取Message实例,由于其Message实例是直接从Handler的消息池中获取的,能够循环利用,没必要另外开辟内存空间,效率比直接使用new Message()建立实例要高。其中具体建立消息的方式,我已经为你们分好类了。具体分类以下:学习

//无参数
public static Message obtain() {...}
//带Messag参数
public static Message obtain(Message orig) {}
//带Handler参数
public static Message obtain(Handler h) {}
public static Message obtain(Handler h, Runnable callback){}
public static Message obtain(Handler h, int what){}
public static Message obtain(Handler h, int what, Object obj){}
public static Message obtain(Handler h, int what, int arg1, int arg2){}
public static Message obtain(Handler h, int what,int arg1, int arg2, Object obj) {}
复制代码

其中在Message的obtain带参数的方法中,内部都会调用无参的obtain()方法来获取消息后。而后并根据其传入的参数,对Message进行赋值。(关于具体的obtain方法会在下方消息池实现原理中具体描述)ui

消息池实现原理

既然官方建议使用消息池来获取消息,那么在了解其内部机制以前,咱们来看看Message中的消息池的设计。具体代码以下:this

private static final Object sPoolSync = new Object();//控制获取从消息池中获取消息。保证线程安全
private static Message sPool;//消息池
private static int sPoolSize = 0;//消息池中回收的消息数量
private static final int MAX_POOL_SIZE = 50;//消息池最大容量
复制代码

从Message的消息池设计,咱们大概能看出如下几点:spa

  1. 该消息池在同一个消息循环中是共享的(sPool声明为static),
  2. 消息池中的最大容量为50,
  3. 从消息池获取消息是线程安全的。

从消息池中获取消息

在上文中,咱们已经知道了在使用消息池得到消息时,都会调用无参的obtain()方法。具体代码以下:线程

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; //从新标识当前Message没有使用过
                sPoolSize--;
                return m;
            }
        }
        return new Message();//若是为空直接返回
    }
复制代码

从上述代码中,咱们能够了解,也就是当前 消息池不为空(sPool !=null)的状况下,那么咱们就能够从消息池中获取数据,相应的消息池中的消息数量会减小。消息池的内部实现是以链表的形式,其中spol指针指向当前链表的头结点,从消息池中获取消息是以移除链表中sPool所指向的节点的形式,具体原理以下图所示: 设计

获取消息.png

回收消息到消息池

在Meaage的消息回收中,消息的实际回收方法是recycleUnchecked()方法,具体以下图所示:

void recycleUnchecked() {
	    //用于表示当前Message消息已经被使用过了
        flags = FLAG_IN_USE;
        //状况以前Message的数据
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
		//判断当前消息池中的数量是否是小于最大数量,其中 MAX_POOL_SIZE=50
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;//记录当前消息池中的数量
            }
        }
    }
复制代码

在recycleUnchecked()方法中,大体分为三步,第一步将该条回收的消息状态设置为正在使用,第二步将Message全部的存储信息都变为初始值,第三步,若是当前消息池仍可以存储回收的消息,那么就将消息存储在消息池中。其中将回收消息加入消息池中是使用链表的形式,具体回收消息到消息池以下图所示:

加入消息.png

Message 消息回收时机

这里为了方便你们梳理逻辑,我提早将几种会调用消息进行回收的状况都描述出来了,具体的状况以下所示:

当Handler指定删除单条消息,或全部消息的时候

void removeMessages(Handler h, int what, Object object)
void removeMessages(Handler h, Runnable r, Object object)
void removeCallbacksAndMessages(Handler h, Object object)
复制代码

当使用Handler删除某条消息的时候,会分别调用MessageQueue的

  • removeMessages(Handler h, int what, Object object)
  • removeCallbacksAndMessages(Handler h, Object object)
  • removeMessages(Handler h, Runnable r, Object object)

这三个方法逻辑比较相似。这里直接选取removeCallbacksAndMessages()方法来进行讲解。具体代码以下:

void removeCallbacksAndMessages(Handler h, Object object) {
        if (h == null) {
            return;
        }
        synchronized (this) {
            Message p = mMessages;
            //  第一次循环
            while (p != null && p.target == h
                    && (object == null || p.obj == object)) {
                 //下面操做会将知足回收条件的消息,从消息队列中移除
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }
            // 第二次循环
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && (object == null || n.obj == object)) {
                    //下面操做会将知足回收条件的消息,从消息队列中移除
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
复制代码

在removeCallbacksAndMessages(Handler h, Object object)方法中,在该方法中分别进行了两次循环,确定有不少读者朋友会很好奇,为何这里会进行两次循环呢?下面我就具体来说解一下。

咱们都知道,在Handler机制中,多个handler对应同一个MessageQueue对应同一个Looper,Handler与MessageQueue与Looper之间的关系是N:1:1。也就是说在MessageQueue中咱们能够有多个不一样Handler发送的Message。那么咱们再结合上面的代码,咱们来分析这两次循环。

第一次循环

根据上文对代码的理解,第一次循环会将MessageQueue中,当前Handler发送的全部消息移除,注意!!!!!!!!!这里并不会将整个MessageQueue中的当前Handler发送的消息所有移除,而是在遍历过程当中,若是有其余Handler发送的消息,那么就会将mMessages指向头结点并跳出循环。以下图所示:

第一次循环.png

第二次循环

通过上文的分析,咱们已经知道了,在进行第一次循环后,已经将在removeCallbacksAndMessages方法执行时全部对应的Handler发送的消息移除掉了,可是MessageQueue中可能任然会残留没有移除掉的消息。那么第二次循环,根据代码来理解的话,咱们能够获得下图:

第二次循环.png

因此为了保证将整个MessageQueue中该Handler发送的消息所有被移除,在第一次循环移除以后,咱们必需要再执行一次循环移除操做

ps:很是感谢@承香墨影指出的错误的地方,以前没有考虑到同步的关系,误导了你们。心里感到无比的愧疚。

当Loooper取出消息时

public static void loop() {
		 //省略部分代码
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
			//省略部分代码
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
		    //省略部分代码
		    
		    //回收消息
            msg.recycleUnchecked();
        }
    }
复制代码

咱们都知道消息的取出是经过Looper类中的loop方法。从代码中咱们能够看出,当消息取出并执行相应操做后。最后会将消息回收。

当Looper取消循环消息队列的时候

public void quitSafely() { mQueue.quit(true);}
public void quit() { mQueue.quit(false); }
复制代码

当退出消息队列的时候,也就是调用Loooper的quitSafely()或quit()方法,从代码中咱们能够看出,会调用其内部的MessageQueue的quit(boolean safe)方法。咱们继续跟踪代码。

void quit(boolean safe) {
        if (!mQuitAllowed) {//注意,主线程是不能退出消息循环的
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {//若是当前循环消息已经退出了,直接返回
                return;
            }
            mQuitting = true;
			
            if (safe) {//若是是安全退出
                removeAllFutureMessagesLocked();
            } else {//若是不是安全退出
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
复制代码

在MessageQueue的quit(boolean safe)方法中,会将mQuitting (用于判断当前消息队列是否已经退出)置为true,同时会根据当前是否安全退出的标志 (safe)来走不一样的逻辑,若是安全则走removeAllFutureMessagesLocked()方法,若是不是安全退出则走removeAllMessagesLocked()方法。下面分别对这两个方法进行讨论。

非安全退出
private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
复制代码

非安全退出其实很简单,就是将全部消息队列中的消息所有回收。具体示意图以下所示:

回收所有消息.png

安全退出
private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;//当前队列中的头消息
        if (p != null) {
            if (p.when > now) {//判断时间,若是Message的取出时间比当前时间要大直接移除
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {//继续判断,取队列中全部大于当前时间的消息
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {//将全部全部大于当前时间的消息的消息回收
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }
复制代码

观察上诉代码,在该方法中,会判断当前消息队列中的头消息的时间是否大于当前时间,若是大于当前时间就会removeAllMessagesLocked()方法(也就是回收所有消息),反之,则回收部分消息,同时没有被回收的消息任然能够被取出执行。具体示意图以下所示:

回收部分消息.png

当消息队列退出的,可是仍然发送消息过来的时候

在Looper调用quit()方法时,也就是Looper退出消息循环的时候,咱们已经知道了其内部会调用MessageQueue的quit(boolean safe)方法。当MessageQueue退出的时候,会将mQuitting置为true。那么当对应的Handler发送消息时,咱们都知道会调用MessageQueue的enqueueMessage(Message msg, long when)方法。那么如今咱们观察下列代码:

boolean enqueueMessage(Message msg, long when) {
	   ...省略部分代码
        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;
            }
            ...省略部分代码
     }
复制代码

观察该代码咱们得知,当循环消息退出的时候,若是这个时候Handler继续发送消息来。会将该消息回收。可是如今这里有个问题。既然咱们的消息队列已经结束循环了。那么咱们回收该消息又有什么用呢?咱们又不能从新的开启消息循环。不知道Google这里为何会这么设计。

总结

  • 在使用Handler发消息时,建议使用Message.obtin()方法,从消息池中获取消息。
  • 在Message中消息池是使用链表的形式来存储消息的。
  • 在Message中消息池中最大容许存储50条的消息。
  • 在使用Handler移除某条消息的时候,该消息有可能会被消息池回收。(会判断消息池是否仍然能存储消息)
相关文章
相关标签/搜索