android looper_关于不得不学的Android知识之消息机制

概述

相信不管是出入Android,还是已开发多年的老司机们,肯定都对Android的Handler不会陌生,而它就是今天要介绍的Android消息机制中的一部分。
在Android系统中,有两大特色利剑:Binder IPC机制和消息机制。Android也由大量的消息驱动方式来交互,大到四大组件的工作流程,小到异步回调更新UI等等,各处都有消息机制的存在。

角色

在对消息机制进行分析之前,先来看一下消息机制中,都含有哪些角色以及他们各自的作用又是什么:

  • Message
    消息本体,一切逻辑都围绕它来展开。
  • MessageQueue
    消息队列,管理消息的入队和出队。
  • andler
    消息机制的两端,可作为消息产生端,也可作为消息消费端。
  • Looper
    消息机制运转的动力,不断的循环执行,取出消息、分发消息。

他们之间的关系,可以通过一个简单图来表示一下:

daca56d953fbeea9dfe63d02e4e725bd.png

Handler

在消息机制的四个角色中,我们经常使用和见到的就是Handler了,那就先从Handler看起。

构造

Handler有很多构造方法,但是可用开发中使用的只有如下几个:

  • Handler()
  • Handler(@Nullable Callback callback)
  • Handler(@NonNull Looper looper)
  • Handler(@NonNull Looper looper, @Nullable Callback callback)

这样来看无非是可设置两个参数:LooperCallback
如果不指定Looper,在构造时会通过Looper.myLooper()获取当前线程的Looper,如果当前线程没有Looper那么会抛出异常。

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

至于Callback,后面会进行分析。

作为生产者生产Message

Handler的sendEmptyMessage、sendEmptyMessageAtTime、sendEmptyMessageDelayed、sendMessagesendMessageDelayed,最终都会调用sendMessageAtTime

  public boolean sendMessageAtTime(@NonNull 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);
  }

这其中的uptimeMillis = SystemClock.uptimeMillis() + delayMillis,之所以采用SystemClock.uptimeMillis(),是因为它是已开机时间,而如果使用System.currentTimeMillis()在用户修改手机时间时,该值就会发生变化。

除了上面的sendMessageAtTime,还有一个特殊的方法sendMessageAtFrontOfQueue

  public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
          MessageQueue queue = mQueue;
          if (queue == null) {
              RuntimeException e = new RuntimeException(
                  this + " sendMessageAtTime() called with no mQueue");
                  Log.w("Looper", e.getMessage(), e);
                  return false;
         }
         //固定uptimeMillis为0
        return enqueueMessage(queue, msg, 0);
   }

可以发现这两类方法最终都会通过调用方法:

  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
              long uptimeMillis) {
      //将msg的target属性指向当前Handler
      msg.target = this;
      msg.workSourceUid = ThreadLocalWorkSource.getUid();

      if (mAsynchronous) {
          msg.setAsynchronous(true);
      }
      //转到MessageQueue,消息入队
      return queue.enqueueMessage(msg, uptimeMillis);
  }

作为消费者消费Message

Looper在通过MessageQueue读取到下一条消息时,就会通过handler的dispatchMessage分发给目标Handler来消费这条消息:

  public void dispatchMessage(@NonNull Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              //如果配置了Callback,就不再走Handler的handleMessage
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
   }

Looper

既然在创建Handler时需要制定或从当前线程获取Looper,那么接下来就看一下Looper

构造

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

从构造方法可以看出:

1.私有方法,不允许外部直接通过构造方法创建

2.初始化时会初始化MessageQueue

3.初始化时会记录当前线程

在线程中创建Looper,可以使用prepare

  public static void prepare() {
      //必须可退出
      prepare(true);
  }

  //quitAllowed是否允许退出Looper
  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));
  }

Android系统在创建主线程Looper时,是通过prepareMainLooper

  public static void prepareMainLooper() {
      //不允许退出
      prepare(false);
      synchronized (Looper.class) {
          if (sMainLooper != null) {
              throw new IllegalStateException("The main Looper has already been prepared.");
          }
          sMainLooper = myLooper();
      }
  }

开启死循环

Looper既然是通过死循环,为消息机制提供运转动力,那么在创建Looper之后,就要适时的开启死循环:

  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;
      //省略部分代码
      for (;;) {
          //从queue中取消息,可能会阻塞当前线程
          Message msg = queue.next();
          if (msg == null) {
              // 取出null,说明消息机制退出,那么跳出循环
              return;
          }
          final 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);
          }
        
          //省略部分代码
        
          //回收这条消息
          msg.recycleUnchecked();
      }
  }

MessageQueue

Looper初始化时,会初始化MessageQueue,接下来就看一它有哪些内容。

构造

  //quitAllowed是否允许退出
  MessageQueue(boolean quitAllowed) {
      mQuitAllowed = quitAllowed;
      mPtr = nativeInit();
  }

不允许退出(quitAllowed传false)会怎样:

  void quit(boolean safe) {
      if (!mQuitAllowed) {
          throw new IllegalStateException("Main thread not allowed to quit.");
      }
    
      //省略部分代码
  }

消息入队

在前面分析Handler时,最终发送消息都会通过MessageQueueenqueueMessage

  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) {
              //没有准备要分发的消息
              //或者这条消息是sendMessageAtFrontOfQueue发送的 
              //或者这条消息要发送的时间比下一条要早
              //那么下一条就是你了
              msg.next = p;
              mMessages = msg;
              needWake = mBlocked;
          } else {
        
              //省略部分代码
            
          }

          //省略部分代码
        
      }
      return true;
  }

消息出队

Looper中,会通过死循环的方式调用queue.next()来获取下一条消息:

  Message next() {

      //省略部分代码

      for (;;) {
        
          //省略部分代码
        
          synchronized (this) {
              final long now = SystemClock.uptimeMillis();
              Message prevMsg = null;
              Message msg = mMessages;
            
              //省略部分代码
            
              if (msg != null) {
                  if (now < msg.when) {
                    
                      // 省略部分代码
                        
                  } else {
                      // 取到了一个消息
                      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();
                      //返给looper
                      return msg;
                  }
              }
            
              //省略部分代码
            
          }
      }
  }

我是主线程,我要做的事很多,但有优先级机制

我们知道,在Android中,主线程不只是要完成开发者写代码逻辑需求,还要完成系统对它的指示,比如刷新页面。
对于同一个Looper来说,是可以同时存在多个Handler,可以同时向Looper中发送消息,这其中既有Android系统中定义的各种Handler,又有开发者编写的Handler,那么如何才能让MessageQueue首先将系统发布的msg分发出来,能够被率先执行呢?

MessageQueue.postSyncBarrier(long when)

往消息队列头部,放入一个系统的“告示”,告知MessageQueue接下来我会发送一些优先级高的指令,务必先执行我接下来的优先指令。

  //类似于enqueueMessage,根据when合理的插入这个“告示”
  //此方法被hide标记
  //添加成功后会返回一个唯一的token,标识该屏障
  public int postSyncBarrier() {
      return postSyncBarrier(SystemClock.uptimeMillis());
  }

  private int postSyncBarrier(long when) {
      synchronized (this) {
          final int token = mNextBarrierToken++;
          final Message msg = Message.obtain();
          //未给该msg设置target,是“告示”的身份特征
          msg.markInUse();
          msg.when = when;
          msg.arg1 = token;

          Message prev = null;
          Message p = mMessages;
          if (when != 0) {
              while (p != null && p.when <= when) {
                  prev = p;
                  p = p.next;
              }
          }
          if (prev != null) {
              msg.next = p;
              prev.next = msg;
          } else {
              msg.next = p;
              mMessages = msg;
          }
          return token;
      }
  }

再来MessageQueue是怎么识别“告示”的:

  Message next() {

      //省略部分代码
    
      for (;;) {
    
          //省略部分代码
    
          synchronized (this) {
              final long now = SystemClock.uptimeMillis();
                  Message prevMsg = null;
                  Message msg = mMessages;
                  if (msg != null && msg.target == null) {
                      // msg.target == null 说明是系统“告示”,让我先进行优先消息的分发
                      do {
                          prevMsg = msg;
                          msg = msg.next;
                          //当找到第一个msg.isAsynchronous() = true的消息时,就会跳出循环,首先分发这个消息
                      } while (msg != null && !msg.isAsynchronous());
                  }
                
                  //省略部分代码
                
          }
    
      //省略部分代码
    
      }

  }

Message.setAsynchronous(true)

通过postSyncBarrier,系统告知MessageQueue接下来先执行的事,那么哪些才是要先执行的事呢?就是通过msg.setAsynchronous(true)方法,标记为true的事。

MessageQueue.removeSyncBarrier(int token)

系统除了可以在特定的场合(如刷新屏幕)添加同步屏障,告知MessageQueue先执行特定的优先级消息之外,还可以取消同步屏障,让MessageQueue回复正常排队执行。比如本来需要刷新下一帧,但是页面在下一帧刷新时间前被关闭了,那么就移除之前的“告知”。

一些思考

没有这个“告知”,始终在MessageQueue.next中判断msg.isAsynchronous为true,那么就优先分发它行不行?

如果没有这个添加“告知”和移除“告知”的存在,那么有些消息,包括系统发出的普通消息(msg.isAsynchronous = false),就可能永远不会被执行了(开发者把所有消息都进行msg.setAsynchronous(true))。必须要在合适的时机,让queue按照时间顺序,依次执行消息的分发,而不是始终将isAsynchronous标志放在第一位。

这里放出一个问题,可以思考一下:如果将Message的setAsynchronous进行hide处理,在MessageQueue.next中始终判断msg.isAsynchronous,优先分发msg.isAsynchronous == true的消息,又是否可行呢?

MessageQueue.IdleHandler

再来回看MessageQueue.next方法:

  Message next() {

  //省略部分代码

  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) {

      if (msg != null) {
          if (now < msg.when) {
              //省略部分代码
          }else{
        
              //省略部分代码
        
              //如果找到了要分发的msg,并且到了分发时间,那么就返回给looper
              return msg;
          }
      }

      //省略部分代码
    
      //以下代码执行条件,是未找到下一条msg或下一条msg还未到分发时间
    
      if (pendingIdleHandlerCount < 0
                          && (mMessages == null || now < mMessages.when)) {
          pendingIdleHandlerCount = mIdleHandlers.size();
      }

      if (pendingIdleHandlerCount <= 0) {
          mBlocked = true;
          continue;
      }

      if (mPendingIdleHandlers == null) {
          mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
      }
      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

      for (int i = 0; i < pendingIdleHandlerCount; i++) {
          final IdleHandler idler = mPendingIdleHandlers[i];
          mPendingIdleHandlers[i] = null; // release the reference to the handler

          boolean keep = false;
          try {
              //执行idler.queueIdle,保存返回结果
              keep = idler.queueIdle();
          } catch (Throwable t) {
              Log.wtf(TAG, "IdleHandler threw exception", t);
          }

          if (!keep) {
              synchronized (this) {
                  //如果不保留当前idler,那么移除
                  mIdleHandlers.remove(idler);
              }
          }
      }    

  }

  }

再来看一下接口IdleHandler:

  public static interface IdleHandler {
      boolean queueIdle();
  }

使用场景:

  • 在Activity绘制完成后,做一些事情
  • 结合HandlerThread, 用于单线程消息通知器

关于使用场景,更详细的内容,之后再详解。

死循环为什么不会阻塞App

阻塞App时,往往是不能在继续处理后续的逻辑,但是Android的消息机制,虽然是死循环,但是依然在有条不紊的接收和处理任务。这跟业务代码中,错误书写的一个局部小的死循环不同,正是由于存在这个死循环的存在,主线程才能一直轮询处理新的任务,保持应用的生机。

在消息机制中,取消息时,如果没有可分发消息或下一条要分发的消息还未到分发时间,就会进行适当的阻塞;在有消息传入时,会根据目标线程的阻塞状态,决定是否进行唤醒,已使其能够顺利的处理接下来的消息分发。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值