在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消息机制使用示例

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

  1. 首先实例化一个Handler实例,如代码所示,我们看到,里面还重写了handleMessage方法,顾名思义,handleMessage是处理消息的意思,在上面介绍Handler时,只说了Handler是子线程传递消息的渠道,其实忘记说了取出消息也是通过Handler。Handler取到消息后,根据消息的类型做相应的动作,这里只是简单更新一下TextView的文字。另外,这里我们看不到Looper,MessageQueue,这些是幕后英雄,在分析源码时会涉及到。
Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case 0://完成主界面更新,拿到数据String data = (String)msg.obj;tshow.setText(data);break;default:break;}}};
  1. 子线程调用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() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case 0://完成主界面更新,拿到数据String data = (String)msg.obj;tshow.setText(data);break;default:break;}}};@Overrideprotected 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() {@Overridepublic void onClick(View v) {UpdateTextView();}});}@Overrideprotected void onResume() {super.onResume();}private void UpdateTextView() {new Thread(new Runnable(){@Overridepublic void run() {Message msg =new Message();msg.obj = "子线程更新数据";//可以是基本类型,可以是对象,可以是List、map等;//发送消息mHandler.sendMessage(msg);}}).start();}
}

Android消息机制基本原理和使用相关推荐

  1. Android消息机制Handler用法

    这篇文章介绍了Android消息机制Handler用法总结,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 1.简述 Handler消息机制主要包括: Messa ...

  2. 【腾讯Bugly干货分享】经典随机Crash之二:Android消息机制

    为什么80%的码农都做不了架构师?>>>    本文作者:鲁可--腾讯SNG专项测试组 测试工程师 背景 承上经典随机Crash之一:线程安全 问题的模型 好几次灰度top1.top ...

  3. android消息池,回转寿司你一定吃过!——Android消息机制(构造)

    消息机制的故事寿司陈放在寿司碟上,寿司碟按先后顺序被排成队列送上传送带.传送带被启动后,寿司挨个呈现到你面前,你有三种享用寿司的方法. 将Android概念带入后,就变成了Android消息机制的故事 ...

  4. android handler的机制和原理_一文搞懂handler:彻底明白Android消息机制的原理及源码

    提起Android消息机制,想必都不陌生.其中包含三个部分:Handler,MessageQueue以及Looper,三者共同协作,完成消息机制的运行.本篇文章将由浅入深解析Android消息机制的运 ...

  5. Android 消息机制详解(Android P)

    前言 Android 消息机制,一直都是 Android 应用框架层非常重要的一部分,想更加优雅的进行 Android 开发,我想了解消息机制是非常必要的一个过程,此前也分析过很多次 Handler ...

  6. 理解 Android 消息机制

    本人只是Android小菜一个,写技术文章只是为了总结自己最近学习到的知识,从来不敢为人师,如果里面有不正确的地方请大家尽情指出,谢谢! 本文基于原生 Android 9.0 源码来解析 Androi ...

  7. Android 系统(177)---Android消息机制分析:Handler、Looper、MessageQueue源码分析

    Android消息机制分析:Handler.Looper.MessageQueue源码分析 1.前言 关于Handler消息机制的博客实际上是非常多的了. 之前也是看别人的博客过来的,但是过了一段时间 ...

  8. Android 消息机制(Handler运行机制)

     1 Android 消息机制 Android 的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑 2 为什么要用Handler消息 ...

  9. Android消息机制(Handler机制) - 线程的等待和唤醒

    我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息. 通常在消息队列中(MessageQueue)中没有消 ...

最新文章

  1. 2018-4-8使用兔子的例子对比说明遗传算法,局部搜索,禁忌搜索,模拟退火方法
  2. aliyun服务器安装git,g++
  3. ubuntu 12.10 安装mysql_Ubuntu12.10安装Mysql数据库
  4. Linux下有趣的命令
  5. mysql 分割后循环,mysql实现字符串分割SPLIT函数的四种方法
  6. php mysql网站设计_基于PHP和MySQL的网站设计与实现
  7. SQLServer2012 查询分析器的快捷键
  8. 测度论与概率论笔记6:符号测度
  9. Unity TileMap 存档 保存地图
  10. c++builder 6.0中OnCliked= fun实现的原理
  11. 系统扫描修复cmd命令
  12. 基于Python+MySQL的书店销售管理管理子系统设计
  13. 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
  14. Java语言写汽车租赁系统
  15. 去哪儿2017校园招聘笔试题——获得文件扩展名filename extension
  16. C++:评估二伽玛或 psi 功能(附完整源码)
  17. 2021年6月PMP考试紧急缓考怎么办理?
  18. 王老吉做奶茶 是要火还是要凉?
  19. 75个顶级开源安全应用
  20. ECCV2018 | PKT_概率知识蒸馏

热门文章

  1. boost::mpi模块实现传输数据类型的骨架和内容的通信器的测试
  2. boost::prior用法的测试程序
  3. boost::graph_as_tree用法的测试程序
  4. boost::graph模块实现双连通分量算法的测试程序
  5. boost::geometry::num_geometries用法的测试程序
  6. boost::fusion::fold用法的测试程序
  7. Boost:基于Boost的阻塞udp echo的测试程序
  8. DCMTK:DcmFloatingPointDouble类的测试程序
  9. VTK:Utilities之ArrayCalculator
  10. VTK:绘图之LinePlot