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

Android Handler机制之Message及Message回收机制
小松鼠.jpg

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

前言

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

Message中可以携带的信息

Message中可以携带的数据比较丰富,下面对一些常用的数据进行了分析。

/**
 * 用户定义的消息代码,以便当接受到消息是关于什么的。其中每个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机制,这里就不过多的描述了,有兴趣的小伙伴可以自行查询相关资料学习。

创建消息的方式

官方建议使用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方法会在下方消息池实现原理中具体描述)

消息池实现原理

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

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的消息池设计,我们大概能看出以下几点:

  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所指向的节点的形式,具体原理如下图所示:

Android Handler机制之Message及Message回收机制
获取消息.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所有的存储信息都变为初始值,第三步,如果当前消息池仍能够存储回收的消息,那么就将消息存储在消息池中。其中将回收消息加入消息池中是使用链表的形式,具体回收消息到消息池如下图所示:

Android Handler机制之Message及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)方法中,在该方法中分成了两步,

  • 第一步:回收满足条件的第一条消息。同时将该消息从消息队列中移除。
    在第一步中,我们可以看出会循环遍历消息队列中的消息找到p.target == h&&((object == null || p.obj == object),然后进行回收,也就是说在第一步中,会移除对应的Handler。(在Handler机制中,多个handler对应同一个MessageQueue,对应同一个Looper,Handler与Message与Looper之间的关系是N:1:1)
  • 第二步:回收已经回收的第一条消息之后所有满足条件的消息。同时将这些消息从消息队列中移除。

思考:为什么不直接走第二步回收消息就行了。反正满足条件的消息都会移除,为毛要先移除第一条,在接着移除后面的消息(这里有点困惑,如果大家有理解的,望大家指出)。

这里要大家要注意的是,如果object==null,那么就会移除消息队列中所有对应Handler发送的消息所有mesaage.object==null的消息。默认情况下,我们都是不会对Message.obj进行赋值的。所以默认情况下,如果你传入object =null,有可能会移除消息队列中的所有的消息。

当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;
    }

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

Android Handler机制之Message及Message回收机制
回收全部消息.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()方法(也就是回收全部消息),反之,则回收部分消息,同时没有被回收的消息任然可以被取出执行。具体示意图如下所示:

Android Handler机制之Message及Message回收机制
回收部分消息.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移除某条消息的时候,该消息有可能会被消息池回收。(会判断消息池是否仍然能存储消息)