•前言

  我们在开发 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. React Refs All In One

    React Refs All In One https://reactjs.org/docs/react-api.html#refs Ref https://reactjs.org/docs/refs ...

  2. Angular Learning Paths

    Angular Learning Paths Angular Expert refs https://app.pluralsight.com/search/?q=angular xgqfrms 201 ...

  3. vue2.0用法以及环境配置

    一.配置环境搭建 1.安装node.js (可以去官网看) 2.安装git (推荐看廖雪峰文章,点击查看) 3.安装vue: cmd:npm install vue //最新稳定版本 npm inst ...

  4. django学习-8.django模板继承(block和extends)

    1.前言 django模板继承的作用:模板可以用继承的方式来实现复用,减少冗余内容. 一般来说,一个网站里一般存在多个网页的头部和尾部内容都是一致的,我们就可以通过模板继承来实现复用. 父模板用于放置 ...

  5. springboot学习过程随记

    1.整合shiro+jwt(若忘记需结合测试代码springboot-mybatisplus-shiro-demo看) 配置比较简单 定义一个类继承AuthorizingRealm 如下: (1)pu ...

  6. SpringBoot(七):SpringBoot中如何使用过滤器(Filter)?

    方式一: 通过注解方式实现: 1.编写一个Servlet3的注解过滤器(和上一章Servlet相似) 贴代码: package com.example.springbootweb.filter; im ...

  7. ISC BIND9 - 最详细、最认真的从零开始的BIND 9 服务讲解

    DNS and BIND 服务的搭建说明 目录 目录 DNS and BIND 服务的搭建说明 1. 背景 1.1 DNS 1.2 FQDN 1.3 BIND 1.4 本文中搭建模拟DNS服务网络虚拟 ...

  8. brew安装Nginx

    目录 安装流程 常用命令记录 典型配置方式 查看启动状态是否有报错 php 启动 参考 安装流程 这里使用 brew 来安装软件. 安装 brew install nginx 查看安装信息(经常用到, ...

  9. PUToast - 使用PopupWindow在Presentation上模拟Toast

    PUToast Android10 (API 29) 之前 Toast 组件默认只能展示在主 Display 上,PUToast 通过构造一个 PopupWindoww 在 Presentation ...

  10. 剑指 Offer 49. 丑数 + 小根堆 + 动态规划

    剑指 Offer 49. 丑数 Offer_49 题目详情 解法一:小根堆+哈希表/HashSet 根据丑数的定义,如果a是丑数,那么a2, a3以及a*5都是丑数 可以使用小根堆存储按照从小到大排序 ...