android线程间通信的几种方法_Android线程间通信机制

android线程间通信的几种方法_Android线程间通信机制讲解Handler机制的博文很多,我也看了很多,但说实话,在我对Handler几乎不怎么了解的情况下,每一篇文章我都没太看懂,看完之后脑子里还是充满了疑问。究其原因,是因为几乎每一篇文章一上来就开始深入Handler源码,使得在一些宏观的问题上还是充满疑问,如果你从来没接触过Handler,对一些基础的问题还充满疑问,那深入源码去探究根源肯定会有些吃力。下面,我就从一个初学者思考的角度,来讲一讲H…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

讲解Handler机制的博文很多,我也看了很多,但说实话,在我对Handler几乎不怎么了解的情况下,每一篇文章我都没太看懂,看完之后脑子里还是充满了疑问。究其原因,是因为几乎每一篇文章一上来就开始深入Handler源码,使得在一些宏观的问题上还是充满疑问,如果你从来没接触过Handler,对一些基础的问题还充满疑问,那深入源码去探究根源肯定会有些吃力。

下面,我就从一个初学者思考的角度,来讲一讲Handler运行机制,准确的说应该是Android消息处理机制,因为虽然说Handler很重要,它最多被提到,但其它的几个角色也是各司其职,一个都不能少。

先完整叙述一遍我们要讲解的问题:Android线程间通信机制

一、两个关键词,两点疑问##

在”Android线程间通信机制”这句话中,有两个关键词需要我们需要搞清,一个是线程间,一个是通信,当我一开始深入思考这两个关键词的时候,心中就有了些疑问,可是很多博文也没有讲到,这也是导致我一开始看别人写的文章就稀里糊涂的原因,发现文章看完,疑惑还在,而疑惑就来源于这两个关键词。

1、线程间###

我们知道,Android应用程序的一个进程当中可能会存在多个线程,但它们的地位是不一样的,分为两种:有一个是主线程(也叫UI线程),其它的都是普通的工作线程。那么线程间通信就会分为两种情况:主线程和工作线程通信;工作线程和工作线程通信。

之所以要把这两种情况给提出来,是因为基本上所有的文章都是上来直接讲主线程和普通工作线程之间的通信机制,我还没有看到过讲两个普通工作线程通信的,这就让我有了一个疑问,Q1:难道线程间通信只能发生在主线程和工作线程之间吗?而没有两个工作线程通信的情况吗?

答案是可以的,可能是因为主线程和工作线程通信的情况最常见,例如工作线程向主线程发送消息进行更新UI的操作,而两个工作线程通信的情况比较少见吧(我也是猜测,毕竟我的开发经验太有限)。虽然主线程和普通工作线程地位不同,但只要使用“线程间通信机制”(我们下面要讲的),线程间都是可以互相通信的。相信大家看完文章,不用解释就自然明白了。

2、通信###

通信是一个过程,但这个表达很模糊,不够具体,我们把它具体描述应该是:发送消息 + 接收消息 + 处理消息,这样一来定义就清晰了一些。如果我们把这个过程想到这儿,看着好像也明白了,不就那么个过程嘛。但如果大家再多想一步,仔细思考下这个过程,就会心生疑问,至少我当时就有这个疑问,是什么呢,大家先看两张图,是我画的两种通信方式的模型,下面我会解释。

7657f541c461

线程间通信.模型猜想一

我们知道两个线程通信是使用Handler的,这个模型的意思是:我们使用一个Handler进行通信,线程A和线程B都可以发送消息给对方、接收对方传来的消息以及进行消息处理。

7657f541c461

线程间通信.模型猜想二

这个模型的意思是:我们使用一个Handler进行通信,线程A和线程B之间,只有一个可以接收消息并处理,另一个只能够发送。

那么我的疑问就是,Q2:当我们使用一个Handler进行线程间通信时,到底这两个模型,哪个是正确的?答案是第二个,解释在后面,或许当你看完,也不需要我解释了。

下面开始正式讲解。

二、消息处理中的几大角色##

先给出一张图,这张图是我从别人的博文中看见的,画的不错,我直接拿过来用了,在文末有参考链接。

7657f541c461

Android线程间通信流程图

Message:

线程间通信就是在传递消息,Message就是消息的载体。常用的有四个字段:arg1,arg2,what,obj。obj可以携带Object对象,其余三个可以携带整形数据。

MessageQueue:

MessageQueue是消息队列,它主要用于存放所有通过Handler发送的消息(也就是一个个Message),这部分的消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

Looper:

每个线程通过Handler发送的消息都保存在,MessageQueue中,Looper通过调用loop()的方法,就会进入到一个无限循环当中,然后每当发现Message Queue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。

Handler:

它主要用于发送和处理消息的发送消息,一般使用sendMessage()方法,还有其他的一系列sendXXX的方法,但最终都是调用了sendMessageAtTime()方法,除了sendMessageAtFrontOfQueue()这个方法。你只要在Looper线程(就是实现了Looper的线程)构建Handler类,那么这个Handler实例就获取该Looper线程MessageQueue实例的引用,Handler 在sendMessage()的时候就通过这个引用往消息队列里插入新消息。

ThreadLocal:

这个类我也没太搞懂,不过参考别人的文章有如下一个解释,可以先暂时这样理解:线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。MessageQueue对象,和Looper对象在每个线程中都只会有一个对象,怎么能保证它只有一个对象,就通过ThreadLocal来保存

三、创建Handler的两种方式##

Handler的创建方式有两种:一个是在主线程中创建,一个是在普通工作线程中创建,两种创建方法是不一样的。Handler在哪个线程中创建,那该线程就负责接收和处理消息,其它的线程只能发送消息。为什么?请往下看。

1、在主线程中使用Handler###

在主线程中使用Handler的示例:

public class TestHandlerActivity extends AppCompatActivity {

private static final String TAG = “TestHandlerActivity”;

private Handler mHandler = new Handler(){

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

//获得刚才发送的Message对象,然后在这里进行UI操作

Log.e(TAG,”————> msg.what = ” + msg.what);

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_handler_test);

initData();

}

private void initData() {

//开启一个线程模拟处理耗时的操作

new Thread(new Runnable() {

@Override

public void run() {

SystemClock.sleep(2000);

//通过Handler发送一个消息切换回主线程(mHandler所在的线程)

mHandler.sendEmptyMessage(0);

}

}).start();

}

大家如果使用过Handler,这个应该是最常见的了,也是使用最简单的。只需在主线程创建一个handler对象,在子线程通过在主线程创建的handler对象发送Message,在handleMessage()方法中接受这个Message对象进行处理。通过handler很容易的从子线程切换回主线程了。

2、在普通工作线程中使用Handler###

我们下面再看一下,在普通工作线程中应该如何使用Handler:

public class TestHandlerActivity extends AppCompatActivity {

private static final String TAG = “TestHandlerActivity”;

//主线程的Handler

private Handler mHandler = new Handler(){

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

//获得刚才发送的Message对象,然后在这里进行UI操作

Log.e(TAG,”————> msg.what = ” + msg.what);

}

};

//子线程中的Handler

private Handler mHandlerThread = null;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_handler_test);

initData();

}

private void initData() {

//开启一个线程模拟处理耗时的操作

new Thread(new Runnable() {

@Override

public void run() {

SystemClock.sleep(2000);

//通过Handler发送一个消息切换回主线程(mHandler所在的线程)

mHandler.sendEmptyMessage(0);

//调用Looper.prepare()方法

Looper.prepare();

mHandlerThread = new Handler(){

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

Log.e(“sub thread”,”———> msg.what = ” + msg.what);

}

};

mHandlerThread.sendEmptyMessage(1);

//调用Looper.loop()方法

Looper.loop();

}

}).start();

}

与在主线程中创建的方式不同,在工作线程中创建的代码中,我们的Handler是在Looper.prepare()和Looper.loop()中间创建的,那这两行代码是做什么用的呢?我们看下源码:

public final class Looper {

…………

private static void prepare(boolean quitAllowed) {

//如果线程的TLS已有数据,则会抛出异常,一个线程只能有一个Looper,prepare不能重复调用。

if (sThreadLocal.get() != null) {

throw new RuntimeException(“Only one Looper may be created per thread”);

}

//往线程的TLS插入数据,简单理解相当于map.put(Thread.currentThread(),new Looper(quitAllowed));

sThreadLocal.set(new Looper(quitAllowed));

}

…………

}

在这里可以看出,sThreadLocal对象保存了一个Looper对象,首先判断是否已经存在Looper对象了,以防止被调用两次。sThreadLocal对象是ThreadLocal类型,因此保证了每个线程中只有一个Looper对象。Looper对象在创建时做了什么呢,我们进入看看,如下:

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

可以看出,这里在Looper构造函数中创建出了一个MessageQueue对象和保存了当前线程。从上面可以看出一个线程中只有一个Looper对象,而Message Queue对象是在Looper构造函数创建出来的,因此每一个线程也只会有一个MessageQueue对象。

所以,当Looper.prepare()执行完了之后,普通的工作线程就变成了Looper线程,该线程就可以接收并处理消息了。如图:

7657f541c461

而Looper.loop()方法就是进入一个无限循环,不断的从MessageQueue当中获取消息,当没有消息时就阻塞在那里,这里不再详解。

3、梳理一下###

从我们刚才的讲解可以明白:只有实现了Looper和MessageQueue的线程,才能够处理消息,否则一个线程都没有MessageQueue,它又哪来的消息可处理呢?没有Looper,谁来取消息呢?它俩是一套的。

因此,Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。如果该线程不是Looper线程,在这个线程new Handler 就会报错!

还记得我们刚开始提到的那个线程间通信的简单模型吗,就是下面这个:

7657f541c461

因为我们只创建了一个Handler,所以它必定持有某个线程(这里是线程B)的Looper引用和这个线程的消息队列的引用,也就只能在这一个线程中接收和处理消息,其它的只能发送消息。若想实现双向的通信,那就必须在令一个线程当中也创建Looper,并在该线程下再创建一个Handler。

4、一点疑问###

我们刚才讲了两种使用Handler的方式,一个是在主线程当中,一个是在其它的普通线程当中。而两种方式的不同就在于:在主线程当中创建Handler,并没有调用Looper.prepare()和Looper.loop()方法,也就是我们没有在主线程中创建Looper。那你可能就会问了,为什么主线程中没有创建Looper,它却可以用来处理消息。为什么?

因为系统在启动之时,已经帮我们创建好了。也就是说:在任何进程下使用Handler来处理消息,都必须要先创建Looper,在创建Looper的过程中同时也就创建了MessageQueue,否则无法处理消息。之所以会存在两种创建方式,就是因为主线程已经在开始的时候帮我们都准备好了Looper,不用我们手动调用Looper.prepare()和Looper.loop()了。

那系统是怎么做的呢?这个内容很多博文已经讲了,我会放上我参考的两个讲的比较好的文章在下面,大家感兴趣可以看看。

完。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/185112.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Hadoop切分纯文本时对某一行跨两个分片这种情况的处理

    Hadoop切分纯文本时对某一行跨两个分片这种情况的处理Hadoop切分纯文本时对某一行跨两个分片这种情况的处理

  • sql与hsql的区别以及分别怎么用!

    sql与hsql的区别以及分别怎么用!在java开发当中,会用到一些框架,比如说sh(struts和hibernate),ssh(struts,spring以及hibernate)等这些框架,hibernate因为连表方便,直接将表映射到java实体类中,因此用到的比较广泛,那sql和hsql区别在于哪里呢?又如何使用呢?1.java中用sql实现增删改查,sql是直接面向数据库的,下面附上一段代码解析:try{24…

  • URL 规范 整理

    URL 规范 整理

  • USB引脚及定义_u盘引脚数据线接线图

    USB引脚及定义_u盘引脚数据线接线图USB2.0USB接口定义:USB引脚定义:针脚名称说明接线颜色1VCC+5V电压红色2D-数据线负极白色3D+数据线正极绿色4GND接地黑色MiniU

  • Java经典算法(二)

    Java经典算法(二)【程序10】题目:将一个正整数分解质因数。例如:输入90,打印出90=233*5。程序分析:对n进行分解质因数,应先找到一个最小的质数k,然后按下述步骤完成:(1)如果这个质数恰等于n,则说明分解质因数的过程已经结束,打印出即可。(2)如果n!=k,但n能被k整除,则应打印出k的值,并用n除以k的商,作为新的正整数你n,重复执行第一步。(3)如果n不能被k整除,则用k+1作为k的值,重复执行第一步。解题代码:importjava.util.Scanner;publicclassTe

  • docker下载安装教程_mac docker 性能

    docker下载安装教程_mac docker 性能前言Docker提供轻量的虚拟化,你能够从Docker获得一个额外抽象层,你能够在单台机器上运行多个Docker微容器,而每个微容器里都有一个微服务或独立应用,例如你可以将Tomcat运行在一个D

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号