安卓中的几种线程间通信方式

article/2025/9/24 17:53:01

一:Handler实现线程间的通信

         andriod提供了 Handler 和 Looper 来满足线程间的通信。例如一个子线程从网络上下载了一副图片,当它下载完成后会发送消息给主线程,这个消息是通过绑定在主线程的Handler来传递的。

在Android,这里的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,这个事android的新 概念。我们的主线程(UI线程)就是一个消息循环的线程。针对这种消息循环的机制,我们引入一个新的机制Handle,我们有消息循环,就要往消息循环里 面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除,消息的的处理,把这些都封装在Handle里面,注意Handle只是针对那 些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。

但是这里还有一点,就是只要是关于UI相关的东西,就不能放在子线程中,因为子线程是不能操作UI的,只能进行数据、系统等其他非UI的操作。

  在Android,这里的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,这个是android的新概念。我们的主线程(UI线程)就是一个消息循环的线程。针对这种消息循环的机制,我们引入一个新的机制Handler,我们有消息循环,就要往消息循环里面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除,把这些都封装在Handler里面,注意Handler只是针对那 些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。

但是这里还有一点,就是只要是关于UI相关的东西,就不能放在子线程中,因为子线程是不能操作UI的,只能进行数据、系统等其他非UI的操作。

 

  一个Handler的创建它就会被绑定到这个线程的消息队列中,如果是在主线程创建的,那就不需要写代码来创建消息队列了,默认的消息队列会在主线程被创建。但是如果是在子线程的话,就必须在创建Handler之前先初始化线程的消息队列。

 

二、解决线程间通信的问题:使用AsyncTask:

 

借助AsyncTask(抽象类),即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。(AsyncTask背后的实现原理也是基于异步消息处理机制的,知识Android帮我们做好了封装而已)

基本用法:

1、创建一个类去继承AsyncTask抽象类,同时可以为AsyncTask类指定三个泛型参数:

         Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;

         Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位;

         Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型;

示例如下:

ClassDownloadTask extends AsyncTask<Void, Integer, Boolean>{

……

}

这里把第一个参数泛型类型指定为void:表示在执行AsyncTask的时候不需要传入参数给后台任务;

第二个参数泛型类型指定为Integer:表示使用整型数据来作为进度显示单位;

第三个参数泛型类型指定为Boolean:表示使用布尔型数据来反馈执行结果;

2、重写AsyncTask类中的几个方法,完成对任务的定制:

①onPreExecute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

②doInBackground(Params…):这个方法中的所有代码都会在子线程中运行,应在这里去处理所欲的耗时任务。任务一旦完成,可以通过return语句来将任务的执行结果返回。如果AsyncTask的第三个泛型参数类型指定为Void,就可以不返回任务执行结果。【注意,这个方法中是不可以进行UI操作的,如需要更新UI元素,比如反馈当前任务的执行进度,可以调用publishProgress(Progress…)方法来完成。】

③onProgressUpdate(Progress…):当在后台任务重调用了publishProgress(Progress…)方法后,这个方法就会很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应地更新。

④onPostExecute(Rusult):当后台任务执行完毕,并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,在这里进行一些收尾的工作,如提醒任务结束,关闭掉进度条对话框等。

3、启动任务:new一个该类的实例,然后调用其execute()方法;

三:EventBus实现线程间的通信

背景介绍

如果你学习过设计模式,那么当想通知其他组件某些事情发生时你一定会使用观察者模式。好了,既然能想到这个设计模式,那么就来看一个屌爆天的Android开源框架EventBus。主要功能是替代Intent、Handler、BroadCast在Fragment、Activity、Service、线程之间传递消息。他的最牛逼优点是开销小,代码简洁,解耦代码。

  • Event:事件
  • Subscriber:事件订阅者,接收特定的事件。
  • Publisher:事件发布者,用于通知Subscriber有事件发生。

其中,Event可以使任意类型对象。Subscriber都是以约定的onEvent开头的函数,具体是onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个。Publisher可以通过post(Object)在任意线程任意位置发送事件。

官方这个图就很直观的说明了这种观察者模式的架构:

这里写图片描述

依据开源库组件的说明文档来操作:

  1. 在工程gradle中添加:compile ‘de.greenrobot:eventbus:2.4.0’。
  2. 按照文档HOWTO.md进行操作。

Subscriber以onEvent开头的4个函数区别:

  • onEvent:事件的处理在和事件的发送在相同的线程,所以事件处理时间不应太长,不然影响事件的发送线程。

  • onEventMainThread: 事件的处理会在UI线程中执行。事件处理时间不能太长,长了会出现臭名远之的ANR。

  • onEventBackgroundThread:事件的处理会在一个后台线程中执行。虽然名字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接在当前后台线程执行事件;如果当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。

  • onEventAsync:事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程,但最好限制线程的数目。

四:详细介绍几种方法

1、runOnUiThread()

子线程中持有当前Activity引用(假如为Activity mActivity;),即可以调用mActivity的runOnUiThread(Runnable r)方法。

2、post()和postDelay()

子线程如果持有某个View的引用,要对该View进行更新,则可调用该View对象的post(Runnable r)或postDelay(Runnable r)方法

Handler对象也有post()方法。其实在Android的源码中,这些post()方法都是借助下面的第3种方法:Handler + Message来实现的。

3、Handler + Message或者Handler + Thread + Message

主线程建立时,默认情况下是有Looper的,可以处理消息队列。

在主线程建立一个Handler对象,复写其handleMessage()方法,在该方法中实现UI更新。

示例:

复制代码

private static final int MSG_CODE = 1001;private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg){//接收并处理消息if(msg.what == MSG_CODE){//UI更新}}};public void doSomething(){new Thread(){@Overridepublic void run(){//子线程发送信息Message msg = mHandler.obtainMessage(MSG_CODE);msg.sendToTarget();}}.start();}

4、Broadcast

子线程中发送广播,主线程中接收广播并更新UI

5、AsyncTask

AsyncTask可方便地实现新开一个线程,并将结果返回给UI线程,而不需要开发者手动去新开一个线程,也无须开发者使用Handler,非常方便。

应当注意的是AsyncTask是一个抽象类,其三个泛型参数的意义如下:
AsyncTask<Param, Progress, Result>
Param:发送给新开的线程的参数类型
Progress:表征任务处理进度的类型。
Result:线程任务处理完之后,返回给UI线程的值的类型。

该类中有四个抽象函数,onPreExecute(), doInBackground(Params... param),
onProgressUpdate(Progress... progress), onPostExecute(Result result)。
除了,doInBackground(Params...)方法,其它三个方法都运行在UI线程。

自定义一个类继承AsyncTask并至少实现doInBackground()函数。在该函数中执行的过程中,可以随时调用publishProgress(Progress...)报告其执行进度。此时会触发另一个方法onProgressUpdate(Progress... progress),以便在UI线程中的某些控件(如ProgressBar)上更新任务处理的进度。

也可以等doInBackground()执行完,进入onPostExecute()方法后,再进行UI控件的更新。

可在任意时间,任意线程中,取消AsyncTask开启的任务(调用自定义的AsynTask子类的cancel(boolean mayInterruptIfRunning)方法)

使用示例如下:

//如果没记错的话,这个例子应该是之前总结的时候从官网剪下来的

public void onClick(View v) {new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {/** The system calls this to perform work in a worker thread and* delivers it the parameters given to AsyncTask.execute() */protected Bitmap doInBackground(String... urls) {return loadImageFromNetwork(urls[0]);}/** The system calls this to perform work in the UI thread and delivers* the result from doInBackground() */protected void onPostExecute(Bitmap result) {mImageView.setImageBitmap(result);}}

6、EventBus

  • 什么是EventBus

    EventBus是Android下高效的发布/订阅事件总线机制。作用是可以代替传统的Intent,Handler,Broadcast或接口函数在Fragment,Activity,Service,线程之间传递数据,执行方法。特点是代码简洁,是一种发布订阅设计模式(Publish/Subsribe),或称作观察者设计模式。

 

  • 下载EventBus

    1. 下载EventBus库:

    2. EventBus-2.4.0.jar放入libs即可

 

  • 如何使用EventBus

    1. 定义事件, 定义一个类,继承默认的Object即可,用于区分事件和传输数据。 本例为MsgEvent1和MsgEvent2
    2. 添加订阅者:EventBus.getDefault().register(this); 将所在类作为订阅者,框架会通过反射机制获取所有方法及其参数。
        订阅者所在类可以定义以下一个或多个方法用以接收事件:

         public void onEvent(MsgEvent1 msg)public void onEventMainThread(MsgEvent1 msg)public void onEventBackgroundThread(MsgEvent1 msg)public void onEventAsync(MsgEvent1 msg

   3.发布者发布事件:EventBus.getDefault().post(new MsgEvent1("主线程发的消息1"));
      一旦执行了此方法, 所有订阅者都会执行第二步定义的方法。
   4. 取消订阅:EventBus.getDefault().unregister(this); 当订阅者不再被使用,或者被关闭时,最好进行取消订阅,不再接受事件消息。
   5. 注意事项:发布者post方法参数是Object类型,也就是可以发布任何事件。订阅者接受消息时,只要定义的是第二步四个方法任意一个,并且参数和发布者发布的一致,即可被执行。发布者也可以通过第二步接收消息,订阅者也可以作为发布者发消息给自己。

    • 代码实现 (本例是两个Fragment交互, 也可以是Service,Activity,Fragment以及任意类之间交互)
    • 点击左边面板的条目, 可以发送事件,右面板(另一个Fragment)接收到事件,显示界面,打印日志。
    • 代码下载 http://yunpan.cn/cctFTVuWtyIgK  访问密码 66ed

 

      

1.主界面搭建:
java

public class MainActivity extends FragmentActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}}

 

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:divider="?android:attr/dividerHorizontal"android:orientation="horizontal"android:showDividers="middle"android:baselineAligned="false"tools:context="com.itheima.eventbusdemo.MainActivity" ><fragmentandroid:id="@+id/left_fragment"android:name="com.itheima.eventbusdemo.LeftFragment"android:layout_width="0dip"android:layout_height="match_parent"android:layout_weight="1" /><fragmentandroid:id="@+id/right_fragment"android:name="com.itheima.eventbusdemo.RightFragment"android:layout_width="0dip"android:layout_height="match_parent"android:layout_weight="3" /></LinearLayout

2定一个事件类MsgEvent1 (MsgEvent2与此一致):public class MsgEvent1 {
private String msg;

public MsgEvent1(String msg) {
super();
this.msg = msg;
}
public String getMsg() {
return msg;
}

 

3. 将右面板作为订阅者, 执行方法并接收数据:

public class RightFragment extends Fragment {private TextView tv;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);                // 界面创建时,订阅事件, 接受消息EventBus.getDefault().register(this);}@Overridepublic void onDestroy() {super.onDestroy();// 界面销毁时,取消订阅EventBus.getDefault().unregister(this);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// 布局只有一个TextView,不再贴代码View view = inflater.inflate(R.layout.fragment_right, null);tv = (TextView) view.findViewById(R.id.tv);return view;}/*** 与发布者在同一个线程* @param msg 事件1*/public void onEvent(MsgEvent1 msg){String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId();System.out.println("onEvent(MsgEvent1 msg)收到" + content);}/*** 执行在主线程。* 非常实用,可以在这里将子线程加载到的数据直接设置到界面中。* @param msg 事件1*/public void onEventMainThread(MsgEvent1 msg){String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId();System.out.println("onEventMainThread(MsgEvent1 msg)收到" + content);tv.setText(content);}/*** 执行在子线程,如果发布者是子线程则直接执行,如果发布者不是子线程,则创建一个再执行* 此处可能会有线程阻塞问题。* @param msg 事件1*/public void onEventBackgroundThread(MsgEvent1 msg){String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId();System.out.println("onEventBackgroundThread(MsgEvent1 msg)收到" + content);}/*** 执行在在一个新的子线程* 适用于多个线程任务处理, 内部有线程池管理。* @param msg 事件1*/public void onEventAsync(MsgEvent1 msg){String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId();System.out.println("onEventAsync(MsgEvent1 msg)收到" + content);}/*** 与发布者在同一个线程* @param msg 事件2*/public void onEvent(MsgEvent2 msg){String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId();System.out.println("onEvent(MsgEvent2 msg)收到" + content);tv.setText(content);}

4. 在左面板发布消息。(任意类都可以发布消息)

public class LeftFragment extends ListFragment {@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);String[] strs = new String[]{"主线程消息1", "子线程消息1", "主线程消息2"};setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, strs));}@Override)      public void onListItemClick(ListView l, View v, int position, long id) {switch (position) {case 0:// 主线程System.out.println("----------------------主线程发的消息1" + " threadName: "+ Thread.currentThread().getName() + " threadId: " + Thread.currentThread().getId());EventBus.getDefault().post(new MsgEvent1("主线程发的消息1"));break;case 1:// 子线程new Thread(){public void run() {System.out.println("----------------------子线程发的消息1" + " threadName: "+ Thread.currentThread().getName() + " threadId: " + Thread.currentThread().getId());EventBus.getDefault().post(new MsgEvent1("子线程发的消息1"));};}.start();break;case 2:// 主线程System.out.println("----------------------主线程发的消息2" + " threadName: "+ Thread.currentThread().getName() + " threadId: " + Thread.currentThread().getId());EventBus.getDefault().post(new MsgEvent2("主线程发的消息2"));break;}}

分别点击左边条目, Log输出分析

 

EventBus框架原理流程图

        


1. Publisher是发布者, 通过post()方法将消息事件Event发布到事件总线
2. EventBus是事件总线, 遍历所有已经注册事件的订阅者们,找到里边的onEvent等4个方法,分发Event
3. Subscriber是订阅者, 收到事件总线发下来的消息。即onEvent方法被执行。注意参数类型必须和发布者发布的参数一致。
 


http://chatgpt.dhexx.cn/article/iV7KubpY.shtml

相关文章

Java中的线程通信的几种方式

Java中的线程间通信是指不同线程之间相互协作&#xff0c;以完成一些复杂的任务或实现某些功能的过程。线程间通信主要包括两个方面&#xff1a;线程之间的互斥和同步&#xff0c;以及线程之间的数据共享和通信。Java提供了多种方式来实现线程间通信&#xff0c;本文将介绍Java…

创建线程的四种方式 线程通信

文章目录 1.1 创建线程1.1.1 创建线程的四种方式1.1.2 Thread类与Runnable接口的比较1.1.3 Callable、Future与FutureTask 1.2 线程组和线程优先级1.3 Java线程的状态及主要转化方法1.4 Java线程间的通信1.4.1 等待/通知机制1.4.2 信号量1.4.3 管道 1.1 创建线程 1.1.1 创建线…

【多线程间几种通信方式】

一、使用 volatile 关键字 基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想。大致意思就是多个线程同时监听一个变量&#xff0c;当这个变量发生变化的时候 &#xff0c;线程能够感知并执行相应的业务。这也是最简单的一种实现方式 代码案例 package com.han…

线程之间的通信方式

前言 我只是个搬运工&#xff0c;尊重原作者的劳动成果&#xff0c;本文来源下列文章链接&#xff1a; https://zhuanlan.zhihu.com/p/129374075 https://blog.csdn.net/jisuanji12306/article/details/86363390 线程之间为什么要通信&#xff1f; 通信的目的是为了更好的协…

Java线程间的通信方式

文章目录 线程间通信的定义一、等待—通知&#xff08;1&#xff09;等待—通知机制的相关方法&#xff1a;&#xff08;2&#xff09;注意事项&#xff1a;&#xff08;4&#xff09;notify()方法的核心原理&#xff08;5&#xff09;等待—通知机制的经典范式&#xff08;6&a…

线程间实现通信的几种方式

目录 线程通信相关概述提出问题方式一&#xff1a;使用Object类的wait() 和 notify() 方法方式二&#xff1a;Lock 接口中的 newContition() 方法返回 Condition 对象&#xff0c;Condition 类也可以实现等待/通知模式方法三&#xff1a;使用 volatile 关键字方法四&#xff1a…

线程间的通信方式

对共享数据进行更改的时候&#xff0c;先到主内存中拷贝一份到本地内存中&#xff0c;然后进行数据的更改&#xff0c;再重新将数据刷到主内存&#xff0c;这中间的过程&#xff0c;其他线程是看不到的。 1、为什么需要线程通信 线程是操作系统调度的最小单位&#xff0c;有自…

进程和线程的几种通信方式

进程之间通信的几种方式 1. 管道&#xff1a;是内核里面的一串缓存 管道传输的数据是单向的&#xff0c;若相互进行通信的话&#xff0c;需要进行创建两个管道才行的。 2. 消息队列&#xff1a; 例如&#xff0c;A进程给B进程发送消息&#xff0c;A进程把数据放在对应的消息队…

线程的几种通信方式

目录 一、Object的wait()、notify()、notifyAll()方法 二、Condition的await()、signal()、signalAll()方法 三、CountDownLatch 四、CyclicBarrier 五、Semaphore 线程间的通信方式常用的有如下几种&#xff1a; Object的wait()、notify()、notifyAll()方法&#xff1b; …

线程间的通信方法

线程间的通信方法 1. 线程通信简介 一般而言&#xff0c;在一个应用程序&#xff08;即进程&#xff09;中&#xff0c;一个线程往往不是孤立存在的&#xff0c;常常需要和其它线程通信&#xff0c;以执行特定的任务。如主线程和次线程&#xff0c;次线程与次线程&#xff0c…

Matlab基本操作函数 abs函数

分享一下我老师大神的人工智能教程&#xff01;零基础&#xff0c;通俗易懂&#xff01;http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01; 1、abs函数&#xff1a;数值的绝对值和复数…

MATLAB中FFT的整理

作为一个资深的健忘症患者&#xff0c;需要把每次用都忘记的FFT问题进行整理。 FFT可将信号从时域转换到频域。 首先是一些简单常识&#xff1a; 采样周期&#xff1a;两次采样之间的时间间隔。 采样频率&#xff1a;1/采样周期。每秒采样的点数。&#xff08;注意&#xff1a…

matlab中abs函数,matlababs是什么意思 是是是什么意思

matlababs是什么意思 是是是什么意思以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! matlab 中的abs函数什么意思 编程知识 matlab中的abs(x)是去绝对值的函数 例如:x=-1.23 abs(x) ans 1.23 以上即是取了-1.23…

Matlab 用法

MATLAB基础&#xff1a; 清除命令 clc 清空命令行的命令 clf 清除当前figure中的内容 clear 清除工作区变量 close all 关闭所有图形窗口 清除命令通常放在代码最前方&#xff0c;避免其他变量或代码的干扰 变量命名规则 ①以英文字母开头&#xff0c;可包含英文字母、…

abs 三种功能及代码详解 matlab函数

1.abs函数功能 求实数的绝对值、复数的模、字符串的ASCII值 2.基本用法 abs(x)函数是对数组元素进行绝对值处理的函数。 函数的定义域包括复数。 对于复数xab*i&#xff0c;有abs(x)sqrt(a2b2)。 3.代码 clc; clear all;a -7; b 12i; abs(a…

android 屏幕坐标总结

android 屏幕坐标好多个&#xff0c;有时候傻傻分不清楚&#xff0c;经常记错&#xff0c;然后只能一个个试。尴尬&#xff5e;&#xff5e; 把它们总结下来&#xff0c;以备不时之需嘿嘿。 一、视图坐标 最外面一层是屏幕&#xff0c;左上角是坐标原点&#xff0c;向右向…

【Unity3D】世界坐标与屏幕坐标

Unity3D由于是在三维世界中编程&#xff0c;而最终的结果是需要反馈到肉眼所示的2D屏幕之上的。这就产生了一种比较需要考虑的问题&#xff0c;尤其在一些涉及屏幕与Unity3D的3D世界交互的情况。网络上对于这方面的文字&#xff0c;大部分罗列了许许多多文字与代码或者API&…

Unity世界坐标转换屏幕坐标(测试)

下面展示一下上一篇说的两种实现方式打包文件在不同分辨率下的效果 1.WorldToScreenPoint 1920 * 1080 800 * 600 2.WorldToViewportPoint 1920 * 1080 800 * 600 总结 可以看到四种情况全部都显示正确&#xff0c;我们再看一下原来的代码 public Vector3 GetScreenPositio…

Unity 屏幕坐标转UI坐标

1&#xff1a;屏幕坐标转UI坐标 首先我们来明确下三个坐标概念&#xff1a; 世界坐标&#xff1a;指的是Transform组件的position字段 UI坐标&#xff1a;指的是RectTransform组件的anchoredPosition字段 屏幕坐标&#xff1a;指的是屏幕空间的坐标 (也可以说是相机空间的坐…

经纬度转换成屏幕坐标

学期projet总结&#xff1a; 当把点的数据和线的数据读进来之后&#xff0c;为了画出地图还有最重要的一步就是把实际的经纬度转换成屏幕像素点的坐标。在找老师讨论之前&#xff0c;我在网上查资料&#xff0c;找到了下边链接的文章&#xff0c;并按照这个方法画出了地图。 …