我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android消息机制基本原理和使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android消息机制基本原理和使用

在Android开发过程中,我们常常遇到子线程更新UI的需求,例如在子线程进行耗时较长的下载,等下载完成之后,再去更新UI,提示用户下载完成,直接在子线程里更新UI,会得到报错提示:Only the original thread that created a view hierarchy can touch its views

Android老手知道这是怎么回事,并且知道解决方案,新手只能去网上找答案,网上的答案告诉我们报错是因为子线程不能直接更新UI线程,也就是主线程的控件,必须通过Android消息机制来更新。略微遗憾的是,网上的答案要么仅仅是罗列了可用方案的代码片段,不知道背后的原理是什么,要么是陷入源码分析的细节中,看完之后的感觉作者早就走远了,我们还在源代码细节中晕头转向。
为此,决定自己写一篇,如果能用通俗语言说明白,表明自己也会了。
在这里插入图片描述

2.为什么其他线程不能直接访问UI线程?

要回答这个问题,可以反过来想一想,如果子线程能够访问UI线程会怎样?Android的UI线程是非线程安全,非线程安全是***不对数据进行加锁保护,多线程访问数据时,导致出现数据不一致或者数据污染情况***,例如两个线程同时设置同一个UIView的背景颜色,那么很有可能渲染显示的是颜色A,而此时在UIView逻辑上的背景颜色属性为B,因此假如子线程能直接修改UI控件的话,会导致出现不可预期结果,那么UI线程为什么不设置成线程安全呢?要设置成线程安全需要加锁,即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。但这样做会降低执行效率,如果一个子线程长时间占据着,其他线程只能干等,而UI界面直接面对用户,是Android的门面,首先要保证响应快,越快越好,因此UI线程只能是非线程安全。

既然是非线程安全,又不想让子线程随便修改,只能禁止子线程直接访问UI线程的控件。

既然子线程不能直接访问UI,那怎么实现更新UI呢?这就用到了Android消息机制。

3.Android消息机制

什么是Android消息机制?说白了是跨线程传递信息机制,注意这里是跨线程,不是跨进程,Android里跨进程通信使用Binder,跨线程传递消息使用消息机制,为什么跨线程通信不使用Binder?

我们知道不同进程内存空间是隔离的,而一个进程里不同线程共享内存空间,用日常生活来理解就是,不同进程好比是不同的房子,一个进程是一间房子,而一个进程里包含不同线程,这些线程就像是不同的人,例如你,你父母,你老婆孩子,这些家庭成员同在一个房子里,也就是共享内存空间。

两个进程两间房子,相互之间通信使用电话沟通,而同在一个房间里的不同线程还使用电话沟通效率降低了,这也是为什么不同线程之间不用Binder的原因。

你可能会问,既然家庭成员同在一间房里时,直接面对面喊话不就行了吗,为什么还设计消息机制来沟通,不还是降低效率了吗?这又回到上节里说的,如果直接喊话就响应,让子线程直接访问UI线程,会导致混乱,这可以由日常生活的例子来理解,假设由你掌控遥控器,你老婆想看电影频道,你小孩想看动画频道,你父母想看戏剧频道,如果他们同时提需求,你到底是按哪个频道?为了解决这个混乱问题,你可以设计一个消息机制来应付。

你可以在桌面上放着一个传票叉,谁想看什么节目就把需求写在便笺,然后插到传票叉上,这样有个先后顺序,如果叉上有便笺,你取出来,看是写了什么,例如老婆便笺写着看10分钟电影,她先把便笺插到叉上,小朋友便笺写着看动画1分钟,也插到叉上,这样你先取出小朋友的便笺,然后给他看一分钟动画,一分钟后,再取出你老婆的便笺,换台到电影频道。这样虽然效率不高,但保证顺序不乱,当然传票叉是后来居上机制,这么设计容易挨打。

在这里插入图片描述

Android消息机制也是类似操作。

我们先来看Android消息机制里的各个角色名称。

Message

用于传递消息的载体,对应于上述例子的便笺;

MessageQueue

消息队列,对应上述例子的传票叉,家人看哪个频道的需求写到便笺上,然后插到传票叉上,形成消息队列。其实我们可以发现,正因为传票叉的存在,便笺才有先后顺序,你处理起来才不会乱,如果家人把便笺散放在桌子上,那和直接喊话没什么区别。同理,正是MessageQueue的存在,才解决了UI线程能够挨个处理每个子线程的而不错乱的问题。需要注意的是,从传票叉取出便笺的过程只能由你自己完成,其他人代劳还是会引发混乱,同理,消息队列也只能由接收消息的线程来处理;

Looper

有部电影英文名是《Looper》,只看英文名可能觉得这电影没什么知名度,说中文名称你应该就想起这部电影,这是由囧瑟夫主演的《环形使者》。那这部电影和Android消息机制里的Looper有啥关系?电影中杀手要干掉的目标是未来的自己,陷入死循环,消息机制里的Looper,是为了令程序进入无限循环,在这个循环里,不断检查MessageQueue是否有消息进来,如果有消息,则读取出来,并传递到Handler的handleMessage()方法中。注意,每个线程中只会有一个Looper对象。用遥控器的例子来理解Looper的话,Looper操作你进入不停检查传票叉是否有便笺插进来的状态;

Handler
在遥控器的例子中,你代表主线程,你的家人代表子线程,他们只需要在便笺上写上需求,再自己插到传票叉上,你取出来,这就模拟了消息的跨线程传递。但在程序中,需要使用Handler类来完成往传票叉上插便笺的过程,也就是子线程给主线程传递消息。在主线程中构建Handler类实例,子线程再调用这个Handler类的sendMessage()方法即可,为什么主线程构建的Handler类实例子线程能够使用?因为它们在同一个进程里,同一个进程内不同线程共享资源的,主线程创建的实例子线程当然可以访问到。说到这我们还可以这样理解Handler,Handler相当于主线程分发给不同线程的专用传话筒,子线程可以通过这个传话筒联络主线程。

我们可以使用一个示意图将消息机制里的几个角色的作用展示出来,如图所示。
其中,环形使者Looper启动线程进入无限循环,不停查看MessageQueue是否有消息进来,有消息进来使用Handler取出,线程1实例化出Handler,给线程2引用,线程2有消息发送的话,则使用Handler的sendMessage方法,发送消息,发送的消息进入MessageQueue里。

在这里插入图片描述

4.Android消息机制使用示例

看了消息机制的基本原理,现在来看一下如何使用。

首先实例化一个Handler实例,如代码所示,我们看到,里面还重写了handleMessage方法,顾名思义,handleMessage是处理消息的意思,在上面介绍Handler时,只说了Handler是子线程传递消息的渠道,其实忘记说了取出消息也是通过Handler。Handler取到消息后,根据消息的类型做相应的动作,这里只是简单更新一下TextView的文字。另外,这里我们看不到Looper,MessageQueue,这些是幕后英雄,在分析源码时会涉及到。

  Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    //完成主界面更新,拿到数据
                    String data = (String)msg.obj;
                    tshow.setText(data);
                    break;
                default:
                    break;
            }
        }
    };
子线程调用Handler实例发送消息
private void UpdateTextView() {         
        new Thread(new Runnable(){  
            @Override  
            public void run() {    
                Message msg =new Message();  
                msg.obj = "子线程更新数据";//可以是基本类型,可以是对象,可以是List、map等;  
                //发送消息
                mHandler.sendMessage(msg);  
            }  
        }).start();          
    }

然后,然后没有然后了,使用消息机制就这么简单,可以看出,因为使用简单,开发者使用消息机制完成子线程更新主线程其实没增加多少工作量。对于小白而言,虽然使用简单,但了解其背后的原理还是有必要的,不然不知道为什么这么使用,我开始接触消息机制时,只知道拷贝他人的代码,不知道原理,导致每次用到的时候,都是拷贝代码片段,然后替换成自己的变量,因为自己写不知道从哪里入手。

本文只是简单理解消息机制的各个角色,以及它们之间如何配合,下一篇博客,我们分析消息机制的源代码。

最后附上Activity的源代码。

package com.test.threaddemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
    private TextView tshow;
    private Button button;
    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    //完成主界面更新,拿到数据
                    String data = (String)msg.obj;
                    tshow.setText(data);
                    break;
                default:
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tshow=findViewById(R.id.tvshow);
        button=findViewById(R.id.bClick);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                UpdateTextView();
            }
        });
    }
    @Override
    protected void onResume() {
        super.onResume();
    }
    private void UpdateTextView() {
        new Thread(new Runnable(){
            @Override
            public void run() {
                Message msg =new Message();
                msg.obj = "子线程更新数据";//可以是基本类型,可以是对象,可以是List、map等;
                //发送消息
                mHandler.sendMessage(msg);
            }
        }).start();
    }
}

作者:风行南方


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android消息机制基本原理和使用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android消息机制基本原理和使用

在Android开发过程中,我们常常遇到子线程更新UI的需求,例如在子线程进行耗时较长的下载,等下载完成之后,再去更新UI,提示用户下载完成,直接在子线程里更新UI,会得到报错提示:Only the original thread that
2022-06-06

Android消息机制原理深入分析

这篇文章主要介绍了Android消息机制原理,Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程
2022-12-09

深入剖析Android消息机制原理

在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误。在学习Android中消息机制之前,我们先了解与消息有关的几个类:1.Mess
2022-06-06

基础知识Android消息处理机制1

必备知识 Android消息处理机制:Handler,MessageQueue,Looper,Thread 线程概念:一个应用程序运行时它的主体被称为进程, 一个进程内部可以有多个线程 线程共享进程的资源 分析源码: 1. 创建Messag
2022-06-06

Android消息机制Handler如何使用

这篇文章主要介绍“Android消息机制Handler如何使用”,在日常操作中,相信很多人在Android消息机制Handler如何使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Android消息机制Ha
2023-06-21

Android消息处理机制Looper和Handler详解

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。 Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMess
2022-06-06

OC消息发送和转发机制原理

本文将深入探讨OC的消息发送和转发机制原理,包括其基本概念、实现过程、代码示例以及实际应用场景。
OC消息发送2024-11-30

Android进阶之Handle和Looper消息机制原理和源码分析(不走弯路)

App中一般多会有多个线程,多线程之间难免需要进行通信。在我们平时开发中线程通信用的最多的就是Handler,例如子线程进行数据处理,在主线程中进行UI更新。

老生常谈 C# 开发 Windows 消息循环机制的原理和流程

C#开发中的Windows消息循环机制是实现与用户交互的基础。通过创建应用程序主窗口,启动消息循环,接收和处理消息,我们可以实现丰富的交互功能。熟悉消息循环的原理和流程,对于开发Windows应用程序是非常重要的。

Vite 的插件机制:插件应用和基本使用

因为 Vite 依赖于 Rollup ,这意味着 Vite 可以利用 Rollup 插件的强大生态系统。很多功能性的支持,在 Rollup 项目中需要添加插件,而在 Vite 为了能够提供开箱即用的体验,已经内建支持了。

Golang中使用RabbitMQ实现消息队列的原理和实践

在Golang中使用RabbitMQ实现消息队列的原理和实践主要涉及以下几个方面:1. RabbitMQ介绍:RabbitMQ是一个开源的消息队列中间件,它基于AMQP(Advanced Message Queuing Protocol)协
2023-10-08

冷饭新炒:理解JWT的实现原理和基本使用

本文会翻炒一个用以产生访问令牌的开源标准JWT,介绍JWT的规范、底层实现原理、基本使用和应用场景。
JWT网络原理2024-12-03

消息队列的六种经典使用场景和 Kafka 架构设计原理详细解析

Apache Kafka 是一个高吞吐量、分布式的流处理平台,广泛应用于实时数据管道和流处理应用中。Kafka 以其高性能、低延迟、扩展性和可靠性,成为了大数据生态系统中的重要组件。

队列技术在PHP与MySQL中的异步任务处理和消息回调机制的应用

随着互联网的快速发展,用户对于网站和应用的需求也越来越高。为了提高用户体验和应对高并发访问的需求,异步任务处理和消息回调机制成为了开发中不可或缺的一环。本文将介绍如何使用队列技术,在PHP与MySQL中实现异步任务处理和消息回调机制,并提供
2023-10-21

Android基于OpenGL在GLSurfaceView上绘制三角形及使用投影和相机视图方法示例

本文实例讲述了Android基于OpenGL在GLSurfaceView上绘制三角形及使用投影和相机视图方法。分享给大家供大家参考,具体如下: 定义三角形 OpenGL 允许我们使用三维坐标来定义物体。在绘制三角形前,我们需要定义它各个点的
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录