Android 中的消息机制

annyonelike 5年前
   <p>最近写博客的时间,都是在晚上图书馆学习回到宿舍后,大概是11点半开始写,写着写着就1点多了,这还是我积累的比较充分了的情况下的,然后自己要看一遍再睡觉。第二天早上还要审稿后再发表。下星期开始调整写作时间,早睡早起学习效率才高。</p>    <p>写一篇文章真的不容易,我会陆续写一个路线的博客,如事件分发机制,自定义View,View的绘制机制和加载过程,Activity的加载过程等等 。</p>    <p>进入正题。执行耗时的操作,比如网络请求,IO操作等,需要在子线程中运行,不然会阻塞主线程。</p>    <p>而执行完网络请求等耗时操作后通常需要更新UI,如果在子线程中更新UI,那么程序会崩溃。因为Android的UI是线程不安全的。</p>    <p>解决的方案是只需把更新UI的操作切换到主线程即可,这时就轮到Handler出场了,相信大家都对Handler的用法很熟悉了。当我们在子线程向服务端拉取数据后,主线程是不知道的,这时handler在子线程发送一个消息到主线程告诉主线程:我已经请求数据完毕,现在你要更新UI了。然后handlerMessage方法接收到消息即可处理数据更新UI。</p>    <p>这一切都是那么的自然。</p>    <p>Android中的消息机制,由四个角色承担。分别是Handler,Looper(消息循环),MessageQueue(消息队列),Thread。</p>    <p>看到下图这四个角色的关联,先有一个大概的认识</p>    <p><img src="https://simg.open-open.com/show/96c2fa4ea8ee537649a591e354a0377e.png"></p>    <p>那接下来就从源码(Api-23)的角度一起来学习消息机制吧</p>    <p>先从handler对象的创建开始,接着再分析handler怎么发送消息。</p>    <p>我们通常在主线程中创建handler,看看他的构造方法。</p>    <pre>  <code class="language-java">public Handler() {      this(null, false);  }</code></pre>    <p>可以看到这个无参数的构造方法,内部使用this关键字调用含有两个参数的构造方法。那就找到两个参数的构造方法,源码如下:</p>    <pre>  <code class="language-java">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 that has not called Looper.prepare()");      }      mQueue = mLooper.mQueue;      mCallback = callback;      mAsynchronous = async;  }</code></pre>    <p>可以看到Looper调用myLooper方法获取到Looper对象, 如果mLooper == null的话,会抛出</p>    <pre>  <code class="language-java">Can't create handler inside thread that has not called Looper.prepare()</code></pre>    <p>的异常。大概的意思就是无法在没有调用Looper.prepare()的线程中创建handler。</p>    <p>我在刚开始学习Handler的时候经常会遇到这个错误。不急,等下在分析到底为什么,现在我们只需要知道如果Looper.myLooper()没有获取到Looper对象的话就会报这个错。</p>    <p>到了这里,Handler和Looper就建立起了关联。接着往下看完最后几行代码</p>    <pre>  <code class="language-java">mQueue = mLooper.mQueue;</code></pre>    <p>从Looper对象中取出MessageQueue对象并赋值。MessageQueue就是消息队列,那么他里面存储着很多消息吗?</p>    <p>到了这一步,Handler通过Looper与MessageQueue也建立起了关联。</p>    <p>我们跟踪Looper的myLooper方法进去,解决为什么会抛出Can’t create handler inside thread that has not called Looper.prepare()异常。</p>    <p>myLooper方法源码如下:</p>    <pre>  <code class="language-java">public static @Nullable Looper myLooper() {      return sThreadLocal.get();  }</code></pre>    <p>只有一行代码,从线程中取出Looper对象,那么我们有理由相信,这个ThreadLocal是通过set方法把Looper对象设置进去的。</p>    <p>想一想ThreadLocal在哪里把Looper对象设置进去了呢。回到刚才想要解决的问题:Can’t create handler inside thread that has not called Looper.prepare() 。那会不会是Looper的prepare方法呢?</p>    <pre>  <code class="language-java">public static void prepare() {      prepare(true);  }</code></pre>    <p>prepare方法调用了它的一个参数的重载,那么我们就看看那个重载的方法</p>    <pre>  <code class="language-java">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));  }</code></pre>    <p>找到了线索,ThreadLocal确实是在Looper的prepare方法里把Looper对象设置进去的,而且从第一行的判断可以知道,一个线程只有一个Looper对象。</p>    <p>到了这里,Looper与ThreadLocal建立起了关联。可以看下Looper的构造方法</p>    <pre>  <code class="language-java">private Looper(boolean quitAllowed) {      mQueue = new MessageQueue(quitAllowed);      mThread = Thread.currentThread();  }</code></pre>    <p>创建了一个MessageQueue对象。</p>    <p>好,结合我们的分析可以知道,如果Looper没有调用prepare方法,ThreadLocal的get方法就会返回空,那么Looper.myLooper()也会返回空,所以就抛出了 Can't create handler inside thread that has not called Looper.prepare() 的异常。</p>    <p>那么问题又来了,我们写程序时好像没有手动调用Looper.prepare()吧,也不会抛出异常。前面提到,我们通常都是在主线程,也就是UI线程中创建handler的。而在主线程中,系统已经为我们创建了一个Looper对象,所以不会抛出异常了。。。而那些会抛出异常报错的情况,是在子线程中创建的handler,但是又没有调用Looper.prepare()去创建Looper对象。</p>    <p>继续前进。那就来看看,主线程在什么时候创建了Looper对象吧。</p>    <p>在ActivityThread的main方法,这个方法是应用程序的入口。</p>    <p>main方法的源码如下:</p>    <pre>  <code class="language-java">public static void main(String[] args) {        //代码省略        Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();      thread.attach(false);        if (sMainThreadHandler == null) {          sMainThreadHandler = thread.getHandler();      }        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");  }</code></pre>    <p>找到了Looper.prepareMainLooper(),这和Looper.prepare()太像了吧,跟进去看看</p>    <pre>  <code class="language-java">public static void prepareMainLooper() {      prepare(false);      synchronized (Looper.class) {          if (sMainLooper != null) {              throw new IllegalStateException("The main Looper has already been prepared.");          }          sMainLooper = myLooper();      }  }</code></pre>    <p>又兜了回来,还是调用了prepare方法的。所以主线程是已经创建了一个Looper对象的。</p>    <p>Handler的创建过程分析完毕,现在总算搞明白了。</p>    <p>那先总结一下,Handler的创建是依赖于Looper的。而主线程是默认创建了一个Looper对象的。每一个Looper会关联一个线程(ThreadLocal中封装了Looper)。每一个Looper中又会封装一个消息队列。</p>    <p>这样一来,handler,Looper,MessageQueue,Thread四个角色就关联了起来,你中有我,我中有你。</p>    <p>handler在主线程中创建,是因为要和主线程的消息队列关联起来,那样handler的handleMessage方法才会在主线程中执行,那么这样在更新UI就是线程安全的了。</p>    <p>接着继续吧,还很多问题没有解决</p>    <p>相信你更想了解Handler是怎么发送消息的。通常我们是创建一个Message对象,并将一些从服务端拉取的数据,标记,参数等赋值到Message的一些字段what,arg1,obj等,handler调用sendMessage方法发送,就能将这个数据发送到主线程,然后在handlerMessage方法处理更新UI即可。</p>    <p>那我们就从handler的sendMessage方法开始寻找信息</p>    <pre>  <code class="language-java">public final boolean sendMessage(Message msg)  {      return sendMessageDelayed(msg, 0);  }</code></pre>    <p>sendMessage会调用sendMessageDelayed方法并将message对象传进去,第二个参数是延时时间,使用sendMessage方法时默认为0的。</p>    <p>那么来到sendMessageDelayed方法</p>    <pre>  <code class="language-java">public final boolean sendMessageDelayed(Message msg, long delayMillis)  {      if (delayMillis < 0) {          delayMillis = 0;      }      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  }</code></pre>    <p>兜兜转转,最终会调用sendMessageAtTime方法,并将message对象传进。</p>    <p>继续跟进sendMessageAtTime方法,</p>    <pre>  <code class="language-java">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);  }</code></pre>    <p>上面分析了,在创建Looper对象的时候,会创建一个MessageQueue,所以只要Looper是正常创建的话,消息队列是不为空的。</p>    <p>那么到最后一行的enqueueMessage方法,源码如下:</p>    <pre>  <code class="language-java">private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {      msg.target = this;      if (mAsynchronous) {          msg.setAsynchronous(true);      }      return queue.enqueueMessage(msg, uptimeMillis);  }</code></pre>    <p>可以看到最后一行调用了MessageQueue的enqueueMessage方法。</p>    <p>注意: 上面贴出的enqueueMessage是Handler的方法,不是MessageQueue的,只是做了一层包装而已,真正的入队消息队列的操作当然是在MessageQueue中。而且从第一行的msg.target = this中可以知道,msg的target字段,其实就是handler。</p>    <p>MessageQueue的enqueueMessage方法源码如下:</p>    <pre>  <code class="language-java">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;  }</code></pre>    <p>Messagequeue中有一个对象mMessage用于指向当前传进的msg,即最新的消息。而刚才的sendMessageAtTime(Message msg, long uptimeMillis)方法,第二个参数指定了时间,然后在这里按照这个uptimeMillis来进行消息的排序,而我分析的结果msg.next是指向下一个消息,这样每一个消息都是按照时间的排序关联了起来,排在前面的消息指向了排在后面的消息。</p>    <p>以上是进入消息队列的分析,handler调用sendMessage方法的最终将message对象传进messagequeue。</p>    <p>完毕,那么消息是怎么从消息队列出来的呢?</p>    <p>这时我们要回看ActiviryThread的main方法,去寻找点线索。源码在上面已贴出。</p>    <p>发现了倒数第二行的Looper.loop(),简单理解就是消息循环执行循环操作。</p>    <p>这里一定能满足我们的好奇心。那么跟进。loop方法的源码如下:</p>    <pre>  <code class="language-java">public static void loop() {      final Looper me = myLooper();      if (me == null) {          throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");      }      final MessageQueue queue = me.mQueue;        // Make sure the identity of this thread is that of the local process,      // and keep track of what that identity token actually is.      Binder.clearCallingIdentity();      final long ident = Binder.clearCallingIdentity();        for (;;) {          Message msg = queue.next(); // might block          if (msg == null) {              // No message indicates that the message queue is quitting.              return;          }            // This must be in a local variable, in case a UI event sets the logger          Printer logging = me.mLogging;          if (logging != null) {              logging.println(">>>>> Dispatching to " + msg.target + " " +                      msg.callback + ": " + msg.what);          }            msg.target.dispatchMessage(msg);            if (logging != null) {              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);          }            // Make sure that during the course of dispatching the          // identity of the thread wasn't corrupted.          final long newIdent = Binder.clearCallingIdentity();          if (ident != newIdent) {              Log.wtf(TAG, "Thread identity changed from 0x"                      + Long.toHexString(ident) + " to 0x"                      + Long.toHexString(newIdent) + " while dispatching to "                      + msg.target.getClass().getName() + " "                      + msg.callback + " what=" + msg.what);          }            msg.recycleUnchecked();      }  }</code></pre>    <p>抓重点看就好。首先是调用myLooper方法获取到Looper对象,这里是没问题的,那就继续</p>    <pre>  <code class="language-java">MessageQueue queue = me.mQueue</code></pre>    <p>然后从Looper对象中取出关联的消息队列,</p>    <p>接着进入了一个死循环,调用messagequeue的next方法取出message对象。这个next方法我没看懂,所以不贴源码出来分析了,反正next方法的作用就是取出message对象的。有兴趣的同学自己去研究研究吧。</p>    <p>到这里可以总结一下:通过Looper.prepare()来创建Looper(消息循环)对象,然后通过Looper.loop()来执行消息循环,Looper.prepare()和Looper.loop()通常是成对出现的。</p>    <p>好,回来继续</p>    <p>经过一系列的判断后会来到这里,很重点</p>    <pre>  <code class="language-java">msg.target.dispatchMessage(msg);</code></pre>    <p>上面已经分析,msg.target就是handler,那么这行代码的意义就是调用handler的dispatchMessgage的方法去分发消息,</p>    <p>那么看到dispatchMessage的方法源码,相信谜底就要揭开了</p>    <pre>  <code class="language-java">public void dispatchMessage(Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }  }</code></pre>    <p>从上述的代码跟踪中,都没有发现给message的callback字段赋值,那么我们就先不搭理,默认callback为空,那么就一定会来到handleMessage方法。</p>    <p>message对象传递到了handleMessage方法。</p>    <pre>  <code class="language-java">/**   * Subclasses must implement this to receive messages.   */  public void handleMessage(Message msg) {  }</code></pre>    <p>handleMessage是一个空方法,需要我们去重写。</p>    <p>至此,海阔天空。完美的从子线程切换到主线程,我不得不说Android的源码设计是多么精彩。</p>    <p>以上就是handler使用sendMessage方法发送消息的源码分析。</p>    <p>为什么我会这么说呢?因为handler还有一种方法可以发送消息,是post方法,理解这个方法。可以解决刚才没搭理的那个message的callback字段的问题。看到post方法源码</p>    <pre>  <code class="language-java">public final boolean post(Runnable r)  {     return  sendMessageDelayed(getPostMessage(r), 0);  }</code></pre>    <p>接收一个实现了Runable接口的对象,然后将其传进getPostMessage()方法。跟进getPostMessage()方法看看</p>    <pre>  <code class="language-java">private static Message getPostMessage(Runnable r) {      Message m = Message.obtain();      m.callback = r;      return m;  }</code></pre>    <p>其实就是将Runable包装成message的callback嘛。</p>    <p>所以,如果我们使用post方法发送消息,callback字段是不为空的,那么就会执行handleCallback()方法,而不是执行handleMessage方法了。</p>    <p>handleCallback方法源码如下:</p>    <pre>  <code class="language-java">private static void handleCallback(Message message) {      message.callback.run();  }</code></pre>    <p>直接是调用run方法,表明我们直接在run方法里进行UI操作就行了。</p>    <p>我们发现不管是使用post方法还是sendMessage方法来发送消息,最终都会调用sendMessageDelayed方法。handler将消息追加到消息队列中的过程都是一样的,然后Looper不断的从MessageQueue中取出消息,并由handler去分发消息,处理消息,这样就构成了完善的Android消息机制体系。</p>    <p>呼,终于结束了,搞定了Android的消息机制,才能更深刻的理解Android中的多线程。以后的时间里一起打怪升级。</p>    <p> </p>    <p>来自:http://blog.csdn.net/xyh269/article/details/52554712</p>    <p> </p>