Android中的消息异步处理机制及实现方案

基本介绍

  • 当我们需要执行复杂的计算逻辑,网络请求等耗时操作时,服务器可能不会立即响应请求,如果不将这类操作放在子线程中运行,就会导致主线程被阻塞住,从而影响用户的使用体验
  • 如果想要更新应用程序中的UI控件,则必须在主线程中进行,否则就会出现android.view.ViewRootImpl$CalledFromWrongThreadException异常

代码实践

  • 有些时候,我们需要在子线程中执行一些耗时任务,再根据任务的执行结果来更新相应的UI控件,对此,Android提供了一套异步消息的处理机制,解决了在子线程中进行UI操作的问题,我们先来看看异步消息处理的使用方法,再来分析其中的原理
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;import com.example.myapplication1.R;import java.util.concurrent.TimeUnit;public class EventHandlerActivity extends AppCompatActivity {private static final int CALCULATE_KEY = 2024;private Button calculateBtn;private TextView resultTv;// 创建Handler实例,用于在主线程中更新UIprivate Handler myHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {if (msg.what == CALCULATE_KEY) {resultTv.setText("Result: " + msg.obj);}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_event_handler);calculateBtn = findViewById(R.id.calculateBtn);resultTv = findViewById(R.id.resultTv);calculateBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 点击按钮后,开始执行复杂计算calculateFunc();}});}private void calculateFunc() {// 创建一个新线程来执行耗时操作new Thread(new Runnable() {@Overridepublic void run() {long result = factorial(5);// 模拟复杂的耗时计算try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 发送消息给Handler,以便在主线程中更新UI// 另外,为了避免频繁地创建和销毁 Message 对象,可以使用 Message.obtain() 方法从消息池中获取一个消息实例,以减少内存分配和垃圾回收的频率Message message = Message.obtain();message.what = CALCULATE_KEY;message.obj = "calculate result = " + result;myHandler.sendMessage(message);// sendMessageDelayed(Message msg, long delayMillis): 在指定的延迟时间后发送Messag, delayMillis为单位为毫秒// myHandler.sendMessageDelayed(message,5000);}}).start();}private long factorial(int num){if (num < 2) return 1;return num * factorial(num - 1);}
}
  • activity_event_handler.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".handle.EventHandlerActivity"><Buttonandroid:id="@+id/calculateBtn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Calculate Factorial"android:textAllCaps="false"/><TextViewandroid:id="@+id/resultTv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/calculateBtn"android:layout_marginTop="16dp"/></RelativeLayout>
  • 如上代码,点击calculateBtn按钮,交由工作线程完成计算操作,将计算机结果显示在resultTv(交由主线程完成UI操作)

原理实现

  • Android中的异步消息处理主要由四部分组成:Message、Handler、MessageQueue和Looper,下面对这四部分来进行详细介绍

Message

  • Message是在线程之间传递的消息,可以在内部携带少量的数据,用于在不同线程间交换,其实例包含what
  • Message的what字段是一个整数值,可用来分区不同的消息类型,可为不同的任务或事件分配不同的what值,当调用Handler实例handleMessage方法时,可检查Message对象的what字段来确定如何处理该消息
  • arg1和arg2字段:均为整数类型字段,可携带what之外其他的整数类型数据
  • obj字段:Object类型字段,可携带字符串、数组、对象、Bundle等类型数据

Handler

  • Handler:处理者,主要用于发送和处理消息,发送消息一般是调用Handler实例的**sendMessage()方法,而发出的消息经过一系列辗转处理后,最终会交由handleMessage()**方法来处理
  • Handler是在主线程中创建的,handleMessage()方法也会在主线程中执行,故不存在UI操作引起的线程安全问题

MessageQueue

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

Looper

  • Looper:每个线程中MessageQueue的管理者,调用loop()之类的方法后,就会进入到消息的循环监听中,每当发现MQ中存在消息,就会将其取出,传递到handler.handleMessage()方法中;每个线程中也只用一个Looper对象

异步处理流程

  • 1)在主线程中创建Handler对象,并重写handleMessage()方法
  • 2)当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将Message发送出去
  • 3)发送出的Message会被添加到MessageQueue中等待被处理
  • 4)Looper会一直监听MessageQueue中的消息,一旦发现待处理的消息就取出,再分发到Handler的handleMessage()方法中处理消息
    在这里插入图片描述
    如上,Message经过一系列辗转调用后,由子线程完成耗时操作的处理,再由主线程完成UI操作,通过消息的异步处理机制解决UI操作可能会导致的线程安全问题

其他异步处理的实现

  • 异步处理还可以通过定时任务来实现,一种是Java API提供的Timer类,一种Android的Alarm机制,这两种实现在多数情况下都能实现类似的效果,但Timer不太适用于长期在后台运行的定时任务
  • 为能让电池耐用,Android手机会在长时间不操作的情况下自动让CPU进入到睡眠状态,这可能会导致Timer中的定时任务无法正常运行;而Alarm有唤醒CPU功能,可保证在大多数情况下需要执行定时任务时CPU都能正常工作
    下面重点介绍下Alarm的基本使用:

Alarm机制

  • Android的Alarm机制是一种系统服务可在将来的某个时间点触发定时操作,即使你的应用程序不在运行;这个机制由AlarmManager类提供,它可以用于执行定时任务,比如在特定时间发送通知、启动服务或者执行其他后台操作

主要组件:

  • AlarmManager:系统服务,负责管理和触发闹钟,可通过getSystemService(Context.ALARM_SERVICE)获取实例
  • PendingIntent:描述将要执行操作的意图对象,当闹钟触发时,AlarmManager会发送对应的PendingIntent
  • AlarmManagerService:AlarmManager的服务端实现,运行在系统进程中,负责处理闹钟的触发

基本使用如下:

    public void sendAlarm(){Intent intent = new Intent(ALARM_ACTION);// 创建用于广播的延迟意图PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_IMMUTABLE);// 从系统服务中获取闹钟管理器AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);// 闹钟的触发时机long triggerTime = SystemClock.elapsedRealtime() + 2 * 1000;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 允许在空闲时发送广播(Android6.0之后新增的方法)alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerTime, pendingIntent);} else {// 设置一次性闹钟,延迟若干秒后,携带延迟意图发送闹钟广播(Android6.0之后,set方法在暗屏时不保证发送广播)alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerTime,pendingIntent);}// 设置重复闹钟,每隔一定时间间隔就发送闹钟广播(从Android4.4开始,setRepeating方法不保证按时发送广播)//  alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),1000,pendingIntent);// 取消闹钟// alarmManager.cancel(pendingIntent);// 获取下一个闹钟的信息// alarmManager.getNextAlarmClock();}
使用说明
  • AlarmManager部分源代码如下:
@SystemService(Context.ALARM_SERVICE)
public class AlarmManager {private static final String TAG = "AlarmManager";// .../** @hide */@IntDef(prefix = { "RTC", "ELAPSED" }, value = {RTC_WAKEUP,RTC,ELAPSED_REALTIME_WAKEUP,ELAPSED_REALTIME,})// ...@Retention(RetentionPolicy.SOURCE)public @interface AlarmType {}
public void set(@AlarmType int type, long triggerAtMillis, @NonNull PendingIntent operation) {setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,(Handler) null, null, null);}//  ...
}

参数说明:

  • type:指定AlarmManager的工作类型,有四个选项:RTC_WAKEUP、RTC、ELAPSED_REALTIME_WAKEUP、ELAPSED_REALTIME
    RTC_WAKEUP:让定时任务的触发时间从1970年1月1日0点开始算起,不会唤醒CPU;
    RTC:让定时任务的触发时间从1970年1月1日0点开始算起,不会唤醒CPU
    同理,
    ELAPSED_REALTIME_WAKEUP:让定时任务的触发时机从系统开机算起,会唤醒CPU;
    ELAPSED_REALTIME:让定时任务的触发时机从系统开机算起,但不会唤醒CPU
    =》带WAKEUP的会唤醒CPU,带ELAPSED_REALTIME的从系统开机算起
  • triggerAtMillis:定时任务触发的时间,单位为毫秒
    SystemClock.elapsedRealtime():从系统开机至今所经历的毫秒数
    System.currentTimeMillis():从1970年1月1日0点至今所经历的毫秒数
  • PendingIntent对象:一般调用getService()或getBroadcast()方法来获取执行服务或广播的PendingIntent;当定时任务被触发时,服务的onStartCommand()或广播接收器onReceive()方法就可得到执行

扩展

前面我们知道了如何异步地处理消息,实现原理,现在再来全面地看看消息异步处理解决的问题:

  • 1)线程安全:Android UI 是非线程安全的,即所有的 UI 操作必须在主线程中执行;任何在工作线程中直接对 UI 进行操作都会导致不可预知的行为,甚至可能导致应用崩溃;消息异步处理机制确保了所有的 UI 更新都在主线程中执行,从而保证了线程安全
  • 2)避免ANR(Application Not Responding):如果主线程因为长时间运行的任务(如数据库操作,执行复杂计算,网络请求)而被阻塞,系统会认为应用无响应,可能会触发ANR;消息异步处理机制允许这类耗时任务在工作线程中执行,从而避免了主线程的阻塞,减少ANR的发生
  • 3)控制线程的生命周期:使用Handler和Looper,开发者可精细地控制线程的生命周期(如在线程完成所有任务后退出,或在线程空闲时清理资源)
  • 4)支持延时消息和定时任务:Handler提供了发送延时消息的功能,允许在将来的某个时间点执行任务
  • 5)提高用户体验:通过在工作线程中执行耗时任务,用户界面可以保持响应,提供流畅的用户体验;用户可以继续与应用交互,而不会因为后台任务的执行而感到延迟

实现异步处理的方式:Handler(sendMessage和sendMessageDelayed方法)、Timer、Alarm机制、WorkManager、Coroutine,篇幅受限,这里不多讲解

参考资料:

  • 《Android第一行代码》第二版

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/350664.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中List流式转换为Map的终极指南

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 在Java编程中&#xff0c;经常需要将一个List对象转换为另一个Map对象。这可能是因为需要根据List中的元素的某些属性来创建一个新的键值对集合。在本文中&#xff0c;我将向您展示如何使用Java 中的流式API轻松地实…

神经网络学习2

张量&#xff08;Tensor&#xff09;是深度学习和科学计算中的基本数据结构&#xff0c;用于表示多维数组。张量可以看作是一个更广义的概念&#xff0c;涵盖了标量、向量、矩阵以及更高维度的数据结构。具体来说&#xff0c;张量的维度可以是以下几种形式&#xff1a; 标量&am…

借助ChatGPT撰写学术论文,如何设定有效的角色提示词指

大家好&#xff0c;感谢关注。这个给大家提供关于论文写作方面专业的讲解&#xff0c;以及借助ChatGPT等AI工具如何有效辅助的攻略技巧。有兴趣的朋友可以添加我&#xff08;yida985&#xff09;交流学术写作或ChatGPT等AI领域相关问题&#xff0c;多多交流&#xff0c;相互成就…

Javaweb8 数据库Mybatis+JDBC

Mybatis Dao层&#xff0c;用于简化JDBC开发 1步中的实体类 int类型一般用Integer &#xff1a;如果用int类型 默认值为0,会影响数据的判断,用Integer默认值是null,不会给数据的判断造成干扰 2.在application .properties里配置数据库的链接信息-四要素 #驱动类名称 #URL #用…

高考志愿填报,选专业应该考虑哪些因素?

这是一个复杂的社会&#xff0c;各种影响就业的因素层出不穷&#xff0c;也从未断绝。对于高考生而言&#xff0c;高考刚结束&#xff0c;短暂的放松后&#xff0c;就必须考虑自身以后应该就读什么专业&#xff0c;如果不对就读专业进行评估&#xff0c;仔细挑选&#xff0c;毕…

微服务链路追踪ELK

微服务链路追踪&ELK 链路追踪概述链路追踪sluthzipkinelk日志管理平台 一 链路追踪 1 概述 1.1 为什么需要链路追踪 ​ 微服务架构是一个分布式架构&#xff0c;它按业务划分服务单元&#xff0c;一个分布式系统往往有很多个服务单元。由于服务单元数量众多&#xff0…

swiftui中使用icon图标时,让中间部分不透明显示

在使用了Image(systemName: "plus.circle.fill")这个视图组件后&#xff0c;发现中间的加号竟然是透明的&#xff0c;但是我们想要的是不让它透明&#xff0c;该怎么做呢&#xff1f; 最简单的方式就是给这个图片设置一个白色的背景是不是就好了&#xff1f;我们可以…

基于深度学习的红外船舶检测识别分类完整实现数据集8000+张

随着遥感技术的快速发展&#xff0c;包括无人机、卫星等&#xff0c;红外图像在船舶检测识别中的作用日益凸显。相对于可见光图像&#xff0c;红外图像具有在夜晚和恶劣天气条件下高效检测识别船舶的天然优势。近年来&#xff0c;深度学习作为一种强大的图像处理技术&#xff0…

网络通信的两大支柱:TCP与UDP协议详解(非常详细)零基础入门到精通,收藏这一篇就够了

在构建现代互联网通信的基石中&#xff0c;TCP&#xff08;传输控制协议&#xff09;和UDP&#xff08;用户数据报协议&#xff09;起着至关重要的作用。本文将深入探讨两者的区别及应用场景。 1 TCP和UDP的共同点 传输层协议&#xff1a; TCP和UDP都是传输层协议&#xff…

算法day32

第一题 207. 课程表 步骤一&#xff1a; 通过下图的课程数组,首先画出DAG图&#xff08;有向无环图&#xff09; 步骤二&#xff1a; 其次我们按照DAG图&#xff0c;来构建该图的拓扑排序&#xff0c;等有效的点都按照规则排完序后&#xff0c;观察是否有剩下的点的入度不为0&…

解决linux jenkins要求JDK版本与项目版本JDK不一致问题

背景–问题描述&#xff1a; 新入职公司&#xff0c;交接人说jenkins运行有问题&#xff0c;现在都是手动发布&#xff0c;具体原因让我自己看&#xff08;笑哭&#xff09;。我人都蒙了&#xff0c;测试环境都手动发布&#xff0c;那不是麻烦的要死&#xff01; 接手后&am…

养猫发现猫毛过敏?宠物空气净化器真的能拯救猫毛过敏吗?

广东省 猫咪是许多人梦寐以求的伴侣&#xff0c;但对于轻度猫毛过敏和鼻炎患者来说&#xff0c;养猫似乎是个遥不可及的梦想。我常在社交媒体上羡慕地观看朋友们的吸猫日常&#xff0c;却因过敏无法亲自养猫。这种遗憾驱使我寻找解决方案&#xff0c;从研究低过敏猫种到尝试空气…

洗地机哪款好?洗地机十大名牌排行榜

随着科技的发展&#xff0c;各种家居清洁工具层出不穷&#xff0c;为我们的生活带来了诸多便利。在众多清洁工具中&#xff0c;洗地机的清洁效果更受大家喜爱&#xff0c;它能够完美解决了扫地机无法做到的干湿垃圾“一遍清洁”效果&#xff0c;而且几乎能解决日常生活中所有的…

基于springboot实现交通管理在线服务系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现交通管理在线服务系统演示 摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装交通管理在线服…

❤ npm运行打包报错归纳

❤ 前端运行打包报错归纳 &#xff08;安装依赖&#xff09;Cannot read property ‘pickAlgorithm’ of null" npm uninstall //删除项目下的node_modules文件夹 npm cache clear --force //清除缓存后 npm install //重新安装 备用安装方式 npm install with --for…

调试了一下午,终于把tailwindcss搞进Blazor了

在Vue和Uniapp项目中使用tailwindcss后&#xff0c;实在是太香了&#xff0c;非常符合我这从XAML走过来的老程序员的手感&#xff0c;所以老想着在Blazor项目中引入。看了几个老外大佬的视频&#xff0c;调试了一下午&#xff0c;终于是捣鼓成功了。由于咱们Blazor项目不在node…

【Arthas案例】某应用依赖两个GAV不同但包含两个相同全限定类名StaticLoggerBinder,引起log4j.Level类找不到异常

3分钟内解决问题 两个不同的GAV依赖冲突&#xff0c;包含相同全限定类名&#xff0c;引起ClassNotFoundException Maven依赖的三坐标体系GAV(G-groupId&#xff0c;A-artifactId&#xff0c;V-version) 【案例1】某应用依赖两个GAV不同的jar&#xff0c;但包含两个相同全限定类…

java安装并配置环境

安装前请确保本机没有java的残留&#xff0c;否则将会安装报错 1.安装java jdk&#xff1a;安装路径Java Downloads | Oracle 中国 百度网盘链接&#xff1a;https://pan.baidu.com/s/11-3f2QEquIG3JYw4syklmQ 提取码&#xff1a;518e 2.双击 按照流程直接点击下一步&#x…

Json-server 的使用教程

目录 前言一、简介二、安装与配置1. 安装 node-js2. npm 镜像设置3. 安装 json-server 三、使用1. 创建本地数据源2. 启动 Json Server3. 操作数据&#xff08;1&#xff09;查询数据&#xff08;2&#xff09;新增数据&#xff08;3&#xff09;修改数据&#xff08;4&#xf…

理解Es的DSL语法(二):聚合

前一篇已经系统介绍过查询语法&#xff0c;详细可直接看上一篇文章&#xff08;理解DSL语法&#xff08;一&#xff09;&#xff09;&#xff0c;本篇主要介绍DSL中的另一部分&#xff1a;聚合 理解Es中的聚合 虽然Elasticsearch 是一个基于 Lucene 的搜索引擎&#xff0c;但…