热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android消息机制&Android线程间通信机制

前言:由于Android系统本身决定了其自身的单线程模型结构。在日常的开发过程中,我们又不能把所有的工作都交给主线程去处理(会造成UI卡顿现象)。因此,适当的创建子线程去处理一些耗

前言:由于Android系统本身决定了其自身的单线程模型结构。在日常的开发过程中,我们又不能把所有的工作都交给主线程去处理(会造成UI卡顿现象)。因此,适当的创建子线程去处理一些耗时任务是非常关键的。同时Android中非UI线程不能对UI组件进行操作,因此,熟练的掌握并应用线程间消息通信是很有必要的。接下来,我们从Android线程间通信机制和Android消息机制两个方面对以上内容进行介绍。

一.Android线程间通信机制

Android的线程间通信主要是在非UI线程对UI线程的消息传递,并且修改UI界面的操作。Android中常见的子线程更新UI的方式:

  1. handler.post(Runnable r)
  2. runOnUiThread(Runnable r)
  3. view.post(Runnable r)
  4. Handler+Message+MessageQueue+Looper

接下来我们依次看一下这几种调用方法的内部实现原理。

– handler.post(Runnable r)内部实现原理:

//Handler.post()方法:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}

//Handler.sendMessageDelayed()方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis <0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

//Handler.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);
}

调用链:Handler.post->sendMessageDelayed->sendMessageAtTime
从上面的调用链可以看出,Handler.post方法最终调用了Handler.sendMessageAtTime方法,而通过sendMessageAtTime的实现逻辑可以看出,最终还是通过enqueueMessage方法将Message插入到消息队列,进行轮询处理。因此,底层还是Handler消息机制。

&#8211; runOnUiThread(Runnable r)内部实现原理:

public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

runOnUiThread内部实现机制到这里基本就可以结束了,因为它的实现原理一目了然。简单的判断当前线程是否是UI线程,如果是,就直接执行Runnable方法。如果不是,就通过Handler.post方法处理这个Runnable。因此,底层还是Handler消息机制。。

&#8211; view.post(Runnable r)内部实现原理

public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}

和runOnUiThread内部实现相似,view.post方法也是通过Handler.post方法处理这个Runnable。

从上面的分析可以看出,所有的自线程更新UI组件的方式都是依靠Handler消息机制实现的。因此,接下来我们将详细介绍Android的消息机制-Handler消息机制。

二.Android消息机制(Handler消息机制)

Handler消息机制的运行需要底层的MessageQueue和Looper来支撑,它的主要作用是将一个任务放到某个指定的线程中去执行。接下来我们详细介绍Handler、Message、MessageQueue、Looper的作用和联合工作机制。

MessageQueue:中文意思是消息队列。顾名思义,它的内部存储了一组消息,以队列的形式对外提供添加和删除操作。可能很多小伙伴从它的字面意思,就认为它就是一个队列的数据结构。其实并不是,它的内部是采用单链表的数据结构来存储消息列表,最初的存在形式就是一个空的头节点(mMessage)。

Message:中文含义为消息。很明显,它就是我们上面说到的MessageQueue中存储的实体。上面说到,MessageQueue是以单向链表的形式存储数据,每一个Message都是其中的一个节点。因此开始时,队列为空,即没有需要处理的消息。当有消息到来时,就添加节点到队列尾部(添加新节点到链表尾部,记录Next域)。
Message数据结构:

public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
//记录下一个节点域
android.os.Message next;
}

上面的Message结构只是简单罗列了一些我们常用的字段,其中实现链表最关键的字段就是next字段,它是实现消息队列的核心。我们创建Message对象有多种方式,但是建议通过Message的obtain方法去获取Message对象,而不是直接创建新的Message对象。想了解更多关于obtain方法内部实现原理,请移步:Handler消息机制之深入理解Message.obtain()

Looper:中文含义为循环,在这里即为消息循环。由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper正好弥补了这个缺陷。Looper会以无限循环的方式去查找是否有新的消息,如果有消息,就取出来处理,否则就一直等待。除此之外,它还有一个特殊的概念:ThreadLocal(存储和获取当前线程的Looper),在这里不作过多阐述,想要了解ThreadLocal是如何实现线程私有化存储机制的,可以移步 ThreadLocal内部实现机制详解 查看详情。
Handler:主要功能是切换到指定线程去处理Looper轮询查找到的新消息。

Looper工作原理:

Looper是消息机制中的消息循环角色,它会不断轮询查找MessageQueue中是否有新的消息需要处理。一旦发现需要处理的新消息,就立刻取出消息交给Handler进行处理;否则就一直阻塞在那里。

我们都知道,Looper、Thread、MessageQueue一定是绑定在一起的,因为他的作用就是轮询处理当前线程的消息队列,从它的构造器就可以看出:

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

Looper类常用方法:

  • Looper.prepare()方法:为当前线程创建一个Looper

//判断当前线程是否有Looper,没有就创建一个Looper,并且放到当前线程的ThreadLocal中。
//ThreadLocal:每个线程都有,并且是线程私有的,用于存储线程私有数据
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));
}

  • Looper.prepareMainLooper()方法:和prepare方法类似,只是它专用于给主线程创建Looper。

  • Looper.loop()方法:用于消息轮询,以下是loop方法部分重要代码解析。

public static void loop() {
//获取线程的Looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取Looper需要轮训的消息队列
final MessageQueue queue = me.mQueue;
//开启无限的消息轮询(死循环),唯一跳出循环的方法就是下面msg为空,即MessageQueue调用quit方法退出 Looper。当消息队列被标记为退出状态时,next会返回null。
for (;;) {
//取出需要处理的消息,next是一个阻塞方法,当获取不到要处理的消息时,就会阻塞在这里。当消息队列标记为退出状态是时,不会阻塞,会返回null。接下来,判断到msg为null,就会退出循环。
Message msg = queue.next(); // might block
//如果上面next方法没有阻塞,msg就不可能为空。只有在Looper退出后(调用quit方法),msg才会为空。
if (msg == null) {
return;
}
try {
//msg.target其实是一个Handler对象,这句代码就是处理消息的关键代码,将Message交给Handler去处理
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//参数复位操作
msg.recycleUnchecked();
}
}

  • Looper.quit()方法:退出Looper方法,无论消息是否处理完,直接退出。当Looper退出后,Handler发送消息会失败。
  • Looper.quitSafely()方法:退出Looper方法,要等所有消息处理完后,才会退出。

Handler工作原理:
简介:Handler的主要任务是发送一条消息和获取并处理一条消息。接下来我们总结一下Handler发送消息和处理消息的一些常用方法,并且分析其实现原理。

  • Handler.sendMessage(Message msg)方法:下面是sendMessage方法调用逻辑链。

public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}

在sendMessage内部调用sendMessageDelayed方法:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis <0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

在sendMessageDelayed内部调用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);
}

通过sendMessageAtTime方法逻辑可以看出,最终还是调用enqueueMessage方法将Message插入到消息队列。接下来的逻辑就不用多说了吧。Looper轮询就该上场了…

Handler调用链:sendMessage->sendMessageDelayed->sendMessageAtTime->Looper轮询Handler处理消息。

  • Handler.post(Runnable r)方法:post方法上面已经详细分析了,调用的也是sendMessageDelayed方法去处理Runnable中的Message。

    上面是两个重要的发送消息的方法,接下来看一下当Looper轮询检测到新消息时的处理方法。上面提到,当Looper调用loop方法轮询的时候,检测到新消息会有这么一行代码:msg.target.dispatchMessage(msg),这就是消息分发处理的方法,下面重点讲解这个方法。

    Handler.dispatchMessage(Message msg)方法:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

其中的msg.callback实在创建Message的时候声明的,声明方式如下:

//在创建Message的时候匿名创建的runable对象,一般用不到
Message message = Message.obtain(handler, new Runnable() {
@Override
public void run() {
}
});

  • msg.callback不为空的情况:首先检测Message的callback是否为null,不为null就通过handleCallback方法处理消息。msg.callback是一个runable对象,就是我们通过post方法传递进来的那个runable。

//直接调用Runable的run方法,处理逻辑。即调用上面创建Message时自己实现的run方法
private static void handleCallback(Message message) {
message.callback.run();
}

  • msg.callback为空的情况:检测mCallback是否为null。不为null,就调用mCallback的handleMessage方法处理这条消息;当mCallback为null的时候,直接调用Handler中的handleMessage方法处理消息。其实Handler中的handleMessage方法是一个空实现的方法体,也就是说当mCallback也为null的时候,就不对这条消息进行处理了。接下来再说略显复杂的mCallback不为空的情况。

    mCallback是一个接口,看接口里面的声明方法public boolean handleMessage(Message msg),这不就是我们创建Handler的时候处理消息的方法嘛。别急,看一下在哪里给mCallback赋值的。没错,就是在Handler的构造器里面对它进行的赋值操作。因此,mCallback也是我们在创建Handler的时候自己定义的,处理消息的逻辑也就是我们自定义实现的。

//这就是我们创建Handler对象时常用方法,这里匿名创建的Callback就是上面提到的Handler中的mCallback。
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//处理Message
return false;
}
});

总结:Handler发送消息和处理消息的方法都介绍完了,想必大家都很清楚其中的实现思路了。最后,我们总结一下整个Handler消息处理机制的流程:

  1. 通过handler.post(runable)或handler.sendMessage(msg)方法发送消息。(其中调用sendMessageDelayed->sendMessageAtTime等一系列方法)。
  2. 通过上面的方法,将Message添加至MessageQueue消息队列。
  3. Looper通过loop方法对MessageQueue进行轮询,没有新消息就阻塞。
  4. 检测到有新消息,调用dispatchMessage(msg)方法处理消息。
  5. 调用message.callback.run()或mCallback.handleMessage()方法完成对消息的处理。

结语:希望通过本文的分析,让小伙伴们对Handler消息处理机制能有更深刻的理解。如有不对的地方,望大家指出;如果对您有帮助,望多多支持。


推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 在 Flutter 开发过程中,开发者经常会遇到 Widget 构造函数中的可选参数 Key。对于初学者来说,理解 Key 的作用和使用场景可能是一个挑战。本文将详细探讨 Key 的概念及其应用场景,并通过实例帮助你更好地掌握这一重要工具。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细介绍如何在VSCode中配置自定义代码片段,使其具备与IDEA相似的代码生成快捷键功能。通过具体的Java和HTML代码片段示例,展示配置步骤及效果。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • Java 类成员初始化顺序与数组创建
    本文探讨了Java中类成员的初始化顺序、静态引入、可变参数以及finalize方法的应用。通过具体的代码示例,详细解释了这些概念及其在实际编程中的使用。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 深入了解 Windows 窗体中的 SplitContainer 控件
    SplitContainer 控件是 Windows 窗体中的一种复合控件,由两个可调整大小的面板和一个可移动的拆分条组成。本文将详细介绍其功能、属性以及如何通过编程方式创建复杂的用户界面。 ... [详细]
author-avatar
寡凫lo单鹄官方
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有