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的话,那他们的关系如下图所示:

即:

  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,然后互相发送消息
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/20139,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?