Message的消息池(sPool)

article/2025/11/4 8:13:03

关键总结

1、消息池缓存有可重复使用的消息实例,避免过多的创建与回收消息实例
2、消息池是一个栈(LIFO/FILO=后进先出/先进后出)的数据结构,具体的数据存放是采用了链表方式
3、消息池一开始是空的,需要主动添加消息进入缓存池,然后供后续的取出与使用,消息池的长度是有限制的,这样可以避免缓存过多的对象,导致内存占用过多,尽可能的避免内存泄漏
3、消息入队列时会被设置为已经在使用的状态,消息从消息列表取出被消费后会放入消息池,大多数情况下我们是不需要调Message的回收方法,把消息存入消息池的
5、消息发出去后立刻调用消息的回收方法,可有能会触发"java.lang.IllegalStateException: This message cannot be recycled because it is still in use" 的异常

消息池的数据结构

结构示意图如下
在这里插入图片描述

消息添加到缓存池

Message提供对象方法recycle() 把对象自己放到缓存池中,对象放到消息池后就保存有对象的引用,所以JVM不会回收放到缓存池的对象
放到缓存池的主要操作步骤是

  1. 清空消息的内容(成员对象变量置空,基础类型变量置为零,状态恢复默认值等)
  2. 对缓存池加锁(线程安全操作)
  3. 缓存的消息对象入栈(next引用赋值为当前栈顶,当前栈顶赋值为当前缓存的消息对象),缓存元素大小加1
//回收利用,即入缓存池public void recycle() {//这里的状态我们后面再讲if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycleUnchecked();}//这个包访问限制,我们调不到该方法的,如方法的注释所说当消息从消息列表取出来消息后,looper会把消息回收(调用该方法,让消息入缓存池)/*** 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 = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {//指向当前的栈顶元素next = sPool;//栈顶元素替换为当前入缓存的消息对象sPool = this;//size + 1sPoolSize++;}}}
/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);boolean slowDeliveryDetected = false;for (;;) {// 取出消息Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}try {// 消费消息msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (slowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");slowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.slowDeliveryDetected = true;}}}....删除了很多代码// 回收消息msg.recycleUnchecked();}}

即意味着我们在使用消息机制时,消息机制的框架里会帮我们缓存消息,我们只要在需要的地方调Message.obtain获取缓存的消息来使用就可以了!
如果要手动的回收消息,即调Message.recycle方法时,要考虑上下文操作中消息实例会不会存在跨线程的处理,即回收消息时,消息是不是还没有被消费的情况。
Android消息机制是这样做的,消息入队列时给消息设置为已经在使用的状态,于确保消息在被出队列前是不能调回收的。
如下是个错误的示例,消息被发送后,立刻调用消息的recycle方法,会触发到”This message cannot be recycled because it is still in use”的异常

2021-12-23 15:25:32.277 10716-10716/com.sdk.sdktestdemo E/AndroidRuntime: FATAL EXCEPTION: mainProcess: com.sdk.sdktestdemo, PID: 10716java.lang.IllegalStateException: This message cannot be recycled because it is still in use.at android.os.Message.recycle(Message.java:311)at com.sdk.sdktestdemo.MainActivity.sendMessageTest(MainActivity.java:66)at com.sdk.sdktestdemo.MainActivity$1.onClick(MainActivity.java:27)at android.view.View.performClick(View.java:7441)at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)at android.view.View.performClickInternal(View.java:7418)at android.view.View.access$3700(View.java:835)at android.view.View$PerformClick.run(View.java:28676)at android.os.Handler.handleCallback(Handler.java:938)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loopOnce(Looper.java:201)at android.os.Looper.loop(Looper.java:288)at android.app.ActivityThread.main(ActivityThread.java:7842)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

示例代码

package com.sdk.sdktestdemo;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import android.view.View;public class MainActivity extends AppCompatActivity {// Used to load the 'native-lib' library on application startup.
//    static {
//        System.loadLibrary("native-lib");
//    }@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);this.findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {sendMessageTest(HANDLER_THREAD_A.getHandler(), "发给A的第一个消息");}});initHandlerThread();}/*** A native method that is implemented by the 'native-lib' native library,* which is packaged with this application.*/public native String stringFromJNI();public static HandlerThreadEx HANDLER_THREAD_A;public static void initHandlerThread() {HANDLER_THREAD_A = new HandlerThreadEx("A");HANDLER_THREAD_A.setMessageHandler(new HandlerThreadEx.MessageHandler() {@Overridepublic void handler(Handler handler, Message msg) {// 这里是handler处理其绑定的消息Log.d("HandlerTest", String.format("%s", "这里是HANDLER_THREAD_A的handler在处理消息"));handleMessageTest(msg);sendMessageTest(handler, "测试");}});HANDLER_THREAD_A.start();}public static int S_MSG_ID = 0;public static void sendMessageTest(Handler handler, String msgInfo) {int msgId =  S_MSG_ID++;Message message = Message.obtain();message.what = msgId;message.obj = msgInfo;handler.sendMessageDelayed(message, 5 * 1000L);//消息一发出去,就调消息的回收方法,会触发"java.lang.IllegalStateException: This message cannot be recycled because it is still in use" 的异常message.recycle();Log.d("HandlerTest", String.format("thread:%s >>> 发消息, msgId:%d, info:%s", Thread.currentThread().getName(), msgId, msgInfo));}public static void handleMessageTest(Message msg) {Log.d("HandlerTest", String.format("thread:%s <<< 处理消息, hashcode: %d, msgId:%d, info:%s", Thread.currentThread().getName(), msg.hashCode(), msg.what, msg.obj));}
}class HandlerThreadEx extends HandlerThread {private Handler mHandler;private MessageHandler mMessageHandler;public HandlerThreadEx(String name) {super(name);}public HandlerThreadEx(String name, int priority) {super(name, priority);}@Overrideprotected void onLooperPrepared() {super.onLooperPrepared();Log.d("HandlerTest", String.format("onLooperPrepared, thread:%s", Thread.currentThread().getName()));mHandler = new Handler(getLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {if (mMessageHandler != null) {mMessageHandler.handler(this, msg);}}};}public Handler getHandler() {return mHandler;}public void setMessageHandler(MessageHandler mMessageHandler) {this.mMessageHandler = mMessageHandler;}public interface MessageHandler {void handler(Handler handler, Message msg);}
}

消息从缓存池取出

Message提供类方法(public static Message obtain),取出缓存池当中的栈顶元素供使用
主要操作步骤是

  1. 对缓存池锁对象加锁(保证线程安全)
  2. 获取的对象赋值为当前栈顶元素
  3. 当前栈元素赋值为当前栈顶元素的next,即新栈顶“指针”往下移一个单位
  4. 旧栈顶元素/获取的对象/出栈的元素的next置为空,即链表关系断开,出栈操作完成
  5. 消空出栈元素的使用状态(清空Flag值)
  6. 缓存池size减一
  7. 返回之前的栈顶元素
    /*** 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) {//2.Message m = sPool;//3. 当前栈元素赋值为当前栈顶元素的next,即栈顶“指针”往下移一个单位sPool = m.next;//4. 旧栈顶元素/获取的对象/出栈的元素的next置为空,即链表关系断开,出栈操作完成m.next = null;//5.m.flags = 0; // clear in-use flag//6.sPoolSize--;//7.return m;}}return new Message();}

消息的缓存状态

Message类中有一个int类型的旗标变量成员(flags)顾名思义,利用整型变量的二进制存储空间的某些位是否被置为1为表示某个状态已经为true,0表示某个状态为false;
目前代码中,flags第0位标示是否在使用状态,第1位标示是否为异步消息(异步消息跟同步消息后续找机会再整理与分享)

/** If set message is in use.* This flag is set when the message is enqueued and remains set while it* is delivered and afterwards when it is recycled.  The flag is only cleared* when a new message is created or obtained since that is the only time that* applications are allowed to modify the contents of the message.** It is an error to attempt to enqueue or recycle a message that is already in use.*/
/*package*/ static final int FLAG_IN_USE = 1 << 0;/** If set message is asynchronous */
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;/** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;@UnsupportedAppUsage
/*package*/ int flags;

flags设置FLAG_IN_USE的地方有两个,一个是recycleUnchecked方法,还有markInUse方法,设置FLAG_IN_USE主要是明确消息已经在使用或已经入了缓存池,不能再次调Message的recycle,让消息入缓存池。如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();}

“the process of being delivered to a Handler” - 这里的意思是当我们通过handler发送一个消息,消息入消息队列的时候,消息队列会调消息的markUse方法,将消息标示为在使用当中,如下为调markUse方法时调用栈

在这里插入图片描述

参考文档

  • Android Message解析
  • 揭秘 Android 消息机制之同步屏障:target==null ?
  • Android Studio: 查看android源代码
  • 一个步骤教你调试Android系统源代码

http://chatgpt.dhexx.cn/article/06IcCClg.shtml

相关文章

信息炸弹——Message Boom

前言 好的今天我们来讨论一下什么叫信息炸弹。 可能之前看过我文章的小伙伴们在想&#xff0c;这种听名字就具有攻击性的东西为什么不把它放到黑客七宗罪专栏里&#xff1f; 毕竟这只是个脚本&#xff0c;对于个人账号的攻击性确实强&#xff0c;聊天记录也会占用硬盘。但是…

handler+message【消息机制】

&#x1f356;&#x1f356; 作者 &#xff1a; 不良使 &#x1f356;&#x1f356;&#x1f356;&#x1f356; 潜力创作新星 华为云享专家 &#x1f356;&#x1f356;&#x1f356;&#x1f356;&#x1f356; PythonAndroid &#x1f356;&#x1f356;&#x1f356;&#…

c++中MessageBox弹窗的用法大全

想必大家都知道&#xff0c;MessageBox函数是c语言中很常用且好玩的函数之一&#xff0c;那你知道它怎么用吗&#xff1f; 这是MessageBox函数的标准格式之一&#xff0c;本人喜欢用这种格式&#xff0c;注意函数的大小写&#xff01; MessageBox不在 #include<bits/stdc.h&…

message broker

MB概述 MB的全称是message broker&#xff0c;即“消息代理”。“消息”一词前几年比较火&#xff0c;消息中间件也卖的很火&#xff0c;当时似乎J2EE的产品都要跟“消息”、“中间件”扯上点关系&#xff0c;以彰显潮流。我觉得初学者只需记住“消息”的异步性即可&#xff0c…

MP3音频文件格式(MPEG-1 audio layer 3)

MP3音频文件格式 【百度百科】mp3 &#xff08;一种音频编码方式&#xff09; 【维基百科】MP3&#xff08;本文重定向自 MPEG-1 Audio Layer 3&#xff09; MP3(MPEG-1 audio layer 3) MPEG-1音频分三层&#xff0c;分别为 MPEG-1 Layer1&#xff0c;MPEG-1 Layer2 以及 MPE…

怎么把wav文件改成mp3?

怎么把wav文件改成mp3&#xff1f;有过摄像摄影经历的小伙伴都应该认识wav&#xff0c;wav就是他们作品的保存格式。因为wav格式的文件体积特别大&#xff0c;在储存的时候会占用我们大量的内存&#xff0c;而且为了播放方便&#xff0c;我们通常要把wav文件改成mp3格式的&…

音频文件如何转成mp3格式

当提到音频文件格式时&#xff0c;大家往往会想到最为流行和广泛使用的mp3格式。mp3是一种广受欢迎的音频格式&#xff0c;因为各种音频格式自身特点的原因&#xff0c;所以将其他格式的音频文件转换成mp3是非常普遍的需求。就比如在我们日常生活中&#xff0c;下载到的各种格式…

如何转换音频格式为mp3?

一提到音乐&#xff0c;大家先想到的应该就是MP3了&#xff0c;既然MP3作为常用的&#xff0c;被大家所熟知的一种音频格式&#xff0c;那它必定有其他格式无可比拟的优点。其实mp3从功能上来讲它具有更强的携带性和传输性&#xff0c;利于保存和分享&#xff1b;其次MP3本身的…

电脑音频转换mp3格式怎么弄,教你音频怎么转换mp3格式

mp3格式是目前几乎全兼容的格式了&#xff0c;在我们参加一些会议或讲座时&#xff0c;需要录制一些重要的信息&#xff0c;结束后再进行复盘或分享。然而&#xff0c;不同的录制工具录制的音频格式也不同&#xff0c;这时使用软件将音频统一成mp3格式的话&#xff0c;就会方便…

免费在线MP3转换器:将音乐文件转换为MP3格式

在今天的数字时代&#xff0c;音乐成为了人们生活中不可或缺的一部分。然而&#xff0c;由于音乐文件格式的不同&#xff0c;我们有时可能无法在不同的设备上播放我们最喜爱的歌曲。MP3格式作为最常用的音乐文件格式之一&#xff0c;通常可以被几乎所有的设备支持&#xff0c;因…

mp3格式怎么弄?分享三个音频文件格式转换的方法

不知道小伙伴们有没有遇到过这样的情况&#xff0c;在网上下载一首歌下来&#xff0c;正想打开&#xff0c;结果却发现我们的播放器无法播放。你们知道这是为什么嘛&#xff0c;其实我们的音频文件是有很多不同的格式&#xff0c;其中就有些比较少见的格式&#xff0c;我们的音…

如何从MP4视频文件中抽取MP3音频?

简 介&#xff1a; 为了能够处理视频中的音频&#xff0c;测试了两种提取视频中的音频方法。一种是利用格式工程软件另外一种利用ffmpeg软件。 关键词&#xff1a; 视频文件&#xff0c;音频文件&#xff0c;mp4&#xff0c;mp3 #mermaid-svg-sPs0isryqtLTjZyg {font-family:&q…

如何将音频文件转换为MP3格式?

音频文件有很多种格式&#xff0c;如 WAV、FLAC、AAC 等&#xff0c;其中 MP3 是最为常见的一种格式&#xff0c;因为它具有压缩比高、音质损失少、兼容性强等优点&#xff0c;适合在各种设备上播放。如果你想将一个音频文件转换为 MP3 格式&#xff0c;可以采用以下几种方法&a…

Next() Nextline() hasNext()区别

next类和hasNext方法遇到缓冲区没数据时&#xff0c;会阻塞&#xff0c;等待输入后next类会读取&#xff0c;hasNext会返回true 1&#xff09;nextLine nextLine&#xff08;&#xff09;方法返回的是"\n"之前的所有字符&#xff0c;它是可以得到带空格的字符串的。 …

BNext

又搬来了大神器啊 来自德国HassoPlattner计算机系统工程研究院的NianhuiGuo和HaojinYang等研究者提出了BNext模型&#xff0c;成为第一个在ImageNet数据集上top1分类准确率突破80%的BNN。 两年前&#xff0c;依靠早期 BNN 工作 XNOR-Net 起家的 XNOR.AI 被苹果公司收购&#…

Next.js学习笔记

这是一个用于生产环境的React 框架&#xff0c;Next.js 为您提供生产环境所需的所有功能以及最佳的开发体验&#xff1a;包括静态及服务器端融合渲染、 支持 TypeScript、智能化打包、 路由预取等功能 无需任何配置。 create-next-app 使用 create-next-app创建新的 Next.js …

搭建vue3项目时出现Cannot read property ‘nextSibling‘ of null报错

记录自己学习中&#xff0c;出现的错误 在搭建vue3项目&#xff0c;配置router&#xff0c;vuex,element-ui后&#xff0c;运行项目页面白屏&#xff0c;控制台出现了Cannot read property nextSibling of null的错误 查看main.ts 文件&#xff0c;此时的写法是&#xff1a; …

hasNext、hasNextLine、next、nextLine保姆级详解

目录 前言 hasNext和hasNextLine的区别 hasNext 和 next组合 hasNext 和 NextLine组合 hasNextLine 和 next组合 hasNextLine 和 nextLine组合 验证hasNext、hasNextLine对输入代码的存储寿命 总结 前言 在查阅了大量网上相关资料都没有一个完整的解释&#xff0c;并且我…

细节!关于Java中的next与nextLine

目录 一、发现问题 二、解决问题 &#xff08;1&#xff09;输入连续字符串 &#xff08;2&#xff09;输入不连续字符&#xff08;含有空格等&#xff09; &#xff08;3&#xff09;nextLine()方法在前&#xff0c;next()方法在后 &#xff08;4&#xff09;next()方法在…

NextJs 学习笔记

NextJs 学习笔记 简述 之前使用过 Nuxt3 基于前端框架 Vue3 来开发网站&#xff0c;因为 Nuxt3 很多地方借鉴了基于 React 的 SSR 框架 Next&#xff0c;因此最近抽时间开始学习一下 Next 这个框架。 创建项目 npx create-next-applatest # or yarn create next-app # or p…