•前言

  我们在开发 APP 的过程中,经常需要更新 UI;

  但是 Android 的 UI 线程是不安全的;

  如果想更新 UI 线程,必须在进程的主线程中;

  这里我们引用了异步消息处理机制来解决之一问题。

•异步消息的组成

概念

  Android 的异步消息处理机制主要由 4 个部分组成:Message、Handler、MessageQueue 和 Looper。

Message

  • Message 是线程之间传递信息的机制
  • 它可以在内部携带少量的信息,用于在不同线程之间交换数据
  • Message 可以用 what、arg1、arg2、obj 字段携带信息
  • 其中 arg1、arg2 主要用于携带整型数据,obj 携带 Object 对象

Handler

  • Handler 是消息的处理者,它主要用于发送和处理消息
  • 发送消息使用 Handle 的  sendMessage() 方法
  • 而发出的消息经过一系列的辗转处理后,最终会传递到 Handler 的  handleMessage() 方法中

MessageQueue

  • MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息
  • 这部分消息会一直存在于 MessageQeue 中,直至被 Looper 发送至 handleMessage() 处理
  • 每个线程只会有一个 MessageQueue 对象

Looper

  • Looper 是每个 MessageQueue 的消息管家,调用 Looper 中的  loop() 方法后,就会进入到一个无限循环当中
  • 把 MessageQueue 中的消息取出,并传递到 Handle 的  handleMessage()  方法中进行处理
  • 每个线程当中只会有一个 Looper 对象

Thread,Looper 与 Handler 之间的对应关系

  • 1个线程(Thread)只能绑定 1个循环器(Looper),但可以有多个处理者(Handler)

  • 1个循环器(Looper) 可绑定多个处理者(Handler)

  • 1个处理者(Handler) 只能绑定1个1个循环器(Looper)

    

•异步消息的处理流程

文字描述

  首先需要在主线程中创建一个 Handler 对象,并重写  handleMessage()  方法,我们主要在  handleMessage() 中进行一系列的操作;

  当子线程中需要进行 UI 更新时,就在子线程中创建一个 Message 对象,并通过 Handler 将这条消息发送出去;

  经 Handler 发送的消息会被添加到 MessageQueue 中等待被处理;

  而 Looper 会一直尝试从 MessageQueue 中取出待处理的消息;

  最后 Looper 会将消息发送到 Handler 的  handleMessage()  方法中进行处理。

  由于 Handler 是在主线程中创建的,所以此时  handleMessage() 方法中的代码也会在主线程中运行,

  于是我们就可以安心的进行 UI 更新操作了。

图示

  一条 Message 经过这样一个流程的辗转调用后,从子线程进入到了主线程,从不能更新 UI 变成了可以更新 UI;

  接下来们通过代码进一步理解;

通过内部类的方式创建Handler对象

  新建一个项目,并选择 Empty Activity;

  这样,Android Studio 为我们自动生成了 MainActivity.java 和 activity_main.xml 文件;

  在 activity_main.xml 中添加如下代码;

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="default"
android:textSize="20sp"
android:textColor="@color/black"/> </RelativeLayout>

  在该布局中,我只放置了一个 TextView 控件,并初始化 text 的值为 default;

  接下来修改 MainActivity.java 中的代码;

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Handler handler;
private TextView tv; //自定义MyHandler类,继承自Handler类并重写 handleMessage() 方法
private class MyHandler extends Handler{ //通过重写 handlerMessage() 方法
//从而确定更新 UI 的操作
@Override
public void handleMessage(@NonNull Message msg) {
/*
根据不同线程发送过来的消息,执行不同的 UI 操作
根据 Message 对象的 what 属性,标识不同的消息
*/
switch(msg.what){
case 1:
tv.setText("我是线程A");
break;
case 2:
tv.setText("我是线程B");
break;
default:
}
}
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); tv = findViewById(R.id.tv); //在主线程中创建Handler实例
handler = new MyHandler(); //通过继承Thread类实现多线程
new Thread(){
@Override
public void run() {
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建所需的消息对象
Message msg = Message.obtain();
msg.what = 1;//消息标识
msg.obj = "A";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中
handler.sendMessage(msg);
}
}.start();
new Thread(){
@Override
public void run() {
try {
sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建所需的消息对象
Message msg = Message.obtain();
msg.what = 2;//消息标识
msg.obj = "B";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中
handler.sendMessage(msg);
}
}.start();
}
}

代码分析

  通过继承 Handler 创建了一个 MyHandler 类,并重写了  handlerMessage() 方法;

  在该方法中通过判断 msg.what 的不同对 TextView 实施不同的操作;

  在  onCreate() 方法中,创建了 Handler 实例,并通过  new Thread()  来创建子线程;

  在子线程中通过创建 Message 对象 msg,并通过  handler.sendMessage(msg)  来向主线程发送子线程的意图;

  最后,在 MyHandler 类中通过  switch()  处理不同的 msg.what;

运行效果

  

通过匿名内部类的方式创建Handler对象

  在上述 MainActivity.java 代码中,我们是通过内部类的方式创建了 MyHandler 对象;

  接下来,我们通过匿名内部类的方式创建 Handler 对象;

  修改 MainActivity.java 中的代码;

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Handler handler;
private TextView tv; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); tv = findViewById(R.id.tv); //在主线程中创建Handler实例
handler = new Handler(){
//通过重写 handlerMessage() 方法
//从而确定更新 UI 的操作
@Override
public void handleMessage(@NonNull Message msg) {
/*
根据不同线程发送过来的消息,执行不同的 UI 操作
根据 Message 对象的 what 属性,标识不同的消息
*/
switch(msg.what){
case 1:
tv.setText("我是线程A");
break;
case 2:
tv.setText("我是线程B");
break;
default:
}
}
}; //通过继承Thread类实现多线程
new Thread(){
@Override
public void run() {
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建所需的消息对象
Message msg = Message.obtain();
msg.what = 1;//消息标识
msg.obj = "A";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中
handler.sendMessage(msg);
}
}.start();
new Thread(){
@Override
public void run() {
try {
sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建所需的消息对象
Message msg = Message.obtain();
msg.what = 2;//消息标识
msg.obj = "B";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中
handler.sendMessage(msg);
}
}.start();
}
}

  实现的效果和上面内部类的是一样的;

  不过,通过匿名内部类的方式创建 Handler 对象的方法被 Android Studio 嫌弃了;

•实战演练

预期效果图

开淦

  新建一个项目,命名为 Handle,选择 Empty Activity;

  在 activity_main.xml 中添加如下代码;

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <ImageView
android:id="@+id/img"
android:layout_width="164dp"
android:layout_height="156dp"
android:layout_centerInParent="true"
android:src="@drawable/girl_0"/> </RelativeLayout>

  在该布局中,我添加了一个 ImageView 控件,并使其居中显示;

  修改 MainActivity.java 中的代码;

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Handler handler;
private int[] imgId = new int[]{
R.drawable.girl_0,
R.drawable.girl_1,
R.drawable.girl_2,
R.drawable.girl_3,
R.drawable.girl_4,
R.drawable.girl_5,
R.drawable.girl_6,
R.drawable.girl_7,
};
private int imgStart = 0;
private ImageView img; private class MyHandler extends Handler{ @Override
public void handleMessage(@NonNull Message msg) {
if(msg.what == 0)
img.setImageResource(imgId[imgStart++%8]);
}
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); img = (ImageView) findViewById(R.id.img);
handler = new MyHandler(); new Thread(){ @Override
public void run() { while(true){
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
}.start();
}
}

分析

  在该代码中,通过定义整型数组 imgId 来存储帧动画素材,并通过内部类来创建 Handler 对象;

  在新建的线程  new Thread()  中重写了  run() 方法;

  并在该方法中通过  while(true)  中的  handler.sendEmptyMessage(0)  方法每隔 500毫秒 让 handler 发送一个空信息;

  以此来实现帧动画的特效;

  这样,就效果图中的效果就实现了;

  需要注意的是,一定要添加  sleep()  方法,并且至少要让子线程休眠 1ms;

  当然,也可以通过使用定时器实现每隔 500毫秒 发送一个空消息的效果;

  修改 MainActivity.java 中的代码;

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private Handler handler;
private int[] imgId = new int[]{
R.drawable.girl_0,
R.drawable.girl_1,
R.drawable.girl_2,
R.drawable.girl_3,
R.drawable.girl_4,
R.drawable.girl_5,
R.drawable.girl_6,
R.drawable.girl_7,
};
private int imgStart = 0;
private ImageView img; private class MyHandler extends Handler{ @Override
public void handleMessage(@NonNull Message msg) {
if(msg.what == 0)
img.setImageResource(imgId[imgStart++%8]);
}
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); img = (ImageView) findViewById(R.id.img);
handler = new MyHandler(); //使用定时器,每隔500毫秒让handler发送一个空信息
new Timer().schedule(new TimerTask(){ @Override
public void run() {
handler.sendEmptyMessage(0);
}
},0,500);
}
}

  该代码的运行效果与上一个的运行效果一样;

•声明

参考资料

  【异步消息处理机制

  【Handler消息传递机制浅析

  【Android 异步通信:图文详解Handler机制工作原理

  【Android 异步通信:手把手教你使用Handler消息传递机制(含实例Demo)

  在此感谢大佬的帮助!

Android学习之异步消息处理机制的更多相关文章

  1. Android开发之异步消息处理机制AsyncTask

    转自:Android AsyncTask完全解析,带你从源码的角度彻底理解 另外一篇比较详细的博文:http://blog.csdn.net/liuhe688/article/details/6532 ...

  2. Android线程与异步消息处理机制

    在程序开发时,对于一些比较耗时的操作,我们通常会为其开辟一个单独的线程来执行,这样可以尽可能的减少用户等待的时间.在Android中,默认情况下,所有的操作都是在主线程中进行的,这个主线程负责管理与U ...

  3. Android线程之异步消息处理机制(三)——AsyncTask

    Android的异步消息处理机制能够很完美的解决了在子线程中进行UI操作的问题,但是为了更加方便我们在子线程中对UI进行操作,Android还提供了另一个很好用的工具,AsyncTask就是其中之一. ...

  4. Android线程之异步消息处理机制(二)——Message、Handler、MessageQueue和Looper

    异步消息处理机制解析 Android中的异步消息处理主要有四个部分组成,Message.Handler.MessageQueue和Looper. 1.Message Message是在线程之间传递的消 ...

  5. Android线程之异步消息处理机制(一)

    Android不允许在子线程中进行UI操作,但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件.对于这种情况,Android提供了一套异步消息处理机制,完美 ...

  6. Android开发之异步消息处理机制Handler

    更加详细的介绍Handler的博文-http://blog.csdn.net/guolin_blog/article/details/9991569 Android中的异步消息处理主要有四个部分组成, ...

  7. Android之Handler(异步消息处理)机制

    1. 概述 Handler . Looper .Message 这三者都与Android异步消息处理线程相关的概念.那么什么叫异步消息处理线程呢?异步消息处理线程启动后会进入一个无限的循环体之中,每循 ...

  8. Android Learning:多线程与异步消息处理机制

    在最近学习Android项目源码的过程中,遇到了很多多线程以及异步消息处理的机制.由于之前对这块的知识只是浅尝辄止,并没有系统的理解.但是工程中反复出现让我意识到这个知识的重要性.所以我整理出这篇博客 ...

  9. Android中Handler的消息处理机制以及源码分析

    在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui.我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui.而一些耗时操作,比如数据库,网络请 ...

随机推荐

  1. 200万枚SPC空投来袭,这样的薅羊毛活动你确定不参加吗?

    在过去的2020年,币圈真的是很火爆,很多人在参与数字货币交易或DeFi挖矿中赚到了大钱.但是转眼到了2021年,DeFi进入了下半场,区块链市场也进入了新的阶段,那么区块链的下一个爆点是什么呢?很多 ...

  2. PAA房产智慧社区:解决社区管理服务的痛点难点

    社区,是社交与生活的舞台,更是家的延伸.社区之所有能够有所创新发展,得益于借助数字化和智能化.智能化给社区带来的便利体现在社区门禁可以人脸识别:AI的摄像头可以自动捕获异常的现象,便于社区管理员第一时 ...

  3. CMD 中运行 xx 命令提示 不是内部或外部命令,也不是可运行的程序或批处理文件的问题

    出现这个问题的原因一般有2个 这个命令依赖某个软件,而你又没有安装 这里你只需要去下载安装好对应的软件,基本上就可以解决上面的问题了. 软件安装好了,但是需要配置环境变量 第二个原因就按照下图,去设置 ...

  4. 26_ mysql数据操作语言:DELETE语句

    -- DELETE语句 -- 删除10部门中,工龄超过20年的员工记录 DELETE FROM t_emp WHERE deptno=10 AND DATEDIFF(NOW(),hiredate)/3 ...

  5. Python爬虫系统化学习(2)

    Python爬虫系统学习(2) 动态网页爬取 当网页使用Javascript时候,很多内容不会出现在HTML源代码中,所以爬取静态页面的技术可能无法使用.因此我们需要用动态网页抓取的两种技术:通过浏览 ...

  6. DRF 三大认证的配置及使用方法

    目录 三大认证 一.身份认证 1.身份认证配置 1.1 全局配置身份认证模块 1.2 局部配置身份认证模块 2.drf提供的身份认证类(了解) 3.rf-jwt提供的身份认证类(常用) 4.自定义身份 ...

  7. CentOS 7.7上配置mysql

    转载:https://www.cnblogs.com/VinsonYang/p/12333570.html 首先登陆到阿里云,进行远程连接,在这里我使用的是Xshell 6进行连接的. 参照https ...

  8. herry菌插件(B站C站)下载与安装(更新中)>>

    插件简介: 目前该插件支持chrome浏览器(谷歌浏览器).360极速浏览器等chrome内核的浏览器 最新版插件下载: 点此下载>>>> 安装方法: 1.先下载上面的的插件 ...

  9. React开发入门:以开发Todo List为例

    目录 概述 React基本概念 JSX是什么? 设置React APP 初始化APP 应用结构 探索第一个React组件 index.js 变量和props JSX中的变量 组件props props ...

  10. Hadoop的常用命令

    注:hadoop的使用命令包含 hadoop fs 开头 or hdfs dfs开头 等多种形式来操作. 这里以hadoo fs的形式来介绍在这些命令 1.列出根目录下所有的目录或文件 hadoop ...