详解Android中Handler的内部实现原理

 更新时间:2015年12月16日 10:27:10   作者:孙群  
这篇文章主要介绍了Android中Handler的内部实现原理,对Handler和消息循环的实现原理进行源码分析,需要的朋友可以参考下

本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文《详解Android中Handler的使用方法》里面对Android为何以引入Handler机制以及如何使用Handler做了讲解。

概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能。但作为程序员,我不能只知道怎么用Handler,还要知道其内部如何实现的。Handler的内部实现主要涉及到如下几个类: Thread、MessageQueue和Looper。这几类之间的关系可以用如下的图来简单说明:

Thread是最基础的,Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上,我们通过Handler间接地与下面这几个相对底层一点的类打交道。

一图胜千言

我们在本文讨论了Thread、MessageQueue、Looper以及Hanlder的之间的关系,我们可以通过如下一张传送带的图来更形象的理解他们之间的关系。

在现实生活的生产生活中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另一端进行收集处理。

我们可以把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的消息队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,我们可以把发送机滚轮看做是Looper,而发动机的转动是需要电源的,我们可以把电源看做是线程Thread,所有的消息循环的一切操作都是基于某个线程的。一切准备就绪,我们只需要按下电源开关发动机就会转动起来,这个开关就是Looper的loop方法,当我们按下开关的时候,我们就相当于执行了Looper的loop方法,此时Looper就会驱动着消息队列循环起来。

那Hanlder在传送带模型中相当于什么呢?我们可以将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相当于我们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX方法,这就把Message对象放入到了消息队列MessageQueue中了。当货物从传送带的另一端顺着管道划出时,我们就相当于调用了Hanlder的dispatchMessage方法,在该方法中我们完成对Message的处理。

下面重点介绍Handler:

Handler是暴露给开发者最顶层的一个类,其构建在Thread、Looper与MessageQueue之上。
Handler具有多个构造函数,签名分别如下所示:

  • 1. publicHandler()
  • 2. publicHandler(Callbackcallback)
  • 3. publicHandler(Looperlooper)
  • 4. publicHandler(Looperlooper, Callbackcallback)

第1个和第2个构造函数都没有传递Looper,这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。
第3个和第4个构造函数传递了Looper对象,这两个构造函数会将该Looper保存到名为mLooper的成员字段中。
第2个和第4个构造函数还传递了Callback对象,Callback是Handler中的内部接口,需要实现其内部的handleMessage方法,Callback代码如下:

public interface Callback {
  public boolean handleMessage(Message msg);
}

Handler.Callback是用来处理Message的一种手段,如果没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler能够处理Message,我们有两种办法:
1. 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
2. 无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法
也就是说无论哪种方式,我们都得通过某种方式实现handleMessage方法,这点与Java中对Thread的设计有异曲同工之处。
在Java中,如果我们想使用多线程,有两种办法:
1. 向Thread的构造函数传入一个Runnable对象,并实现Runnable的run方法
2. 无需向Thread的构造函数传入Runnable对象,但是要重写Thread本身的run方法
所以只要用过多线程Thread,应该就对Hanlder这种需要实现handleMessage的两种方式了然于心了。

我们知道通过sendMessageXXX系列方法可以向消息队列中添加消息,我们通过源码可以看出这些方法的调用顺序,
sendMessage调用了sendMessageDelayed,sendMessageDelayed又调用了sendMessageAtTime。
Handler中还有一系列的sendEmptyMessageXXX方法,而这些sendEmptyMessageXXX方法在其内部又分别调用了其对应的sendMessageXXX方法。

通过以下调用关系图我们可以看的更清楚些:

由此可见所有的sendMessageXXX方法和sendEmptyMessageXXX最终都调用了sendMessageAtTime方法。

我们再来看看postXXX方法,会发现postXXX方法在其内部又调用了对应的sendMessageXXX方法,我们可以查看下sendMessage的源码:

public final boolean post(Runnable r)
{
  return sendMessageDelayed(getPostMessage(r), 0);
}

可以看到内部调用了getPostMessage方法,该方法传入一个Runnable对象,得到一个Message对象,getPostMessage的源码如下:

private static Message getPostMessage(Runnable r) {
  Message m = Message.obtain();
  m.callback = r;
  return m;
 }

通过上面的代码我们可以看到在getPostMessage方法中,我们创建了一个Message对象,并将传入的Runnable对象赋值给Message的callback成员字段,然后返回该Message,然后在post方法中该携带有Runnable信息的Message传入到sendMessageDelayed方法中。由此我们可以看到所有的postXXX方法内部都需要借助sendMessageXXX方法来实现,所以postXXX与sendMessageXXX并不是对立关系,而是postXXX依赖sendMessageXXX,所以postXXX方法可以通过sendMessageXXX方法向消息队列中传入消息,只不过通过postXXX方法向消息队列中传入的消息都携带有Runnable对象(Message.callback)。

我们可以通过如下关系图看清楚postXXX系列方法与sendMessageXXX方法之间的调用关系:

通过分别分析sendEmptyMessageXXX、postXXX方法与sendMessageXXX方法之间的关系,我们可以看到在Handler中所有可以直接或间接向消息队列发送Message的方法最终都调用了sendMessageAtTime方法,该方法的源码如下:

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);
}

该方法内部调用了enqueueMessage方法,该方法的源码如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  //注意下面这行代码
  msg.target = this;
  if (mAsynchronous) {
   msg.setAsynchronous(true);
  }
  //注意下面这行代码
  return queue.enqueueMessage(msg, uptimeMillis);
}

在该方法中有两件事需要注意:

  • 1. msg.target = this

该代码将Message的target绑定为当前的Handler

  • 2. queue.enqueueMessage

变量queue表示的是Handler所绑定的消息队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。

所以我们通过下图可以看到完整的方法调用顺序:

我们在分析Looper.loop()的源码时发现,Looper一直在不断的从消息队列中通过MessageQueue的next方法获取Message,然后通过代码msg.target.dispatchMessage(msg)让该msg所绑定的Handler(Message.target)执行dispatchMessage方法以实现对Message的处理。
Handler的dispatchMessage的源码如下:

public void dispatchMessage(Message msg) {
  //注意下面这行代码
  if (msg.callback != null) {
   handleCallback(msg);
  } else {
    //注意下面这行代码
   if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
     return;
    }
   }
    //注意下面这行代码
   handleMessage(msg);
  }
}

我们来分析下这段代码:

1.首先会判断msg.callback存不存在,msg.callback是Runnable类型,如果msg.callback存在,那么说明该Message是通过执行Handler的postXXX系列方法将Message放入到消息队列中的,这种情况下会执行handleCallback(msg), handleCallback源码如下:

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

这样我们我们就清楚地看到我们执行了msg.callback的run方法,也就是执行了postXXX所传递的Runnable对象的run方法。

2.如果我们不是通过postXXX系列方法将Message放入到消息队列中的,那么msg.callback就是null,代码继续往下执行,接着我们会判断Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback类型的,我们在上面提到过,在Handler的构造函数中我们可以传递Hanlder.Callback类型的对象,该对象需要实现handleMessage方法,如果我们在构造函数中传递了该Callback对象,那么我们就会让Callback的handleMessage方法来处理Message。

3.如果我们在构造函数中没有传入Callback类型的对象,那么mCallback就为null,那么我们会调用Handler自身的hanldeMessage方法,该方法默认是个空方法,我们需要自己是重写实现该方法。

综上,我们可以看到Handler提供了三种途径处理Message,而且处理有前后优先级之分:首先尝试让postXXX中传递的Runnable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,最后才是让Handler自身的handleMessage方法处理Message。

希望本文对于大家理解Android中的Handler和消息循环机制有所帮助。

相关文章

  • Android 常见的四种对话框实例讲解

    Android 常见的四种对话框实例讲解

    这篇文章主要介绍了android 常见的四种对话框实例讲解,非常不错,具有参考借鉴价值,对android 对话框相关知识感兴趣的朋友一起看看吧
    2016-09-09
  • Android Flutter实现"斑马纹"背景的示例代码

    Android Flutter实现"斑马纹"背景的示例代码

    本文将通过实现一个canvas绘制斑马纹类。使用Stack布局,将斑马纹放在下方作为背景板,需要展示的内容在上方。从而实现 “斑马纹”背景,感兴趣的可以了解一下
    2022-06-06
  • Android Crash与ANR详细介绍

    Android Crash与ANR详细介绍

    对于Android开发的人来说,想必对Crash和ANR这俩都不陌生,并且都对其恨之入骨,因为它俩的产生会大大影响用户体验。所以,在此,结合本人的开发经验,对其做个总结
    2022-11-11
  • Android开发Input系统触摸事件分发

    Android开发Input系统触摸事件分发

    这篇文章主要为大家介绍了Android开发Input系统触摸事件分发示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Android图片选择器ImageEditContainer

    Android图片选择器ImageEditContainer

    这篇文章主要为大家详细介绍了Android图片选择器ImageEditContainer的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • 微信小程序 canvas开发实例及注意事项

    微信小程序 canvas开发实例及注意事项

    这篇文章主要介绍了微信小程序 wxcanvas开发实例及注意事项的相关资料,这里对微信canvas与H5中的canvas做对比,并说明注意事项,需要的朋友可以参考下
    2016-12-12
  • Android开发笔记之:ListView刷新顺序的问题详解

    Android开发笔记之:ListView刷新顺序的问题详解

    本篇文章是对Android中ListView刷新顺序的问题进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 实例讲解Android App使用自带的SQLite数据库的基本方法

    实例讲解Android App使用自带的SQLite数据库的基本方法

    这篇文章主要介绍了Android App使用自带的SQLite数据库的基本方法,SQLite是一个小巧的内嵌型数据库,在数据库需求不大的情况下使用SQLite其实非常有效,需要的朋友可以参考下
    2016-04-04
  • Android性能优化之plt hook与native线程监控详解

    Android性能优化之plt hook与native线程监控详解

    这篇文章主要为大家介绍了Android性能优化之plt hook与native线程监控详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • RxJava2配置及使用详解

    RxJava2配置及使用详解

    这篇文章主要介绍了RxJava2配置及使用详解,RxJava2.0是一个非常棒的流式编程,有兴趣的可以了解一下
    2017-06-06

最新评论