• Android 异步消息处理机制终结篇 :深入理解 Looper、Handler、Message、MessageQueue四者关系


    版权声明:本文出自汪磊的博客,转载请务必注明出处。

    一、概述

    我们知道更新UI操作我们需要在UI线程中操作,如果在子线程中更新UI会发生异常可能导致崩溃,但是在UI线程中进行耗时操作又会导致ANR,这时异步消息处理机制就登场了,大体流程就是我们在UI线程创建一个Handler,子线程创建一个Message,利用Handler将Message发送到MessageQueue中,然后轮到Looper登场了,Looper负责从MessageQueue中不断获取Message,交给Handler处理,最后在Handler的handleMessage方法中处理相应操作即可。

    好了,大体说了一下异步消息处理机制的流程,Demo就不举例了,直接分析源码。

    二、源码层分析

    Looper源码分析

    Looper中最重要的就是prepare()以及loop()方法,首先看下prepare()方法:

     1 public static void prepare() {
     2         prepare(true);
     3 }
     4 
     5 private static void prepare(boolean quitAllowed) {
     6         if (sThreadLocal.get() != null) {
     7             throw new RuntimeException("Only one Looper may be created per thread");
     8         }
     9         sThreadLocal.set(new Looper(quitAllowed));
    10 }

    prepare()方法调用prepare(boolean quitAllowed)方法,第6-8行首先检测当前线程中是否已经存储了Looper,如果已经存储过则抛出异常,这里我们就应该知道一个线程只能有一个Looper实例存在。sThreadLocal就是ThreadLocal,关于ThreadLocal我之前写过一篇介绍文章,这里就不详细说明了,请参照Android 异步消息处理机制前篇(一):深入理解ThreadLocal

    第9行,如果当前线程没有存储过Looper,则new一个存储在当前线程。我们再看下Looper初始化的时候都做了什么:

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

    主要就是初始化了一个MessageQueue赋值给mQueue变量,这里要明白,Looper初始化的时候会初始化一个MessageQueue与当前Looper绑定,后面会多次提到。

    以上便是prepare方法的主要逻辑了,没什么复杂的,我们继续看loop()方法:

     1 public static void loop() {
     2         final Looper me = myLooper();
     3         if (me == null) {
     4             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
     5         }
     6         final MessageQueue queue = me.mQueue;
     7 
     8         // Make sure the identity of this thread is that of the local process,
     9         // and keep track of what that identity token actually is.
    10         Binder.clearCallingIdentity();
    11         final long ident = Binder.clearCallingIdentity();
    12 
    13         for (;;) {
    14             Message msg = queue.next(); // might block
    15             if (msg == null) {
    16                 // No message indicates that the message queue is quitting.
    17                 return;
    18             }
    19 
    20             // This must be in a local variable, in case a UI event sets the logger
    21             Printer logging = me.mLogging;
    22             if (logging != null) {
    23                 logging.println(">>>>> Dispatching to " + msg.target + " " +
    24                         msg.callback + ": " + msg.what);
    25             }
    26 
    27             msg.target.dispatchMessage(msg);
    28 
    29             if (logging != null) {
    30                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    31             }
    32 
    33             // Make sure that during the course of dispatching the
    34             // identity of the thread wasn't corrupted.
    35             final long newIdent = Binder.clearCallingIdentity();
    36             if (ident != newIdent) {
    37                 Log.wtf(TAG, "Thread identity changed from 0x"
    38                         + Long.toHexString(ident) + " to 0x"
    39                         + Long.toHexString(newIdent) + " while dispatching to "
    40                         + msg.target.getClass().getName() + " "
    41                         + msg.callback + " what=" + msg.what);
    42             }
    43 
    44             msg.recycleUnchecked();
    45         }
    46 }

     第2行,调用myLooper()。源码如下:

    1 public static @Nullable Looper myLooper() {
    2         return sThreadLocal.get();
    3 }

    很简单就是获取当前线程存储的Looper实例,3-5行检测是否为空,为空的话则会抛出异常,这里就提示我们loop()方法一定要放在prepare()方法之后调用,否则会抛出异常。

    第6行取出与当前Looper绑定的MessageQueue。接下来就进入13-45行的死循环了。

    14-18行,从MessageQueue消息队列中获取message,如果没有则阻塞。

    如果消息队列中存在未处理的message,则调用 msg.target.dispatchMessage(msg),target是什么鬼?其实就是Handler,这里先知道就可以了,后续会分析到。

    44行,用完消息后对消息进行回收放进缓存池中,Message消息缓存池的实现原理请参照我之前的文章Android 异步消息处理机制前篇(二):深入理解Message消息池

    loop()方法其实就是不断循环检查消息队列中是否存在未处理的消息,如果存在则交给Handler来处理。

    Looper总结:

    Looper会在当前线程创建一个Looper实例存储在当前线程,并且会绑定一个MessageQueue

    Looper的loop()方法,不断从MessageQueue中取消息,交给Handler的dispatchMessage去处理。

    Handler源码分析

    接下来我们分析Handler主要逻辑。先从Handler的创建开始,我们创建的时候一般都是调用空参数的构造函数:

     1 public Handler() {
     2         this(null, false);
     3 }
     4 
     5 public Handler(Callback callback, boolean async) {
     6         if (FIND_POTENTIAL_LEAKS) {
     7             final Class<? extends Handler> klass = getClass();
     8             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
     9                     (klass.getModifiers() & Modifier.STATIC) == 0) {
    10                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
    11                     klass.getCanonicalName());
    12             }
    13         }
    14 
    15         mLooper = Looper.myLooper();
    16         if (mLooper == null) {
    17             throw new RuntimeException(
    18                 "Can't create handler inside thread that has not called Looper.prepare()");
    19         }
    20         mQueue = mLooper.mQueue;
    21         mCallback = callback;
    22         mAsynchronous = async;
    23 }

     本质上调用两个参数的构造函数。

    15行,Looper.myLooper(),这里还记得吧,从当前线程获取存储的Looper实例。

    16-19行,判断是否为空,为空则抛出异常。

    20行,获取与当前线程中Looper实例绑定的消息队列,赋值给当前Handler中mQueue变量。

    通过以上逻辑,Handler就与当前线程中的Looper,MessageQueue建立上了关联。

    接下来Handler最重要的功能就是发送消息了,最常用的是sendMessage(Message msg),源码如下:

     1 public final boolean sendMessage(Message msg){
     2         return sendMessageDelayed(msg, 0);
     3 }
     4 
     5 public final boolean sendMessageDelayed(Message msg, long delayMillis) {
     6         if (delayMillis < 0) {
     7             delayMillis = 0;
     8         }
     9         return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    10 }
    11 
    12 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    13         MessageQueue queue = mQueue;
    14         if (queue == null) {
    15             RuntimeException e = new RuntimeException(
    16                     this + " sendMessageAtTime() called with no mQueue");
    17             Log.w("Looper", e.getMessage(), e);
    18             return false;
    19         }
    20         return enqueueMessage(queue, msg, uptimeMillis);
    21 }

    调来调去最终调用到sendMessageAtTime(Message msg, long uptimeMillis)方法。

    13行获取MessageQueue ,14-19判断是否为空,如果不为空则执行20行逻辑,接下来我们看下enqueueMessage

    1 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    2         msg.target = this;
    3         if (mAsynchronous) {
    4             msg.setAsynchronous(true);
    5         }
    6         return queue.enqueueMessage(msg, uptimeMillis);
    7 }

    第2行,将当前Handler赋值给msg的targe,这里msg就会记住是哪个Handler发送的自己,在Looper的loop()方法中从消息队列中获取msg的时候,会调用 msg.target.dispatchMessage(msg)方法,交给发送自己的Handler的handleMessage方法来处理,这里说起来真绕口,不过这里一定要明白是怎么回事,面试中很可能会问到,比如,UI线程中创建Handler1,Handler2,Handler1发送msg1,Handler2发送Msg2,Handler2中的handleMessage是否会收到msg1?为什么?傻子都会回答不会,但是原理你能清清楚楚的讲解清楚吗,答案就是这里。

    第6行,调用enqueueMessage方法将msg发送到消息队列。

    接下来我们看下Handler中dispatchMessage(Message msg)方法:

     1 public void dispatchMessage(Message msg) {
     2         if (msg.callback != null) {
     3             handleCallback(msg);
     4         } else {
     5             if (mCallback != null) {
     6                 if (mCallback.handleMessage(msg)) {
     7                     return;
     8                 }
     9             }
    10             handleMessage(msg);
    11         }
    12 }

    第2行判断msg的callbac是否为空,如果不为空则调用 handleCallback方法:

    1     private static void handleCallback(Message message) {
    2         message.callback.run();
    3     }

    很简单,就是调用run方法,首先这里就有两个小问题,1,run方法是否在子线程执行?2,平时我们发送msg也不给msg设置callbac啊,这里什么时候会用到?

    回答问题1之前我们先看问题2,Handler的post方法大家应该都用过,典型用法如下:

     1 public class MainActivity extends Activity {  
     2       
     3         private Handler handler;  
     4       
     5         @Override  
     6         protected void onCreate(Bundle savedInstanceState) {  
     7             super.onCreate(savedInstanceState);  
     8             setContentView(R.layout.activity_main);  
     9             handler = new Handler();  
    10             new Thread(new Runnable() {  
    11                 @Override  
    12                 public void run() {  
    13                     handler.post(new Runnable() {  
    14                         @Override  
    15                         public void run() {  
    16                             //UI操作  
    17                         }  
    18                     });  
    19                 }  
    20             }).start();  
    21         }  
    22  }  

    这样我们就可以在run方法里面进行UI操作了,显然这里run方法肯定是在主线程执行的,为什么呢?还是看下源码吧:

    1     public final boolean post(Runnable r)
    2     {
    3        return  sendMessageDelayed(getPostMessage(r), 0);
    4     }
    5     private static Message getPostMessage(Runnable r) {
    6         Message m = Message.obtain();
    7         m.callback = r;
    8         return m;
    9     }

    第7行就将我们传入的Runnable赋值给了msg的callback,然后执行发送消息到消息队列逻辑,之后就是Looper负责取消息发送给Handler,会调用dispatchMessage方法,上面讲过首先会对msg的callback进行判断是否为null,如果不为null,则直接调用run方法,到这里问题2应该就解决了,就是我们调用post方法设置的Runnable其本质是设置给了msg。

    那么问题1呢?什么时候会在子线程执行,答案就是与Handler的创建所在线程是一致的,只不过我们大部分都是用来在主线程创建,子线程的情况用的比较少。

    好了,我们回到Handler中dispatchMessage(Message msg)方法继续分析:

    5-9行如果mCallback不为null则调用mCallback的handleMessage方法,mCallback的赋值是在Handler创建的时候。

    如果mCallback为null则就调用Handler中的handleMessage方法:

    1     /**
    2      * Subclasses must implement this to receive messages.
    3      */
    4     public void handleMessage(Message msg) {
    5     }

    就是一个空方法,注释已经说了子类必须重写此方法接受消息进行处理。

    好了到此Handler中核心部分就分析完了,小小的总结一下:

    1、Handler的构造方法,会得到当前线程中保存的Looper实例,以及Looper实例中的MessageQueue,三者建立起联系。

    2、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。

    MessageQueue源码分析

    MessageQueue就是一个容器,用来存放message,主要就是存入,取出message的操作。

    首先存入源码如下:

     1 boolean enqueueMessage(Message msg, long when) {
     2         if (msg.target == null) {
     3             throw new IllegalArgumentException("Message must have a target.");
     4         }
     5         if (msg.isInUse()) {
     6             throw new IllegalStateException(msg + " This message is already in use.");
     7         }
     8 
     9         synchronized (this) {
    10             if (mQuitting) {
    11                 IllegalStateException e = new IllegalStateException(
    12                         msg.target + " sending message to a Handler on a dead thread");
    13                 Log.w(TAG, e.getMessage(), e);
    14                 msg.recycle();
    15                 return false;
    16             }
    17 
    18             msg.markInUse();
    19             msg.when = when;
    20             Message p = mMessages;
    21             boolean needWake;
    22             if (p == null || when == 0 || when < p.when) {
    23                 // New head, wake up the event queue if blocked.
    24                 msg.next = p;
    25                 mMessages = msg;
    26                 needWake = mBlocked;
    27             } else {
    28                 // Inserted within the middle of the queue.  Usually we don't have to wake
    29                 // up the event queue unless there is a barrier at the head of the queue
    30                 // and the message is the earliest asynchronous message in the queue.
    31                 needWake = mBlocked && p.target == null && msg.isAsynchronous();
    32                 Message prev;
    33                 for (;;) {
    34                     prev = p;
    35                     p = p.next;
    36                     if (p == null || when < p.when) {
    37                         break;
    38                     }
    39                     if (needWake && p.isAsynchronous()) {
    40                         needWake = false;
    41                     }
    42                 }
    43                 msg.next = p; // invariant: p == prev.next
    44                 prev.next = msg;
    45             }
    46 
    47             // We can assume mPtr != 0 because mQuitting is false.
    48             if (needWake) {
    49                 nativeWake(mPtr);
    50             }
    51         }
    52         return true;
    53 }

    MessageQueue内部维护了一个链表,mMessages用于记录链表头部元素。

    19-45行逻辑可以看出,对于messge会有一个按照时间排序的操作,这个也不难理解,在我们发送消息的时候可以指定发送的时候,这个时间并不是推迟入队的时间,而是给message打上时间标记,MessageQueue对message的维护会按照时间来排序。

    其实这里和之前文章讲过的Android 异步消息处理机制前篇(二):深入理解Message消息池 的实现很像都是用链表实现的,只不过这里有个按照时间排序的操作。

    取出的源码就不仔细分析了,大体过程说一下,在入队的时候会对message进行排序,并且mMessages会记录链表头部元素,取出message就是取出mMessages指向的消息,并且mMessages指向下一个消息,如果MessageQueue中不存在message,则阻塞一直等到有消息。

    好了,讲到这里,异步消息通知最核心的部分就讲解完了。

    异步消息通知总体流程总结:

    老规矩,没有什么是一张图不能解决的

    大部分关键点都在图中标注出来了,好了,本文到此就该结束了。

    建议大家有时间看下View的post()方法以及 Activity的runOnUiThread()方法,其本质也都是使用异步消息通知这块技术点。

  • 相关阅读:
    进度条与拖动条的使用学习
    双指针,BFS和图论(三)
    dubbo文档笔记
    ByteBuf
    Netty源码解析—客户端启动
    Netty源码解析---服务端启动
    java并发程序和共享对象实用策略
    docker命令
    elasticSearch基本使用
    Filebeat6.3文档—Log input配置
  • 原文地址:https://www.cnblogs.com/leipDao/p/7826133.html
Copyright © 2020-2023  润新知