Android中的异步消息处理机制

这也是Android中老生常谈的一个话题了,它本身并不是很复杂,可是面试官比较喜欢问。本文就从源码再简单的理一下这个机制。也可以说是理一下HandlerLooperMessageQueue之间的关系。

单线程中的消息处理机制的实现

首先我们以Looper.java源码中给出的一个例子来分析一下在单线程中如何使用这个机制:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          Looper.loop();
      }

 }
复制代码

Looper.prepare()

上面只涉及到HandlerLooperMessageQueue呢?我们来看一下Looper.prepare():

private static void prepare(boolean quitAllowed) {
    ...
    sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

很简单,即new Looper,然后把它放到ThreadLocal<Looper>中(ThreadLocal保存在线程的私有map中)。继续看一下Looper的构造方法:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    ...
}
复制代码

即,在这里MessageQueue作为Looper的成员变量被初始化了。所以 一个Looper对应一个MessageQueue 。 ok,到这里Looper.prepare()所涉及的逻辑以及浏览完毕,继续看一下new Handler():

new Handler()

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
复制代码

Handler会持有一个Looper, 那我们看一下这个Looper来自于哪里: Looper.myLooper()

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
复制代码

即会从当前线程的私有map中取出ThreadLocal<Looper>。所以Handler默认持有当前线程的Looper的引用。如果当前线程没有Looper,那么Handler就会构造失败,抛出异常。其实可以在构造Handler时指定一个Looper,下面会讲到这个。

在持有当前线程的Looper的引用同时,Handler在构造时也会获取Looper的成员变量MessageQueue,并持有它。 如果你在一个线程中同时new多个Handler的话,那他们的关系如下图所示:

即:

扫描二维码关注公众号,回复: 4759982 查看本文章
  1. LooperMessageQueue存放在当前线程的ThreadLocal
  2. Handler持有当前线程的LooperMessageQueue

Looper.loop()

这个方法可以说是核心了:

public static void loop( ) {
    final Looper me = myLooper();
    //...
    final MessageQueue queue = me.mQueue;
    //...
    for (;;) {
        Message msg = queue.next(); // might block
        //…
        msg.target.dispatchMessage(msg);
        //...
    }
}
复制代码

Looper不断检查MessageQueue中是否有消息Message,并调用msg.target.dispatchMessage(msg)处理这个消息。 那msg.target是什么呢?其实它是Handler的引用。

msg.target.dispatchMessage(msg)会导致Handler.handleMessage()的调用,其实到这里单线程中的消息处理机制模型已经有了一个大致的轮廓了,接下来就需要弄清楚

  1. msg.target是在哪里赋值的?
  2. 消息是如何插入到MessageQueue中的?

发送一个消息

Handler发送一个最简单的消息为例:

handler.sendEmptyMessage(0)
复制代码

这个方法最终调用到的核心逻辑是:

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

Handler会把Messagetarget设置为自己,并把消息放入到当前线程的MessageQueue中。

这样Looper.loop()方法中就可以从MessageQueue中取出消息,并把消息发送到msg.target(Handler)去处理,其实msg.target.dispatchMessage(msg)会调用Handler.handleMessage()方法。

到这里就完成了整个消息处理模型的分析。其实 整个Android主线程的UI更新都是建立在这个模型之上的

用下面这个图总结一下整个运行机制:

多线程中的消息处理机制的应用

举一个最典型的例子: 在下载线程中完成了下载,通知主线程更新UI。怎么做呢?

明白了上面Handler/MessageQueue/Looper的关系后,我们只需要往主线程的MessageQueue中发送一个更新UI的Message即可,那怎么往主线程发消息呢?

指定Handler所依附的Looper

对,只需要在构造Hander时把主线程的Looper传递给它即可:

    downLoadHandler = Handler(Looper.getMainLooper())
复制代码

这样downLoadHandler.sendEmptyMessage(2)就会发送到主线程的MessageQueue中。handler.handleMessage()也将会在主线程中回调。

其实更简单的是在主线程中保存一个Handler成员变量,子线程直接拿这个Handler发消息即可。

但在多线程使用Handler时因为涉及到线程切换和异步,要注意内存泄漏和对象可用性检查。比如在更新UI时Activityfinish状态,这时候就需要你的update是否可以继续执行等。

线程通信

运用这个模型我们可以很方便的进行线程通信:

  1. 需要通信的两个线程都建立Looper
  2. 双方分别持有对方的handler,然后互相发送消息

欢迎关注我的Android进阶计划看更多干货

微信公众号:

猜你喜欢

转载自juejin.im/post/5c2c87d1e51d455de37754d8