Handler的原理分析这个标题,很多文章都写过,最近认真将源码逐行一字一句研究,特此也简单总结一遍。

首先是Handler整个Android消息机制的简单概括:

分三部分对消息机制的整个流程进行阐述:

  • Handler的创建,包括LooperMessageQueue的创建;
  • Handler发送消息,Message是如何进入消息队列MessageQueue的(入列);
  • Looper轮询消息,Message出列,Handler处理消息。

一、Handler创建流程分析

1.Handler如何被创建的

// 最简单的创建方式
public Handler() {this(null, false);
}// ....还有很多种方式,但这些方式最终都执行这个构造方法
public Handler(Callback callback, boolean async) {// 1.检查内存泄漏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());}}// 2.通过Looper.myLooper()获取当前线程的Looper对象mLooper = Looper.myLooper();// 3.如果Looper为空,抛出异常if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;
}
复制代码

首先,如何避免Handler的内存泄漏是一个非常常见的面试题,其实Handler的源码中已经将答案非常清晰告知给了开发者,即让Handler的导出类保证为static的,如果需要,将Context作为弱引用的依赖注入进来。

同时,在Handler创建的同时,会尝试获取当前线程唯一的Looper对象:

public final class Looper {static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();public static @Nullable Looper myLooper() {return sThreadLocal.get();}
}
复制代码

关于ThreadLocal,我在上一篇文章中已经进行了分析,现在我们知道了ThreadLocal保证了当前线程内有且仅有唯一的一个Looper

2.Looper是如何保证线程单例的

那就是需要调用Looper.prepare()方法:

public final class 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));}private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}}
复制代码

这也就说明了,为什么当前线程没有Looper的实例时,会抛出一个异常并提示开发者需要调用Looper.prepare()方法了。

也正如上述代码片段所描述的,如果当前线程已经有了Looper的实例,也会抛出一个异常,提示用户每个线程只能有一个Looperthrow new RuntimeException("Only one Looper may be created per thread");)。

此外,在Looper实例化的同时,也创建了对应的MessageQueue,这也就说明,一个线程有且仅有一个Looper,也仅有一个MessageQueue

二、发送消息流程分析

1.sendMessage()分析

sendMessage()流程如下:

// 1.发送即时消息
public final boolean sendMessage(Message msg)
{return sendMessageDelayed(msg, 0);
}// 2.实际上是发射一个延时为0的Message
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}// 3.将消息和延时的时间进行入列(消息队列)
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);
}// 4.内部实际上还是执行了MessageQueue的enqueueMessage()方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

注意第四步实际上将Handler对象最为target,附着在了Message之上;接下来看MessageQueue类内部是如何对Message进行入列的。

2.MessageQueue消息入列

boolean enqueueMessage(Message msg, long when) {//... 省略部分代码synchronized (this) {msg.markInUse();msg.when = when;// 获得链表头的MessageMessage p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// 若有以下情景之一,将Message置于链表头// 1.头部Message为空,链表为空// 2.消息为即时Message// 3.头部Message的时间戳大于最新Message的时间戳msg.next = p;mMessages = msg;needWake = mBlocked;} else {// 反之,将Message插入到链表对应的位置Message prev;// for循环就是找到合适的位置,并将最新的Message插入链表for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}if (needWake) {nativeWake(mPtr);}}return true;
}
复制代码

MessageQueue的数据结构本身是一个单向链表

三、接收消息分析

Handler创建好后,若在此之前调用了Looper.prepare()初始化Looper,还需要调用Looper.loop()开始该线程内的消息轮询。

1.Looper.loop()

public static void loop() {// ...省略部分代码// 1. 获取Looper对象final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}// 2.获取messageQueuefinal MessageQueue queue = me.mQueue;// 3. 轮询消息,这里是一个死循环for (;;) {// 4.从消息队列中取出消息,若消息队列为空,则阻塞线程Message msg = queue.next();if (msg == null) {return;}// 5.派发消息到对应的Handlermsg.target.dispatchMessage(msg);// ...}
}
复制代码

比较简单,需要注意的一点是MessageQueue.next()是一个可能会阻塞线程的方法,当有消息时会轮询处理消息,但如果消息队列中没有消息,则会阻塞线程。

2.MessageQueue.next()

private native void nativePollOnce(long ptr, int timeoutMillis);Message next() {// ...省略部分代码int nextPollTimeoutMillis = 0;for (;;) {// ...// native方法nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;// 从消息队列中取出消息if (msg != null) {// 当时间小于message的时间戳时,获取时间差if (now < msg.when) {// 该值将会导致在下次循环中阻塞对应时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 取出消息并返回mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;msg.markInUse();return msg;}}// ...}
}
复制代码

注意代码片段最上方的native方法——循环体内首先调用nativePollOnce(ptr, nextPollTimeoutMillis),这是一个native方法,实际作用就是通过Native层的MessageQueue阻塞nextPollTimeoutMillis毫秒的时间:

  • 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
  • 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
  • 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

搞清楚这一点,其它就都好理解了。

3.最终将消息发送给Handler

正如上文所说的,msg.target.dispatchMessage(msg)实际上就是调用Handler.dispatchMessage(msg),内部最终也是执行了Handler.handleMessage()回调:

public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}// 如果消息没有定义callBack,或者不是通过// Handler(Callback)的方式实例化Handler,// 最终会走到这里handleMessage(msg);}
}
复制代码

参考&感谢

  • 《Android开发艺术探索》
  • 深入理解MessageQueue
  • Android Handler:手把手带你深入分析 Handler机制源码

关于我

Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github。

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

  • 我的Android学习体系
  • 关于文章纠错
  • 关于知识付费

转载于:https://juejin.im/post/5ca38fbdf265da30b77379c1

Handler原理分析相关推荐

  1. android 实例源码解释,Android Handler 原理分析及实例代码

    Android Handler 原理分析 Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题 今天就为大家详细剖析下Handler的原理 Handler使 ...

  2. Android--Handler使用应运及消息机制处理原理分析

    最近开通了一个小微博,欢迎大家关注,每天分享一些上班路上看的小知识点 点击打开链接 一.Handler是什么 ? handler是android给我们提供的一套用来更新UI的一套机制,也是一套消息处理 ...

  3. Retrofit原理分析

    Retrofit原理分析 温故而知新 还记得retrofit的使用方法嘛,下面我们来回顾一下 接口定义 public interface GitHubService {@GET("users ...

  4. Android应用程序消息处理机制(Looper、Handler)分析(1)

    Android应用程序是通过消息来驱动的,系统为每一个应用程序维护一个消息队例,应用程序的主线程不断地从这个消息队例中获取消息(Looper),然后对这些消息进行处理(Handler),这样就实现了通 ...

  5. Redis数据持久化机制AOF原理分析一---转

    http://blog.csdn.net/acceptedxukai/article/details/18136903 http://blog.csdn.net/acceptedxukai/artic ...

  6. Android Handler原理

    前言 Handler消息处理机制在Android开发中起着举足轻重的作用,我们有必要好好理解下其原理,先前我写的一篇文章,感觉疏漏了好多东西,因此打算写这篇文章,下面我们先从一个简单的例子出发 一.日 ...

  7. Android 应用程序消息处理机制(Looper、Handler)分析

    Android应用程序是通过消息来驱动的,系统为每一个应用程序维护一个消息队例,应用程序的主线程不断地从这个消息队例中获取消息(Looper),然后对这些消息进行处理(Handler),这样就实现了通 ...

  8. MyBatis(五)MyBatis整合Spring原理分析

    前面梳理了下MyBatis在单独使用时的工作流程和关键源码,现在看看MyBatis在和Spring整合的时候是怎么工作的 也先从使用开始 Spring整合MyBatis 1.引入依赖,除了MyBati ...

  9. SAP UI5 应用的 OData 元数据请求响应的解析原理分析

    前一篇文章 SAP UI5 应用的 OData 元数据请求的发送原理分析我们学习了 SAP UI5 应用是如何自动发送 OData 元数据的 HTTP 请求. 本文继续学习该元数据请求的响应到达客户端 ...

最新文章

  1. 网站留言板防重复留言_如何做一个2000年风格复古的个人网站(3)创建个人小站-主页...
  2. Android控件-GridView
  3. 学计算机的学校17w,摇号中签率23.19%,学费一年17W?11所民校详情介绍!
  4. JAVA连接 mongodb(mac OSX)
  5. php 计划任务 curl,通过Task Scheduler定时运行调用cURL的PHP脚本 | 学步园
  6. jwt的token自动续约_JWT(JSON Web Token)自动延长到期时间
  7. dynamodb java_使用Java第2部分查询DynamoDB项
  8. (JAVA)格式化输出日期
  9. CCF大专委2019年大数据发展趋势预测
  10. 未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0, Culture=neutral,解决
  11. 清除工程目录下多余文件和文件夹
  12. 寄存器间接寻址缺点_详解西门子间接寻址之地址寄存器间接寻址
  13. 数据库查看内存,数据大小
  14. Taro从零创建微信小程序步骤
  15. mysql repeated read_mysql事务之可重复读(Repeated Read)
  16. 如何生成SHA2常数序列
  17. 关于swfobject.js详解
  18. Ninja工具介绍及基本语法
  19. 检查nmos管是否损坏
  20. 怎么获取网络舆情舆论数据的三大技术解决方法

热门文章

  1. Linux系统编程13:进程入门之Linux中的环境变量的概念及其相关命令(export;env等)main函数的参数
  2. Thrift 编译链接的时候出问题
  3. USACO-Section2.3 Money Systems
  4. kvm虚拟机网络设置隔离模式(一键shell脚本)
  5. Python之struct介绍及详解(与C/C++通信结构体的交互)
  6. 《明日方舟》Python版公开招募工具
  7. iOS - - JSON 和 XML解析
  8. sharepoint数据库连接
  9. 东软刘积仁:软件已从高科技领域变成大众消费品
  10. 什么是机器阅读理解?跟自然语言处理有什么关系?