Handler学习笔记(二)

700 阅读17分钟

回顾

 在上一篇笔记中,主要了解了Handler的作用和用法,Handler的基本原理,Looper的创建时间和机制,MessageQueue的创建,Looper如何实现消息的循环分发。

  1. Handler的作用和用法

Handler的主要就是工作线程向主线程发送消息,更新UI。

 用法,Handler的两种基本发送消息的方式:

  • Handler.sendMessage(Message)
  • Handler.post(Runnable)
  1. Handler的基本原理:
  • Handler在主线程中创建,创建Handler的时候会给当前Handler绑定主线程的Looper对象,同时将Looper对象中的MessageQueue对象一并设置给当前Handler
  • 创建子线程的时候,可以将Handler对象传递给子线程,这样在子线程中执行完数据之后调用Handler中发送消息的方法,将消息添加到MessageQueue中,由于MessageQueue对象的实例是在主线程中,因此这个时候已经将子线程需要操作的UI对象的Message传递到主线程中了
  • Looper中通过loop方法,其内部有一个死循环,不断调用MessageQueuenext()方法,不断地从MessageQueu中获取Message对象,然后将Message分发给不同的Handler进行处理
  • MessageQueue中的next()方法其内部也是一个死循环,只有当MessageQueue退出的时候会返回null,其余时候有Message就获取Message,没有Message就调用本地方法阻塞线程,等待下一个消息的到来。
  1. Looper的创建时间和机制
  • Looper中只有一个private的构造方法,其接收一个quitAllowed参数用于表示是否允许Looper退出
  • 因此创建Looper对象不能通过构造方法,而是需要调用静态的prepare()方法或者prepareMainLooper()方法,这两个方法的内部都会调用prepare(boolean quitAllowed)的静态方法,它们的区别是:
    • prepare()方法内部直接调用prepare(true)方法,创建一个可退出的Looper
    • prepareMainLooper()方法内部调用prepare(false)方法,创建一个不可退出的Looper,同时会设置sMainLooper为当前Looper,这个方法是在APP启动的时候调用的。
  • 由于sMainLooper是一个静态的变量,当APP启动的时候会调用prepareMainLooper()方法设置这个变量的值,而且每次设置也会检查这个属性的值是否为空,不为空就会抛出异常,这样就保证了整个APP只会有一个sMainLooper,当APP启动后手动调用prepareMainLooper()方法就会报错,从而保证一个APP只能有一个主线程。
  • 当我们自己的线程也需要使用Looper的时候则只能调用prepare()方法创建一个可退出的Looper
  1. MessageQueue的创建
  • MessageQueue只有一个构造方法MessageQueue(boolean quitAllowed),可以通过这个构造方法创建一个MessageQueue
  • Looper中也是在Looper初始化的时候(通过prepare(quitAllowed)方法调用Looper的构造函数),通过调用MessageQueue(quitAllowed)来创建MessageQueue对象的
  • 创建Looper时的quitAllowed对象就这样传递给MessageQueue,这样,由于prepareMainLooper()方法传递的quitAllowed对象是false,也就导致主线程中的MessageQueue在正常情况下不能退出,这样在next()方法中就不会返回null,从而导致Looper中的loop()方法无法退出,从而实现loop不断读取Message
  • 但是普通方式创建的MessageQueue是可以退出。
  1. Looper如何实现消息的分发
  • 通过上面的了解,已经明确了在APP创建的时候就会创建Looper,从而创建MessageQueue,并且创建是不可退出的LooperMessageQueue
  • 创建完这两个对象之后,Looper就会调用loop方法,其内部有一个for(,,)死循环,不断地从MessageQueue中获取需要操作的Message对象
  • MessageQueue对象在UI线程中标记为不可退出的,loop()方法内部会调用MessageQueue内部的next()方法,next()方法内部也是一个for(,,)死循环,有消息就将到时间执行的消息返回给MessageQueue中,没有消息或者还没到消息执行的时间就阻塞线程,继续等待。
  • MessageQueue中的loop()方法获取到需要执行的Message之后,会查看Message中的target对象,这个对象用来表示需要处理自己的Handler,从而将这个消息分给指定的Handler,然后调用对应的HandlerdispatchMessage(msg)方法将消息分发出去。

Handler发送消息

通过sendMessage(message)发送消息

  1. 消息的创建:

 通常情况下我们通过以下方式创建一个消息(虽然Message的构造方法是公开的,但是官方建议通过obtain()或者obtainMessage()方法从一个可回收的对象池中创建一个Message):

Message message = Message.obtain();
message.what = 0;
message.obj = this.getClass().getSimpleName();
  1. Message类的基本信息
  • 通过查看源码可以发现Message使用public final来修饰,不能被继承,其实现了Parcelable序列化接口
  • what字段是给当前的Message添加的用户自定义的标识,通过这个标识来识别当前的Message,不同Handler发送的Message可以使用同一个标识,因为在Looper中会将不同的Meesgae发送给对应的Handler,彼此之间不会影响
  • 如果只需要做简单的操作,可以通过给arg1arg2设置数据来执行,比使用setData(Bundle)的成本更低
  • obj对象,我们一般会将需要操作的信息设置在这个字段中,但是从注释看这个在跨进程通信的时候仍然有部分需要注意的问题,需要后面解决。
  • when表示处理当前Message的时间,通过这个时间决定将当前的Message添加到队列中的哪个位置,同时也通过这个时间设置获取Message的时候的线程的阻塞时间
  • Bundle data;存储数据
  • Handler target;处理当前MessageHandlerMessageQueue中就是通过这属性分发消息的,同时将这个属性设置为null即是标识当前的Message为障栈
  • Message next;通过此属性实现链表的数据结构
  1. obtain()方法的源码:

obtain()方法共有以下重载方法:

public static Message obtain();
public static Message obtain(Message orig);
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);

 下面是obtain()无参方法的源码:

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();
}

 在上面的源码中,会在同步块里面判断sPool的值,然后将sPool取出来作为这次返回的Message,同时将sPool指向之前下一个Message。否则的话则是通过构造函数直接返回一个Message

 但是在之前的源码中已经了解了,官方建议通过这个函数来从可回收的对象池中创建Message,而sPool到目前为止还是空的,因此需要找一下sPool赋值的地方,最终在recycleUnchecked()方法中找到了:

void recycleUnchecked() {
    // 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;
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

 通过上面的源码,可以发现,当调用recycleUnChecked()的时候,会进行如下的操作:

  • 将当前的Message中的属性设置为默认值
  • 判断sPoolSize是否小于MAX_POOL_SIZE,也就是查看池中的元素是否小于最大值50,如果小于最大值,则将当前的Message添加到队列的首部.

 通过对上面源码的分析,可以知道Message中对象池的操作如下图所示:

Message中对象池的操作示意图
Message中对象池的操作示意图

 而对于上面recyclerUnchecked()方法的调用,首先是在recycle()方法中有使用,另外,在上一篇笔记中,在Looper中获取到一个Message并交给对应的Handler处理完成后,也会调用这个方法,如下所示:

//Looper 中 loop()方法的最后一行
msg.recycleUnchecked();

 查看obtain()其它重载函数的源码,可以发现,也只是首先通过调用obtain()无参的构造函数返回一个Message,然后将新的属性设置上去,如下是obtain(Handler h,Runnable callback)的方法源码:

public static Message obtain(Handler h, Runnable callback) {
    Message m = obtain();
    m.target = h;
    m.callback = callback;
    return m;
}

 如果需要拷贝出和一个Message一样的Message,可以通过copyFrom(Message o)实现,但是这个方法只是执行数据的浅拷贝,也不会赋值目标的Handlercallback,源码如下:

public void copyFrom(Message o) {
    this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
    this.what = o.what;
    this.arg1 = o.arg1;
    this.arg2 = o.arg2;
    this.obj = o.obj;
    this.replyTo = o.replyTo;
    this.sendingUid = o.sendingUid;
    if (o.data != null) {
        this.data = (Bundle) o.data.clone();
    } else {
        this.data = null;
    }
}

 通过源码可以看出,这个方法只会复制Message o的数据字段,并不会复制targetcallback字段。

 至此,已经了解了Message中属性和方法,主要是理解了Message中对象池的实现,使用对象池复用对象,避免了对象的频繁创建和销毁。

Handler.sendMessage()源码跟踪

 通过之前的笔记,已经了解了Message,MessageQueueLooper内部的一些源码和实现。下面是Handler.sendMessage()方法的源码部分。

 使用sendXXX方案发送消息的几个方法如下:

  1. sendMessage(Message)立即发送Message到消息队列
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}
  1. sendMessageDelayed(Message msg, long delayMillis)延时指定的时间后发送Message到队列
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
  1. sendMessageAtTime(Message msg, long uptimeMillis)指定发送Message到消息队列的时间,到时间后立即发送Message到消息队列:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    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);
}

 可以看到,发送消息的方法最终都会调用到sendMessageAtTime(Message msg,long uptimeMillis),然后在这个方法中再调用了enqueueMessage(queue,msg,uptimeMillis)方法。

  1. sendMessageAtFrontOfQueue(Message msg)立即发送Message到消息队列,而且是放在消息队列的最前面:
public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}

 可以看到,在这个方法中的处理逻辑和上面的sendMessageAtTime()方法中的逻辑是一样的,最后也是调用了enqueueMessage(queue,msg,0)这个方法,只是最后一个参数的值不一样,在这个方法中最后一个参数的值是0,也就是指定发送Message的时间为0,而在sendMessageAtTime()方法里面最后一个参数的值是当前时间加上指定间隔的时间。也就是说当时间为0的时候会把当前消息插入到消息列表的最前面。

  1. 下面是enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis)方法的源码:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

 可以看到,在这里首先给Messagetarget属性设置了值为当前这个Handler,在上一篇笔记中,当在Looperloop()方法中获取到下一条Message的时候,就是调用了msg.target.dispatchMessage(msg)这个方法来分发消息的。在这里我们就可以验证msg中的target就是发送当前Message的哪个Handler

 设置完target之后,会查看当前的Handler是否是异步的Handler,如果是异步的Handler,那么就设置当前Handler发送出去的Message也是异步的。这样就同步了HandlerMessage的异步状态。不过需要注意的是,我们仍然可以通过直接设置Message.setAsynchronous(boolean)来设置Message的异步状态,即时在一个同步的Handler中也可以这样,只不过这个方法需要在SDK version >= 22的时候才可以使用。也就是说:Handler的异步状态和Message的异步状态时可以不同的,Handler为同步的时候,Message为异步,Handler为异步的时候,Message肯定为异步。

  1. 设置完上面的属性值之后,就会调用queue.enqueueMessage(msg,uptimeMillis)方法,这个方法在MessageQueue中,下面是这个方法的源码:
boolean enqueueMessage(Message msg, long when) {
    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.");
    }
    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;
}

 上面的代码执行了如下的操作:

  • 首先会判断Message中的Handler对象是否为空,如果为空则会抛出异常,从上面的代码追踪来看,我们自己通过Handler发送的Message这里的Handler是不可能为空的。
  • 接着调用Message.isInUse()方法判断当前的Message是否正在使用中。结合下面的同步代码块中的msg.markInUse()方法和Message中的obtain()recycleUnchecked()可以知道,一个Messageobtain()中获取到加入到消息队列中这段时间是没有使用的状态;从加入到消息队列中一直到回收到Message中的对象池中直到下一次被obtain()方法获取这段时间处于使用中的状态,主要参考如下源码:
//Mesage 源码中:
//初始话默认为0
int flags;

//obtain方法中会清空flags的标记位,重置为0
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();
}
//recycleUnchecked()方法中会设置flags标记位为使用中
void recycleUnchecked() {
    // 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;
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

//MessageQueue中的enqueueMessage方法中会调用如下代码:
msg.markInUse();

//Message中的markInUse()方法:通过或运算标记当前的Message处于使用中的状态
void markInUse() {
    flags |= FLAG_IN_USE;
}
  • 接着进入到同步代码块中,首先判断当前的MessageQueue是否正在退出,如果正在退出,则打印异常信息,同时回收当前的Message,返回false表示没有将消息加入到消息队列中。
  • 如果没有退出,则标记当前Message为正在使用中,设置当前消息执行的时间为指定的时间。
  • 紧接着获取到当前消息队列中的头部消息,定义字段needWake表示是否需要唤醒线程。
  • 接下来开始判断:
    • 如果当前消息队列的头部消息是空的,也就是当前消息队列里面没有消息
    • 或者要插入的消息的执行时间为0,也就是这个消息要插入到当前消息队列的头部
    • 或者要插入的消息的执行时间小于当前消息队列头部消息的执行时间

      满足上面任意一个条件,就将当前的消息插入到消息队列的头部,源码如下:
if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
}

 设置头部消息的操作和在Message中对象池回收Message对象的操作是一致的,就是将要插入的Messagenext属性设置为当前消息队列的头部Message

 至于是否需要执行唤醒操作,则是根据mBlocked的值来进行判断的,如果当前线程处于阻塞状态,那么添加了新的头部消息之后就需要唤醒线程。根据上面的判断也可以知道,新加入的头部消息可能需要立即执行,所以需要唤醒线程。

mBlocked的初始值为false,在next()方法中循环查找下一个需要执行的Message的时候,如果已经没有消息,或者还没有到下一个消息的执行时间,同时没有在空闲时间执行的IdleHandler的时候,则会设置mBlocked的值为true,使线程进入阻塞状态。

  • 接下来进入到else的判断条件中,首先是代码中的注释:

 将Message插入到消息队列的中间,通常我们不必唤醒事件队列,除非队列的开头有一个屏障(障栈),并且消息是队列中最早的异步消息。这是因为异步的Message不受障栈的干扰,可以直接执行,源码如下:

needWake = mBlocked && p.target == null && msg.isAsynchronous();

 接下来进入到for循环的,在循环中主要是判断需要加入的Message应该添加到的为止,当p为空的时候说明已经到了队列的末尾,此时只需要将需要加入的Message添加到队列的尾部即可,如果when < p.when,此时需要将需要加入的Message添加到p的前面。

 接下来,需要对needWake的值再次进行判断,刚才判断的情况是队列首部是障栈并且需要加入的Message是异步的,我们就给needWake设置为true,这是不严谨的,因为异步的Message虽然不受障栈的影响,但是仍然受到时间的限制,所以在这里,我们需要遍历一个Message,判断一下这个Message是不是也是异步的,因为还有一种情况是:当前Message也是异步的,但是还没到执行时间,所以导致线程仍然在阻塞,如果遇到这种情况,结合上面的要加入的Message此时不应该在p的前面,所以此时应该让线程继续阻塞,也就需要设置needWake = false

 接下来查找到Message应该添加的位置,将Mesage添加进去就好了,源码如下:

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

 接下来如果此时仍然需要唤醒线程,就是用本地方法唤醒线程:

if (needWake) {
    nativeWake(mPtr);
}

 最后返回true表示Message已经成功添加进队列中:

return true;

 至此,我们已经了解了从Handler.sendMessage(Message)方法开始一直到将指定的Message添加到当前的MessageQueue的合适的位置。

Handler.post(Runnable)方法追踪

Handler.post(Runnable r)方法源码:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

 可以看到,在这个方法中,首先调用了getPistMessage(r)方法,下面是这个方法的源码:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

 上面的这个方法中,仍然是获取了一个Messgae,然后将这个Messagecallback属性赋值为传递进来的Runnable。在Mmessage类中,callback即是Runnable对象。

 通过这个方法将Runnable对象包装为一个Message,然后调用了sendMessageDelayed()方法将生成的Message添加到队列中。

Handler.postDelayed(Runnable r,long delayMillis)方法源码:

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

 可以看到,这个方法也就是通过getMessage(Runnable)方法生成一个Message,然后调用sendMessageDelayed()方法将生成的Message添加到MessageQueue队列当中。

postAtTime(Runnable r, long uptimeMillis)方法则是通过getMessage(Message)方法生成Message后调用sendMessageAtTime(Message,long uptimeMillis)方法来将Message插入到队列中的。

public final boolean postAtTime(Runnable r, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

postDelayed(Runnable r, Object token, long delayMillis)方法,通过调用getMessage(Runnable r,Object token)生成一个Message,并且将当前的Runnable设置给Messagecallback,将token设置给当前Messageobj,然后仍然是调用了sendMessageDelayed(Message,long delayMillis)Message添加到队列中:

private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}
public final boolean postDelayed(Runnable r, Object token, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}

postAtTime(Runnable r, Object token, long uptimeMillis)也是如出一辙,最终调用了sendMessageAtTime(Message msg,long uptimeMillis)

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

postAtFrontOfQueue(Runnable r)方法最终也是通过调用getMessage(Runnable r)来生成一个Message,然后调用sendMessageAtFrontOfQueue(Message)来将当前的Message添加到队列首部:

public final boolean postAtFrontOfQueue(Runnable r)
{
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}

 执行示意图如下:

Handler发送消息示意图
Handler发送消息示意图