Android基础补完系列之Handler

今天我们来一起学习下Android基础里的Handler。

Handler的工作流程是:Handler发送Message消息到MessageQueue,然后通过Looper的不停轮询从MessageQueue里取出消息执行,下面我们来分别看这几个步骤。

Looper的创建以及轮询的开始

首先我们知道Handler的使用必须要有Looper,然后我们在主线程里并没有直接接触到Looper,那么主线程Looper的创建以及轮训是在哪里实现的呢?

ActivityThread.java
  
public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();

    ···

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThread里的main函数会在应用进程被fork出来之后执行到,在这里他替我们创建好了主线程Looper。

然后看创建Looper的具体方法:

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
}

其中sThreadLocal是线程为键的结构,用它来保证每个线程只有一个Looper,然后在创建Looper对象的时候,会同事创建出MessageQueue对象。也就是说每个线程最多只能有1个Looper,从而最多只能有一个MessageQueue。

然后在看Handler的构造方法:

public Handler(@Nullable Callback callback, boolean async) {
    ···
    mLooper = Looper.myLooper();
    ···
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

当我们不传Looper对象的时候,默认是使用构造方法所在的线程来创建Handler,这里也能看出Handler对于线程来说不是唯一的。

Handler发送Message到MessageQueue

当我们创建出Handler对象之后,然后使用其的send或者post发送消息去处理:

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendEmptyMessage(int what){
  return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
  Message msg = Message.obtain();
  msg.what = what;
  return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendMessage(@NonNull Message msg) {
  return sendMessageDelayed(msg, 0);
}

public boolean sendMessageAtTime(@NonNull 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);
}

他最终都是将我们发送的消息组成一个Message对象,然后走到如下方法去发送消息:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

注意,这里有个属性mAsynchronous,如果Handler是异步的,那么所有通过这个Handler发出的消息都是异步的。异步消息有何不同我们后面在介绍。

MessageQueue将消息入队

当一个消息被发送往MessageQueue之后,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;
}

先是做一些检查,然后如果消息MessageQueue以及处于退出状态了,返回false;

然后检查消息的执行,如果小于当前时间,就说明消息需要马上被执行,就插入到队列的最前面;

否则插入到第一条晚于这条消息的执行时间之前。

这个逻辑可以看出消息是按照时间顺序来排列的单链表,时间相同的消息后插入的在后面。

然后可以看到,mBlocked为真的时候,如果当前消息是插在队列最前面,或者当前处于同步屏障状态(p.target == null就代表处于同步屏障)且消息是消息列表里最早的异步消息异步消息(如果有比这条消息更早的异步消息,会在for循环里将needWake置为false),那么会唤醒消息队列。唤醒的阻塞处以及mBlocked的逻辑在MessageQueue取消息处。

Looper的轮训处理消息

下面来看Looper的loop方法:

public static void loop() {
    ···
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
				···
  			msg.target.dispatchMessage(msg);
        ···
        msg.recycleUnchecked();
    }
}

他的核心逻辑如上,不停地冲消息队列里面取消息,然后执行这个消息,之心完了之后将消息回收放入到消息对象池里。

如果消息返回为空,循环就终止了。

MessageQueue取消息逻辑

下面这个方法可以说是Handler体系里最关键的一个方法了:

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}
  1. 首先检查ptr指针如果为0就返回,上面的注释解释了这种情况发生于Looper以及退出了,然后重新调用Looper。loop()这种场景;

  2. 开始循环,如果nextPollTimeoutMillis不为0,就处理Binder的遗留任务,其中nextPollTimeoutMillis可以理解为最近的一个需要处理的消息与当前时间的时间间隔,在第一次循环的时候为这个变量为0。

  3. 调用nativePollOnce(ptr, nextPollTimeoutMillis)阻塞住,这个方法是使用linux的epoll机制,他会等待nextPollTimeoutMillis时间,或者被调用到nativeWake(mPtr)方法唤醒,这个方法在有消息入队的时候可能会被调用到。然后这个方法的等待不会占用住CPU。在第一次循环的时候等待时间为0,所以会继续往下执行;

  4. 然后判断当前是不是出于同步屏障状态,如果不是,msg就取当前消息列表的第一个消息,如果是,那么就取消息列表里的第一个异步消息;从之前的消息入队的逻辑分析来看,这个逻辑所取出的消息就是下一个要被执行的消息。

  5. 然后如果取到的消息不为空,就判断这条消息的执行时间是不是大于当前时间,如果是,就计算出差值为nextPollTimeoutMillis,准备休眠等待消息;如果为否,就很明确了,说明这条消息需要马上被执行,然后就返回这条消息,他会在loop里被执行。

  6. 我们注意到在返回消息之前,将mBlocked只为了false,而mBlocked在消息入队的时候被使用过,他的值能够决定消息入队之后需不需要唤醒,而在这里看到当有消息执行的时候说明不是处于休眠状态,自然就不需要唤醒。

  7. 如果第4步取到的消息为空,就说明现在没有消息要被执行,然后将nextPollTimeoutMillis置为-1,当他为-1的时候,nativePollOnce(ptr, nextPollTimeoutMillis)会一直等待着,知道被唤醒。

  8. 然后继续往下,注意,能走到这里说明nextPollTimeoutMillis要么是-1,要么是一个正值,也就是当前没有消息要马上执行,准备休眠了;然后判断消息是否退出了,退出了就返回空回收,否则继续往下执行;

  9. 然后获取pendingIdleHandlerCount,如果他初始为负(这只会发生在fun循环的第一次执行),且当前的第一个消息晚于马上要执行的时间,就获取空闲任务的大小,注意,这里判断第一条消息的目的是为了防止当前处于同步屏障而对消息的形式误判;所以执行空闲任务的条件是:

    • for循环的第一次执行,
    • 当前的消息列表为空,或者首条消息的时间晚于当前时间。
  10. 如果当前的空闲任务也为空,就开始下一次循环,然后由于我们之前获取到的nextPollTimeoutMillis为-1或者正数,这就说明线程要真的开始休眠了,那么就将mBlocked置为true,一共之后有消息入队的时候能够唤醒;

  11. 然后就执行IdleHandler的全部任务,在执行完了之后将pendingIdleHandlerCount和nextPollTimeoutMillis都置为0,pendingIdleHandlerCount等于0后,这一次执行next函数就不会再执行IdleHandler了,而nextPollTimeoutMillis为0之后,下一次循环就不会休眠,这是防止执行IdleHandler好使了,要重新检查当前时间与下一条消息的执行时间。

  12. 进入到下一次循环,注意有两种情况会进入到下一次循环:

    • 确实准备休眠了,nextPollTimeoutMillis为-1或者正数;
    • 执行了IdleHandler,然后重新走获取消息待执行消息的流程,唯一的区别是不会在执行IdleHandler了;

    对于第一种情况,会进入休眠,对于第二种,结果会是拿到待执行消息返回(这种情况是IdleHandler执行的时间里正好有新的消息入队了或者下一条待执行的消息到时间了),还或者依旧没有马上需要执行的消息,进入下一次循环进行休眠等待。

如上的分析可以看到,next执行的结果分为3种情况:

  • 休眠在nativePollOnce(ptr, nextPollTimeoutMillis)处;
  • 返回待执行消息去执行;
  • 返回空,这只发生在Looper退出的情况。

然后对于IdleHandler在调用next的时候最多执行一次的机制,能够避免一直执行IdleHandler导致Handler没有消息,但是一直在执行IdleHandler这种情况。示例代码如下,如果消息队列里没有消息,如下代码自会执行一次而不会死循环。

looper.getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {

    Log.d(TAG, "queueIdle: ");

    looper.getQueue().addIdleHandler(this);
    return false;
    }
});

到这里后,Handler的主要东西就串联起来了,Handler发送消息到MessageQueue,然后Looper不停地轮询从MessageQueue取出消息执行,如果消息队列为空或者下一条待执行的消息执行时间王宇当前,那么会选择执行IdleHandler,然后休眠。

最后我们注意到,在执行IdleHandler的时候,并没有直接使用mIdleHandlers来做循环,而是将其复制到mPendingIdleHandlers来执行,这是由于执行可能是一个耗时操作,要将其防御同步块之外,防止由于这个耗时操作长时间持有消息队列的锁,从未导致消息的入队等被阻塞,然后如果使用mIdleHandlers来做循环,可能会在执行IdleHandler的时候,有IdleHandler被加入到mIdleHandlers,从而导致一些异常情况。

其实观察下MessageQueue的锁,他锁住的基本都是对mMessage和mIdleHandlers的出入队,以及读取的操作。

Looper的退出

然后我们来看看Looper的退出,首先我们在创建Looper的时候,会指定这个Looper是否允许退出,其中主线程的Looper在创建的时候指定的不允许退出,子线程的Looper默认允许退出。

public static void prepare() {
    prepare(true);
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
public void quit() {
    mQueue.quit(false);
}

可以看到quitAllowed传到了MessageQueue,然后在Looper退出的时候,是调用的MessageQueue的退出函数。

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

如果允许退出,就将mQuitting变量置为真,然后根据是否安全退出来决定一出哪些消息,其中mQuitting我们之前在分析MessageQueue.next()的时候使用了这个变量,当暂时没有消息要执行的时候,会判断这个变量来决定是否返回空,如果返回空,Looper.loop()的死循环就退出了,然后Looper就相当于终结了。对于主线程来说,当loop退出了的时候,属于异常行为,系统会直接抛出异常。

然后我们来看Looper安全退出与非安全退出的区别,他们的区别在于推出的时候调用的移除消息的方法不同:

if (safe) {
    removeAllFutureMessagesLocked();
} else {
    removeAllMessagesLocked();
}
private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
      if (p.when > now) {
        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);
      }
    }
}

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
      Message n = p.next;
      p.recycleUnchecked();
      p = n;
    }
    mMessages = null;
}

可以看到,如果是非安全退出,会直接移除掉所有的消息,这种就可能导致被添加的消息已经到了执行时间,但是被移除了导致没有被执行;而安全退出,是移除执行时间when大于当前时间的消息,然后根据之前对MessageQueue.next()的分析,在执行完这些消息之前不会走到判断mQuitting属性从而推出Looper的场景,所以能保证未被移除的消息能够被执行完。

最后注意到推出的时候调用了nativeWake(mPtr),这是防止调用了退出,但是MessageQueue在休眠,从而导致退出不及时的情况。

同步屏障

对Looper机制的创建退出以及工作流程了解之后,我们来看看Looper比较重要的一个概念——同步屏障。

我们之前的分析里,对于消息并没有看到优先级的概念,消息的执行顺序就是按照when字段来决定的,那么当有一些需要优先执行的消息,比如更新UI的时候,高如何让消息能够先执行呢?这就是同步屏障所处理的事,我们先看如下方法:

public int postSyncBarrier() {
  return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

这个方法的逻辑很简单,就是插入一条消息到链表里,他的位置在when小于等于他的消息之后,但是这条消息很特殊,就是他没有target属性。而我们通过handler发送的消息是有target属性的,没有target属性就是同步屏障消息的标志。然后我们在重新看next方法的如下代码:

if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}

在拿到消息头之后,如果消息的target为空,那么在链表里找到最早的异步消息去处理,这就是同步屏障的含义,屏蔽同步消息,从而使异步消息优先被执行。

然后看移除同步屏障:

public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

逻辑很简单,先根据保存的token找到同步屏障消息,然后将同步屏障消息移除,然后如果满足如下几个条件,唤醒next的阻塞:

  • 被移除的同步消息在消息链表的最前面;
  • 下一条消息为空或者下一条消息不是同步屏障消息;
  • Looper没有退出。

对于第一个条件,如果这个同步屏障消息不在最前面,那么当前同步屏障并没有生效,当然不需要唤醒;

对于第二个条件,这个条件我没想很明白。。。

对于第三个条件,显然的,处于退出状态的时候,next方法一定不处于阻塞状态,所以不需要唤醒。

关于同步屏障机制的使用,等到后续View绘制的文章来具体介绍。

IdleHandler

这个任务的作用是在当前Looper线程处于空闲状态的时候,去执行一些不需要马上执行的一些任务。

然后对于有Looper轮训的线程,他的运行的时候一定是处理消息,或者执行IdleHandler,或者休眠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

星星笑语_

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值