Android源码解析Handler系列第(一)篇 --- Message全局池

2024-09-02 15:08

本文主要是介绍Android源码解析Handler系列第(一)篇 --- Message全局池,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、UI不能在子线程中更新是个伪命题

我们常说UI需要在主线程中进行更新,子线程就不能更新UI吗?不是,我们并不是说不能在子线程中更新UI,而是说UI必须要在它的创建线程中进行更新,比如下面一段代码在子线程更新UI就不会报错。

    new Thread(new Runnable() {@Overridepublic void run() {TextView  textView=new TextView(MainActivity.this);textView.setText("Hello");}}).start();

OK,那我偏偏不在创建线程中进行更新,如下!view在主线程中创建,在子线程中add一个Button。

     @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);final LinearLayout view = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.activity_main, null, false);setContentView(view);new Thread(new Runnable() {@Overridepublic void run() {Button btn=new Button(MainActivity.this);btn.setText("Hello");view.addView(btn);}}).start();}

运行后,Button被顺利的加到了根布局中。尼玛,“UI必须要在它的创建线程中进行更新”这种说法也不对啊!!!不是不对,而是欠妥,我修改一下代码!

     new Thread(new Runnable() {@Overridepublic void run() {Button btn=new Button(MainActivity.this);btn.setText("Hello");SystemClock.sleep(2000);view.addView(btn);}}).start();

就是在addView之前,睡眠了一会,结果还是抛出了 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.这个错误!通常这种错误,就要用Handler向UI线程发送消息来更新!这个后面会对源码进行分析。说了半天,进入正题,我今天要讨论的是Message。要掌握Handler,先对Handler发送的Message有所了解。

2、Message的初步认识

Message就是一个实现了Parcelable的java类,我的第一感觉是它可以可以用在进程间通信(IPC),可以用在网络中传输,看一下它比较重要的几个字段。先来几个熟悉的。

    //当有多个Handler发消息的时候,或者一个Handler发多个消息的时候,可以用what标识一个唯一的消息public int what;//当传递整形数据的时候,不需要使用setData(Bundle)去设置数据,使用arg1,arg2开销更小。public int arg1; public int arg2;//Message所携带的数据对象public Object obj;

这几个都是很常用的,要深入了解Message,不得不了解下面几个。

    //flags表示这个Message有没有在使用,1表示在池中,等待复用,0表示正在被使用,int flags;//Message发送之后,何时才能被处理long when;// Message所携带的数据 Bundle data;//表示这个消息被哪个Handler处理Handler target;//我们用Handler.post一个Runnable,这个Runnable也是被包装成Message对象的Runnable callback;// 作为消息链表所用的一个成员Message next;//sPoolSync是对象锁,因为Message.obtain方法会在任意线程调用private static final Object sPoolSync = new Object();//sPool代表接下来要被重用的Message对象private static Message sPool;//sPoolSize表示有多少个可以被重用的对象private static int sPoolSize = 0;//MAX_POOL_SIZE是pool的上限,这里hardcode是50private static final int MAX_POOL_SIZE = 50;

对于上面列举的,挑几个重点分析一下。

Runable是怎么被封装到Message中的?我们常为会延迟做某个操作写出下面的代码

handler.postDelayed(new Runnable() {@Overridepublic void run() {}},1000);
跟踪进去发现在postDelayed中调用了getPostMessage
   private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}

所以这个run方法只是一个普通的回调而已,千万不要认为他是在另外一个线程中。刚说了Message中的target表示了这个消息要被哪一个Handler所处理,对于Message是怎么被处理的呢?尽管后面要分析Handler源码,这里还是提一下。

Message的处理顺序
Looper.loop()方法中Message被从MessageQueue取出来后会调用msg.target.dispatchMessage(msg)

   /*** Handle system messages here.*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

在dispatchMessage方法中,检查是否有由Runnable封装的消息,如果有,首先处理;其次处理的是mCallback,mCallback是什么?mCallback是Callback的对象,Callback是Handler中的一个内部接口,这个接口的作用是,当你实例化一个Handler对象的时候,可以避免不去实现Handler的handleMessage方法。

    /*** Callback interface you can use when instantiating a Handler to avoid* having to implement your own subclass of Handler.** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/public interface Callback {public boolean handleMessage(Message msg);}

从消息的分发可以看到,如果返回了true,那么handler中的handleMessge方法是不会被执行的,如果返回false或者mCallback没有被赋值,那么就会回调Handlerd的handleMessge,这就是一个Message的分发流程。了解了Message的分发之后,那么Message是怎么被创建的呢。

3、Message全局池

ANDROID系统中很多操作都是靠发消息完成的,比如按下返回键就是一个消息,这样系统会不会构建大量的Message对象呢?上面看到getPostMessage方法中,以 Message.obtain()的方式获取一个消息,而不是通过它的构造函数,获取消息的代码很简单。

 /*** Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}

有一个关键词是sPool,初步感觉是池的意思,别忘记了,在第一节Message的初步认识中,sPool是一个Message对象 ,这里有点蒙逼,一个对象起个名字跟池有什么关系。回顾一下,Message还有一个成员是 next。

// sometimes we store linked lists of these thingsMessage next;

这样每一个Message对象都有一个next指针指向下一个可用的Message,这不就是大学数据结构中的链表嘛!所以一个消息池的结构是这样的。

Message链表

既然知道Message有一个消息池(我们通常称这个消息池为全局池)的机制,那么它设计初衷肯定是要做到复用的,那么还有一个问题,Message何时被入池,何时出池?(MessageQueue虽然是存储消息的,但要弄清楚,这里说的消息池跟MessageQueue并没有什么关系。)

消息要入池,也就是这个消息被回收到池中,等待复用,所以我们大胆猜测这个消息肯定不在使用之中,如果这个消息正在使用之中,是肯定不会把它放到全局池里面的,也就是说只有这个消息完成了它的使命,系统才能把它回收到全局池中。通过这样的分析,消息入池不是在它的创建阶段,而是在回收阶段。直接看代码吧。Message中有一个recycle的方法。

 /*** Return a Message instance to the global pool.* <p>* You MUST NOT touch the Message after calling this function because it has* effectively been freed.  It is an error to recycle a message that is currently* enqueued or that is in the process of being delivered to a Handler.* </p>*/public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}
            return;}recycleUnchecked();}

首先判断消息是否在使用之中

    boolean isInUse() {return ((flags & FLAG_IN_USE) == FLAG_IN_USE);}

如果在使用之中,继续判断gCheckRecycle,gCheckRecycle的默认值是true,这个一个与版本相关的常量,在5.0之后的版本这个值是false,不知道作什么使用,有谁知道可以告诉我一下。在此不纠结了。如果不在使用之中,最后会走进recycleUnchecked。

/*** Recycles a Message that may be in-use.* Used internally by the MessageQueue and Looper when disposing of queued Messages.*/void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}

这段代码也不是很长,很好理解,一开始,把这个消息所有成员赋值成最初的状态,FLAG_IN_USE的值是1一开始说了Message的flags表示这个Message有没有在使用,1表示在池中,等待复用,0表示正在被使用。重点看同步锁中的代码。
假设全局池没有元素时,我们将第一个消息放到池中,sPool一开始是NULL,next指向了sPool,所以此时的消息的sPool和next都是NULL,然后sPool指向当前的Message对象,最后池的数量加1。大致如下图。
全局池中只有一个消息等待被复用

假设有来个消息m2,在走一遍同步锁中的代码,此时全局池的状态如下图所示。

全局池中两个消息等待被复用

同理,三个消息的时候,是这样
全局池中三个消息等待被复用
看到这,相信你已经知道了消息是怎么加到全局池中的,那么何时出池呢?再看消息的获取代码,尝试找出消息何时出池的。

 /*** Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}

obtain经常使用在多线程之中,所以用sPoolSync作为同步锁,第一次sPool为空,会new一个消息返回,这new的消息会在回收的时候,会被加到全局池中。如果sPool不为空,sPool是什么?sPool是指向全局池的头指针,sPool不为空,说明了全局池中有元素。把sPool赋值给一个Message对象m,同时全局池的头指针向后移,指向下一个被复用的消息,然后把m的flags赋值为0,表示这个消息被复用了,池中元素数量减1。经过这个逻辑,上图中的消息m3是不是就被出池了呢?

消息m3离开全局池

OK,本文分析到这里,一个Message大部分内容都被分析了,我们知道了Message内部有一个全局池,保证了开发者可以不构建大量的消息,提高性能。我们也知道了一个消息何时入池,何时出池。OK,Message复用机制到此结束。

Please accept mybest wishes for your happiness andsuccess

这篇关于Android源码解析Handler系列第(一)篇 --- Message全局池的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130303

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get