Android 开发之 Handler 的前世今生

本贴最后更新于 2203 天前,其中的信息可能已经时移世易

文章独家授权公众号:码个蛋
更多分享:http://www.cherylgood.cn

  • 谈到 Android 开发,就离不开线程操作,而面试中也会常常问到有关异步线程、多线程、Handler 等问题,作为面试中中奖率如此之高的一个问题,我们今天不妨来瞅瞅这 handler 长啥样!
目前:假设我们需要在子线程中更新 UI,一般有以下几种方式:

  • 1、view.post(Runnable action)
  • 2、activity.runOnUiThread(Runnable action)
  • 3、AsyncTask
  • 4、Handler

  • 而我们今天的主要目标就是 Handler,咱来一层一层撕开它的真面目吧!

  • 说了那么多废话,我们开始进入正题,首先我们看下 handler 的官方定义:

  • A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

  • Handler 允许你通过使用一个与线程的 MessageQueue 相关联的 Message 和 Runnable 对象去发送和处理消息。 每个处理程序实例与单个线程和该线程的消息队列相关联。 当您创建一个新的处理程序时,它绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将向消息队列传递消息和可运行文件,并在消息发出时执行它们 队列。

  • There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

  • Handler 有两个主要用途:(1)在可预见的时间内去调度消息和作为一些点的可运行程序(2)将不同于自己的线程执行的操作排入队列中。

  • Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).

  • 消息的调度是通过 post(Runnable),postAtTime(Runnable,long),postDelayed(Runnable,long),sendEmptyMessage(int),sendMessage(Message),sendMessageAtTime(Message,long)和 sendMessageDelayed(Message,long)来完成的 。 后台版本允许你将接收到的消息队列调用的 Runnable 对象排入队列; sendMessage 版本允许你将包含将由处理程序的 handleMessage(Message)方法处理的数据包(要求您实现 Handler 的子类)的 Message 对象排入队列。

  • When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

  • 当发布或发送到 Handler 时,你可以在消息队列准备就绪后立即处理该项目或者指定一个延迟时间去处理该消息队列,或者指定一个具体时间处理该消息。 后两者允许您实现超时,定时和其他基于时间的行为。

  • When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.

  • 当为你的应用创建一个进程时,其主线程专用于运行一个消息队列,该消息队列负责管理顶级应用程序对象(activitys, broadcast receivers 等)及其创建的任何窗口。 你可以创建你自己的线程并通过 Handler 与主应用程序线程进行通信。 这可以通过从你的新线程中调用同样的 post 或 sendMessage 方法来实现。 给定的 Runnable 或 Message 将在 Handler 的消息队列中进行调度,并在适当时进行处理。

  • 在查看 Handler 源码之前,我们先了解几个类:
    Handler 、Looper、MessageQueue、Message、ThreadLocation
    Handler 我们就不在介绍该类,上面的官方文档已给出了详细的介绍,我们来看下其余几个:

    • 1、ThreadLocal:每个使用该变量的线程提供独立的变量副本,每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal 内部是通过 map 进行实现的;
    • 2、Looper:可以理解为循环器,就是扶着管理一个消息循环队列(MessageQueue)的;
    • 3、MessageQueue:消息队列,用来存放 handler 发布的消息
    • 4、Message:消息体,封装了我们传输消息所需的数据结构。
  • ok,那么我们从哪里开始看起呢,好吧, 从创建一个 Handler 实例为入口,首先我们看 handler 的构造方法:

    public Handler() {
      this(null, false);
    }
    public Handler(Callback callback) {
      this(callback, false);
    }
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    public Handler(boolean async) {
      this(null, async);
    }
    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;
    }
    
    public Handler(Looper looper, Callback callback, boolean async) {
      mLooper = looper;
      mQueue = looper.mQueue;
      mCallback = callback;
      mAsynchronous = async;
    }
    
    
  • 可以看到,有多个构造方法,但是最终都会调用到最后一个。我们看倒数第二个,有这么一句: mLooper = Looper.myLooper();这里我们看到了个 looper,可以看到,在 handler 里面会有一个 mlooper 对象与之关联,我们先不看 mlooper 是怎么来的,我们先把下面的看完;继续看下面一句: mQueue = mLooper.mQueue;我们的 handler 里面也有一个队列的对象,实际上 mQueue 就是 MessageQueue,后面我们会讲解到。好的,继续往下看, mCallback = callback;一般情况下 mCallback 是 null,我们通常 new 一个 Handler 是不是调用的无参构造方法?callback 的作用后面也会讲解到,好的最后一句: mAsynchronous = async;表示我们的执行过程是异步的还是同步的,一般情况下,默认是异步的。

小结: Handler 会存有 Looper 对象以及消息队列 mQueue,通过关联 looper 与 mQueue,可以想象,handler 要把 message 插入消息队列中,最直接的方式当然是拿到消息队列的实例,实现消息的发送;

  • 看了 Handler 的构造,接下来我们看下 Looper.mLooper:

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

    -可以看到,Looper.myLooper 内部是调用了 sThreadlocal.get();这个 sThreadLocal 其实就是我们之前说的 ThreadLocal 类的实例,他负责存储当先线程的 Looper 实例;是不是真的呢?我们看下 sThreadLocal 在哪里赋值的,很好,我们找到了一个 prepare 方法,看名字是准备的意思,也就是为我们准备我们需要的 looper 对象,我们继续看:

     public static void prepare() {
      prepare(true);
    }
    
    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));
      }
    
    
  • 首先我们可以看到,会先判断 sThreadLocal.get() != null,说明 Looper.prepare()只能被调用一次哦,不然就会抛出异常,这样做是为了保证一个线程只有一个 looper 存在,然后我们的可以看到里面通过 new Looper(quitAllowed)获得当先线程的 looper,我们继续看 Looper 的构造方法:

      private Looper(boolean quitAllowed) {
      mQueue = new MessageQueue(quitAllowed);
      mThread = Thread.currentThread();
        }
    
    
  • 在 looper 的构造方法里,主要做了两件事:1、创建一个 looper 管理的消息队列 messageQueue;2、获得当前的线程;

    小结:Looper 里面会存储当前的线程,以及所管理的消息队列 mQueue,一个 Looper 只会管理一个消息队列 MessageQueue;

  • 从上面的代码中我们可以知道,在 new 一个 handler 的同时,我们就获得了一个 handler 实例、一个当前线程的 looper、一个 looper 管理的 messagequeue,好像拥有了这三个对象,我们就可以发送消息了哦。

  • 大家都知道 looper 从创建之后,就会开始循环,在 looper 类的顶部,官方给出了一段代码:

    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();
    }
    
    
  • 当我们使用 handler 发消息时,步骤是:

    • 1、 调用 Looper.prepare(); 初始化所需的 looper 以及 messageQueue
    • 2、 实例化一个 handler 对象,我们可以在 handleMessage 获得 message 做一些操作,此时 handleMessage 方法是在当前的 Looper 中执行的,也就是说,如果当前的 looper 是 UI Looper,那么你可以更新 UI,如果当前 looper 不是 UI Looper,那么你更新 UI 肯定会报错,你可能会说,我用 handler 时,好像都不用调用 Looper.prepare();,我怎么知道我当前的 looper 是 UI 的还是不是呢,其实系统一般默认都帮我们获取了 UI 的 Looper,后面我们会讲解到;
    • 3、调用 Looper.loop();让 Looper 跑起来吧!

  • Looper.prepare();我们前面已经分析过了,主要是实例化一个 messageQueue,而且只能调用一次;那么我们重点就转移懂到 Looper.loop();看源码:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    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
          final Printer logging = me.mLogging;
          if (logging != null) {
              logging.println(">>>>> Dispatching to " + msg.target + " " +
                      msg.callback + ": " + msg.what);
          }
    
          final long traceTag = me.mTraceTag;
          if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
              Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
          }
          try {
              msg.target.dispatchMessage(msg);
          } finally {
              if (traceTag != 0) {
                  Trace.traceEnd(traceTag);
              }
          }
    
          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();
      }
    }
    
    
  • 1、调用 final Looper me = myLooper();获得一个 looper,myLooper 方法我们前面分析过,返回的是 sThreadLocal 中存储的 Looper 实例,当 me==null 抛出异常;所以,在 looper 执行 loop 跑起来之前,我们要记得调用 prepare()哦。当获得当前的 looper 后,调用 final MessageQueue queue = me.mQueue; 获取 looper 管理的 MessageQueue;然后我们可以看到一个很有意思的 for 语句: for (;;) {...} 这就是循环的开始了,此时我在想,我的天,这不是个无限死循话么?怎么可能呢?当然有退出的条件,不然不就傻逼了么!

  • 2、我们可以看到:他会从 looper 的 queue 中获取 message,当 message==null,循环停止!

    Message msg = queue.next(); // might block
          if (msg == null) {
              // No message indicates that the message queue is quitting.
              return;
          }
    
    
  • 3、循环起来了,咱的 looper 也没闲着,他一直知道它的工作是什么,我们可以看到:msg.target.dispatchMessage(msg);通过调用 msg 对象里的 target 对象的 dispatchMessage(msg)方法把消息处理了。其实 msg 对象里的 target 对象就是我们 new 出来的 handler,我们后面会讲到。

小结:

looper 主要做了如下工作:

  • 1、将自己与当前线程关联在一起,通过 ThreadLocal 存储当前线程的 looper,确保当前线程只有一个 looper 实例;
  • 2、创建一个 MessageQueue 与当前 looper 绑定,通过 prepare 方法控制 looper 只能有一个 messageQueue 实例;
  • 3、调用 loop()方法,不断从 MessageQueue 中去取消息,通过调用 msg.target.dispatchMessage(msg)处理;

  • 分析完了 looper、接下来当然是 hanlder 发送消息了,我们又回到了 handler 中,我们通过 handler 发消息,自然少不了我们得 sendMessag 方法,那么我们就从它入手吧:

     public final boolean sendMessage(Message msg)
    {
      return sendMessageDelayed(msg, 0);
      }
    
     public final boolean sendEmptyMessage(int what)
    {
      return sendEmptyMessageDelayed(what, 0);
    }
    
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
      Message msg = Message.obtain();
      msg.what = what;
      return sendMessageDelayed(msg, delayMillis);
    }
    
    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
      Message msg = Message.obtain();
      msg.what = what;
      return sendMessageAtTime(msg, uptimeMillis);
    }
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
      if (delayMillis < 0) {
          delayMillis = 0;
      }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    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);
    }
    
    public final boolean sendMessageAtFrontOfQueue(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;
      }
      return enqueueMessage(queue, msg, 0);
    }
    
    

  • 可以看到我们的 sendMessage 有多种方法,但最终都会调用 enqueueMessage 方法,我们看 enqueueMessage 方法源码:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
      msg.target = this;
      if (mAsynchronous) {
          msg.setAsynchronous(true);
      }
      return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    
  • 可以看到里面会讲当前的 this 赋值给 msg.target,this
    当前就是我们当前的 handler 了,这也就是之前在分析 looper 时说的,通过调用 msg.target. dispatchMessage(msg)方法处理消息;后面后调用 queue.enqueueMessage(msg, uptimeMillis);把消息放入当前的 looper 的 MessageQueue 队列中去处理,消息的发送流程就分析完了,发送了,接下来就是处理消息了!


  • 我们用 handler 时,都是在 handleMessage 方法中处理消息的,那么我们就从 handleMessage 方法入手:

     /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    
    
  • 可以看到 handleMessage 是一个空的方法,我们看 handleMessage 在哪被调用的呢?

      /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
    }
    
    
  • 可以看到 handleMessage 在 dispatchMessage 中被调用了,奇怪,怎么有两个 handleMessage 方法呢?大家不要弄混了哦,我们 handler 的 handleMessage 方法返回值时 void,所以 mCallback.handleMessage 肯定不是我们 handler 的了;

  • 第一个县判断 msg.callback!=null 调用 handleCallback(msg);
    然后我们追进去看:

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

    看到了 run(),是不是想到了 Runnable?其实 message 中的 callback 就是 Runnable,我们可以从 Message 的创建函数中看到:

     private static Message getPostMessage(Runnable r) {
      Message m = Message.obtain();
      m.callback = r;
      return m;
    }
    
    
  • 我们继续回到 dispatchMessage 方法:也就是说,如果我们的给 massge 设置了 callback,那么我们的 handleMessage 方法就不会被执行了,当然,一般我们的 massge.callback 都是 null 的。后面就会继续判断 mCallback!=null 如果成立则调用 mCallback.handleMessage(msg) mCallback 其实是一个回调接口,可以看到,如果 mCallback.handleMessage(msg)返回 true,就不会执行我们的 Handler.handleMessage 方法,所以我们其实可以通过给 handler 添加 Callback 来实现一个 message 的过滤或者拦截功能。

  • 我们的 Handler.handleMessage 经过重重阻挠,最终终于可以执行了。

    总结:
    • 1、在 Looper.prepare()中会通过 sThreadLocal 保存一个 looper 实例,控制当前线程只能有一个 looper 实例;
    • 2、创建 looper 实例时,会创建一个 MessageQueue 与 looper 关联;
    • 3、因为 looper 只会存在一个实例,所以 当前线程也会只存在一个 MessageQueue 队列;
    • 4、调用 Looper.loop()让 looper 跑起来吧,然后 looper 就可以不停的从 MessageQueue 把消息拿出来,然后通过调用 msg.target.dispatchMessage(msg)处理消息,也是让消息最终进入我们的 Handler.handleMessage 方法,被我们给处理了;所以我们在实例化 handler 时需要重写 handleMessage 方法;
    • 5、实例化 Handler 时,handler 中会获得当前线程的 looper 以及 looper 的 messageQueue;
    • 6、在调用 sendMessage 发送消息时,最终会调用 enqueueMessage 方法,在 enqueueMessage 方法里会将 msg.target=handler,讲 handler 关联到 msg 中,这样 looper 在取出 messageQueue 中的消息时,才知道该消息是要发给那个 handler 处理的,将 handler 与 msg 关联后,就将 msg 加入队列中去了,等待 looper 处理。

  • 来个图解吧,画的不是很好

  • 使用 Handler 注意事项:

    • 1、创建 massage 对象时,推荐使用 obtain()方法获取,因为 Message 内部会维护一个 Message 池用于 Message 的复用,这样就可以避免 重新 new message 而冲内心分配内存,减少 new 对象产生的资源的消耗。
    • 2、handler 的 handleMessage 方法内部如果有调用外部 activity 或者 fragment 的对象,一定要用弱饮用,handler 最好定义成 static 的,这样可以避免内存泄漏;为什么呢?因为一但 handler 发送了消息。而 handler 内部有对外部变量的引用,此时 handler 已经进入了 looper 的 messageQueue 里面。此时 activity 或者 fragment 退出了可视区域,但是 handler 内部持有其引用且为强引用时,其就不会立即销毁,产生延迟销毁的情况。
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖 • 4 关注
  • Handler
    4 引用 • 1 回帖
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    171 引用 • 512 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...