Handler 源码分析及常见问题

2020-08-07  本文已影响0人  wuchao226

使用 Handler 的主要场景是子线程完成耗时操作的过程中,通过 Handler 向主线程发送消息 Message,用来刷新 UI 界面。

new Handler() 源码

final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

在无参构造器里调用了重载的构造方法并分别传入 null 和 false。并且在构造方法中给两个全局变量赋值:mLooper 和 mQueue。

这两者都是通过 Looper 来获取,具体代码如下:

final MessageQueue mQueue;
/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

可以看出,myLooper 通过一个线程本地变量中的存根,然后 mQueue 是 Looper 中的一个全局变量,类型是 MessageQueue 类型。

接下来的分析重点就是这个 Looper 是什么?以及何时被初始化?

Looper 介绍

当我们打开一个 Activity 之后,只要我们不按下返回键 Activity 会一直显示在屏幕上,也就是 Activity 所在进程会一直处于运行状态。实际上 Looper 内部维护一个无限循环,保证 App 进程持续进行。

Looper初始化

ActivityThread 的 main 方法是一个新的 App 进程的入口,其具体实现如下:

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

  ActivityThread thread = new ActivityThread();
  thread.attach(false, startSeq);

  if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
  }
  ... 
   Looper.loop();
}

解释说明:

prepareMainLooper 方法如下:


在 prepareMainLooper 中调用 prepare 方法创建 Looper 对象,仔细查看发现其实就是 new 出一个 Looper。核心之处在于将 new 出的 Looper 设置到了线程本地变量 sThreadLocal 中。也就是说创建的 Looper 与当前线程发生了绑定。

Looper 的构造方法如下:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

可以看出,在构造方法中初始化了消息队列 MessageQueue 对象。
prepare 方法执行完之后,会在图中 3 处调用 myLooper() 方法,从 sThreadLocal 中取出 Looper 对象并赋值给 sMainLooper 变量。

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

注意:
图中 2 处在创建 Looper 对象之前,会判断 sThreaLocal 中是否已经绑定过 Looper 对象,如果是则抛出异常。这行代码的目的是确保在一个线程中 Looper.prepare() 方法只能被调用 1 次。比如以下代码:

执行上述代码程序会秒崩,打印日志如下:


注意:

不是说调用 2 次 prepare 才会抛异常吗?为什么 MainActivity 中只调用了 1 遍就导致程序崩溃? 这是因为在 MainActivity 所在进程被创建时,Looper 的 prepare 方法已经在 main 方法中调用了 1 遍。这会直接导致一个非常重要的结果:

也就是说 UI 线程中只会存在 1 个 MessageQueue 对象,后续我们通过 Handler 发送的消息都会被发送到这个 MessageQueue 中。

Looper 的作用

用一句话总结 Looper 的作用就是:不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务。

在 ActivityThread 的 main 方法中,除了调用 Looper.prepareMainLooper 初始化 Looper 对象之外,还调用了 Looper.loop 方法开启无限循环,Looper 的主要功能就是在这个循环中完成的。

很显然,loop 方法中执行了一个死循环,这也是一个 Android App 进程能够持续运行的原因。

图中 1 处不断地调用 MessageQueue 的 next 方法取出 Message。如果 message 不为 null 则调用图中 2 处进行后续处理。具体就是从 Message 中取出 target 对象,然后调用其 dispatchMessage 方法处理 Message 自身。那这个 target 是谁呢?查看 Message.java 源码可以看出 target 就是 Handler 对象,如下所示:

public final class Message implements Parcelable {
   public int what;
   public int arg1;
   public int arg2;
   ...
   Handler target;
   ...
}

Handler 的 dispatchMessage 方法如下:

 /**
  * Handle system messages here.
  */
 public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                 return;
            }
        }
        handleMessage(msg);
    }
}

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

可以看出,在 dispatchMessage 方法中会调用一个空方法 handleMessage,而这个方法也正是我们创建 Handler 时需要覆盖的方法。那么 Handler 是何时将其设置为一个 Message 的 target 的呢?

Handler 的 sendMessage 方法

Handler 有几个重载的 sendMessage 方法,但是基本都大同小异。我用最普通的 sendMessage 方法来分析,代码具体如下:

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

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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

可以看出,经过几层调用之后,sendMessage 最终会调用 enqueueMessage 方法将 Message 插入到消息队列 MessageQueue 中。而这个消息队列就是我们刚才分析的在 ActivityThread 的 main 方法中通过 Looper 创建的 MessageQueue。

Handler 的 enqueueMessage 方法

可以看出:

Handler 的 post(Runnable) 与 sendMessage 有什么区别

post(Runnable) 的源码实现如下:

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

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

实际上 post(Runnable) 会将 Runnable 赋值到 Message 的 callback 变量中,那么这个 Runnable 是在什么地方被执行的呢?Looper 从 MessageQueue 中取出 Message 之后,会调用 dispatchMessage 方法进行处理,再看下其实现:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
private static void handleCallback(Message message) {
    message.callback.run();
}

可以看出,dispatchMessage 分两种情况:

Looper.loop() 为什么不会阻塞主线程

Looper 中的 loop 方法实际上是一个死循环。但是我们的 UI 线程却并没有被阻塞,反而还能够进行各种手势操作。在 MessageQueue 的 next 方法中,有如下一段代码:

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

private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/

nativePollOnce 方法是一个 native 方法,当调用此 native 方法时,主线程会释放 CPU 资源进入休眠状态,直到下条消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作,这里采用的 epoll 机制。关于 nativePollOnce 的详细分析可以参考:nativePollOnce函数分析

Handler 的 sendMessageDelayed 或者 postDelayed 是如何实现的

在向 MessageQueue 队列中插入 Message 时,会根据 Message 的执行时间排序。而消息的延时处理的核心实现是在获取 Message 的阶段,接下来看下 MessageQueue 的 next 方法。

Message next() {
  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) {
           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;
       }
   }
   ...
}

if (now < msg.when) 表示从 MessageQueue 中取出一个 Message,但是当前的系统时间小于 Message.when,因此会计算一个 timeout,目的是实现在 timeout 时间段后再将 UI 线程唤醒,因此后续处理 Message 的代码只会在 timeout 时间之后才会被 CPU 执行。

注意:在上述代码中也能看出,如果当前系统时间大于或等于 Message.when,那么会返回 Message 给 Looper.loop()。但是这个逻辑只能保证在 when 之前消息不被处理,不能够保证一定在 when 时被处理。

总结

面试必问的Handler
Handler 常见面试题

上一篇 下一篇

猜你喜欢

热点阅读