【Android】Handler消息机制
文章目录
- 前言
- 概述
- 核心组件概述
- Android消息机制概述
- Android消息机制分析
- ThreadLocal的工作原理
- ThreadLocal基础
- ThreadLocal实现原理
- MessageQueue
- Looper
- Handler的工作原理
- 总结
前言
本文用于记录Android的消息机制,主要是指Handler的运行机制。部分内容参考自《Android开发艺术探索》,感兴趣可以阅读原书。
概述
核心组件概述
Handler是Android消息机制的上层接口,通过它可以轻松将一个任务从一线程切换到Handler所在的线程执行,如由子线程切换到主线程更新UI。具体来说有时候我们需要在子线程中执行耗时操作,当耗时操作结束时希望将UI更新,这时候我们就需要将线程切换到主线程了,也就是UI线程(因为Android不允许在子线程更新UI)。
Handler的运行需要底层MessageQueue和Looper的支持。MessageQueue也就是消息队列,它可以存储消息并以队列的形式提供插入消息和删除消息操作(注意:虽然叫消息队列,但它内存存储结构并不是队列而是单链表)。Looper我们可以将它理解为消息循环,它会以无限循环的方式去消息队列中查找是否存在消息,有的话就对消息进行处理,没有的话就一直等待。
此外Looper中还有一个特殊的概念——ThreadLocal,ThreadLocal不是线程,它的作用是在每个线程中独立的存储数据。Handler在创建的时候,需要使用当前线程的Looper来构建消息循环系统,,那么如何获取当前线程的Looper呢?此时就要用到ThreadLocal,我们知道ThreadLocal可以在不同线程互不干扰的存储并提供数据,我们就可以通过ThreadLocal存储并获取每个线程的Looper了。需要注意的是除主线程外,其他线程默认是没有Looper的,想要使用Handler就必须为线程创建Looper。
Android消息机制概述
Android的消息机制主要指Handler的运行机制以及Handler附带的MessageQueue和Looper的工作过程。系统之所以提供Handler,就是为了解决在子线程中无法更新UI的矛盾。试想一下,当我们通过网络请求从服务端拉取了一些数据需要更新在UI上,为了避免造成ANR,我们需要在子线程中执行网络请求操作,那该怎么访问UI呢,这时候我们就可以使用Handler,因为它允许我们将一个任务由某个线程切换到另一个线程执行。
延伸
- 系统为什么不允许在子线程更新UI?
Android的UI控件是线程不安全的,如果在多线程并发访问可能导致UI控件处于不可预期的状态。
- 为什么不给UI控件的访问加上锁机制?
首先加锁会让UI访问的逻辑变得十分复杂,其次加锁会降低UI的访问效率,因为加锁会阻碍某些线程的执行。
接着概述一下Android的消息机制,首先我们需要创建得到Handler,Handler创建时获取当前线程的Looper来构建内部的消息循环系统(除主线程外都需要手动创建Looper)。Handler创建完毕后,此时内部的Looper和MessageQueue就可以和Handler协调工作了。
Android消息机制分析
ThreadLocal的工作原理
ThreadLocal基础
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中才可以获取到存储到数据,对于其他线程数据是不可访问的。
当某些数据是以线程为作用域而且不同线程需要不同的数据副本时,就可以考虑使用ThreadLocal。如对于Handler来说,他需要获取当前线程的Looper,因为Looper的作用域是线程且不同线程有不同的Looper,此时我们就可以通过ThreadLocal实现Looper在线程内的存取。
ThreadLocal的另一个使用场景是实现复杂逻辑下的对象传递。比如监听器的传递,有时一个线程中的任务过于复杂——函数调用栈比较深或代码入口多样,我们需要监听器能够贯彻整个线程的执行过程,这时候就可以使用ThreadLocal让监听器作为一个全局变量而存在,在线程内部只需通过get方法获取监听器即可。
上面介绍了这么多理论知识,接下来我们通过一个具体的代码实例来加深理解,在这个实例中我们会创建一个Integer类型的ThreadLocal对象,接着在主线程和三个子线程中分别设置并输出它的值:
//创建ThreadLocalThreadLocal<Integer> booleanThreadLocal=new ThreadLocal<>();booleanThreadLocal.set(1);//设置主线程的值为1System.out.println("ThreadLocal的值为:"+booleanThreadLocal.get());//子线程1new Thread(new Runnable() {@Overridepublic void run() {booleanThreadLocal.set(2);//设置子线程1的值为2System.out.println("ThreadLocal的值为:"+booleanThreadLocal.get());}}).start();//子线程2new Thread(new Runnable() {@Overridepublic void run() {booleanThreadLocal.set(3);//设置子线程2的值为3System.out.println("ThreadLocal的值为:"+booleanThreadLocal.get());}}).start();//子线程3new Thread(new Runnable() {@Overridepublic void run() {System.out.println("ThreadLocal的值为:"+booleanThreadLocal.get());}}).start();
打印结果如下:
可以看到每个线程打印的都是我们设置的值,因为在子线程3我们没有设置值,所以打印的是null。
ThreadLocal之所以能实现上述的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自线程中取出一个数组,然后根据当前ThreadLocal的索引去查找对应的value。显然每个线程的数组是不同的,所以通过ThreadLocal可以在指定线程存储数据,其他线程无法访问,不同线程间彼此互不干扰。
ThreadLocal实现原理
想弄清ThreadLocal的实现原理,需要弄清它的set方法和get方法
- set方法
这里先介绍一下set方法执行的大概流程,再来介绍一下源码:
1.首先我们会获取当前的线程,然后根据当前的线程获取当前线程的ThreadLocalMap对象(ThreadLocalMap使用一个数组作为存储结构,数组元素为Entry,传入的的ThreadLocal对象作为Entry的key,要存入的值为value)
2.如果获取到了当前线程的ThreadLocalMap对象,那么就将数据存进去
3.如果获取的ThreadLocalMap对象是null,那么就为当前线程创建一个ThreadLocalMap对象
源码分析
打开ThreadLocal的原码找到set方法:
public void set(T value) {Thread t = Thread.currentThread();//获取当前线程ThreadLocalMap map = getMap(t);//获取当前线程对应的ThreadLocalMap对象if (map != null) {map.set(this, value);//这里的this就是当前线程的ThreadLocal对象} else {createMap(t, value);}}
这个set方法接受一个value参数,这个参数可以指定为任何类型,我们先获取当前线程接着获取当前线程的ThreadLocalMap对象,如果当前线程有ThreadLocalMap对象,那么就将数据直接存进去(存进一个Entry对象,当前线程的ThreadLocal对象作为key,要存储的数据作为value);如果当前线程没有ThreadLocalMap对象,那么我们就需要去创建一个ThreadLocalMap对象:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//初始化一个数组table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//计算key的存储位置table[i] = new Entry(firstKey, firstValue);//将数据放到计算出的位置size = 1;setThreshold(INITIAL_CAPACITY);}
可以看到当调用createMap方法后回去创建一个ThreadLocalMap实例,在创建ThreadLocalMap实例时,会初始化一个数组,这个数组就是我们真正用于存取数据的数据结构,这个数组存储数据的数据类型是Entry,也就是一个key-value的数据结构。接着我们会通过传给我们的ThreadLocal参数计算出这个key的储存位置并将数据存到计算出的位置。
小结一下这个过程:我们的每个Thread(线程)都有唯一的一个ThreadLocalMap对象,这个ThreadLocalMap对象使用Enrty[]数组存储数据,Entry[]数组的每个元素都是以当前线程的ThreadLocal对象作为key,要存储的数据作为value的数据结构。
另外提一下使用数组是因为同一线程中可能有多个不同类型的ThreadLocal对象,它们计算出的key是不同的,就对应了在数组上不同的存储位置。
- get方法
源码分析
找到get方法
public T get() {Thread t = Thread.currentThread();//获取当前线程ThreadLocalMap map = getMap(t);//获取当前线程对应的ThreadLocalMap对象if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();//表示当前线程都没有ThreadLocalMap对象或者有ThreadLocalMap对象当不存在ThreadLocal关联的Entry对象,去做一些初始化的工作}
同样先获取当前线程再获取当前线程的ThreadLocalMap对象,如果对象不为空,那么就根据key也就是ThreadLocal对象去获取Entry对象,最后获取到的Entry对象不为空时将数据返回,获取Entry对象的方法如下:
private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);//计算出存储的位置Entry e = table[i];if (e != null && e.refersTo(key))return e;elsereturn getEntryAfterMiss(key, i, e);}
根据传入的key计算出key的存储位置,这个过程在我们set()方法存储数据时是相对应的,都是根据key(ThreadLocal对象)计算出在数组上的存储位置,接着直接存储位置直接获得Entry对象返回。
通过ThreadLocal的get方法和set方法可以看出,不同线程操作的都是各自线程中的ThreadLocalMap对象的Entry[]数组,所以ThreadLocal可以实现线程间存取数据互不干扰。
MessageQueue
MessageQueue也就是消息队列,虽然它叫做消息队列,但它的实现方法并不是队列,而是使用一个单链表来实现消息列表。
MessageQueue主要有两个方法:enqueueMessage()插入消息和next()读取消息,需要注意的是读取消息时会伴随着消息的删除操作,也就是在读取完这条消息后将其从消息队列中移除。
Looper
Looper在Android消息机制中扮演循环者的角色,它会不停在MessageQueue中查看是否有新消息,如果有新消息就立即处理,否则就一直阻塞在那。在创建Looper时会创建一个MessageQueue对象并获取当前的线程,源码如下:
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}
上面我们介绍过,Handler的工作需要Looper,如果一个线程没有Looper对象使用Handler对象就会报错,那么我们怎么获取Looper对象呢,答案是直接调用Looper.prepear()方法即可创建一个Looper对象,然后通过Looper.loop()来开启消息循环,如下:
//子线程1new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();Handler hanlder=new Handler();Looper.loop();}}).start();
Looper也是可以退出的,有quit()和quitSafely()两种方法,使用quit()会直接退出Looper,而quitSafely()会设置一个退出标记,接着把消息队列中已有的消息处理完毕才能安全退出。当Looper退出后,使用Handler的send方法发消息会返回false。
如果我们在子线程中创建了Looper,那么我们就应该在所有事做完之后调用上述两个方法退出Looper。
上面我们还提到了一个loop()方法,只有调用Looper.loop()方法后,消息循环系统才会真正的起作用,我们可以看看它的源码:
打开loop()方法的源码我们可以看到有下面一段代码:
for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}
}
首先我们可以知道loop方法是一个死循环,只有当!loopOnce(me, ident, thresholdOverride
条件满足时才会跳出循环,接着我们打开loopOnce方法看看,首先里面有段代码:
Message msg = me.mQueue.next(); // might block
if (msg == null) {// No message indicates that the message queue is quitting.return false;
}
loop()方法会调用MessageQueue的next()方法一直获取消息,此时一直会处于循环状态。只有当MessageQueue的next()方法返回了null循环才会结束,那么什么情况下MessageQueue的next()方法才会返回null呢。答案是当Looper调用了quit()方法或quitSafely()方法时,我们看看这两个方法:
public void quit() {mQueue.quit(false);
}
public void quitSafely() {mQueue.quit(true);
}
可以看到Looper的quit()方法和quitSefely()方法这两个方法调用了MessageQueue的quit()方法,我们打开MessageQueue来看看它的quit()方法:
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {return;}mQuitting = true;if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}}
MessageQueue的quit()方法接收一个布尔型参数用来表示是不是安全的,也就是区分Looper的quit()方法和quitSafely()方法。MessageQueue的quit()方法中有段代码,用来真正进行处理:
if (safe) {//quitSafely()安全退出removeAllFutureMessagesLocked();
} else {//quit()removeAllMessagesLocked();
}
点进这两个方法你会发现,这两个方法都是将MessageQueue.next()方法返回的值设为null,这样我们的Looper.quit()和Looper.quitSafely()方法就会执行,Looper退出。
如果MessageQueue的next()方法返回的值不为null,那么就会调用loopOnce()方法里的msg.target.dispatchMessage(msg);
方法来处理这条消息,msg.target表示发送这条消息的Handler对象,也就是说Handler发送完消息,这条消息最终又交给Handler的dispatchMessage()方法来处理了,但dispatchMessage()方法是在Looper.loop()方法中执行的。这样我们就可以实现在子线程执行耗时操作,然后在子线程调用Looper.loop()方法执行Handler的dispatchMessage()方法,通知Handler来处理消息,最后到主线程(也就是Handler所在的线程)进行处理消息的操作了。
Handler的工作原理
Handler主要包括消息的发送和接收。发送就是通过post的一系列方法以及send的一系列方法来完成的,其实post的一系列方法最终也是通过send的一系列方法来实现的。
Handler的sendMessage()方法经过层层调用最终会调用到一个enqueueMessage()方法,是不是有点眼熟,在MessageQueue中我们就是通过这个方法插入消息,在Handler的enqueueMessage()方法就是调用MessageQueue.enqueueMessage()方法来向消息队列中添加一条消息,Handler的enqueueMessage()方法如下:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);//调用MessageQueue的enqueueMessage()方法插入一条消息}
当调用Handler的send()方法时,MessageQueue的enqueueMessage()方法执行向消息队列中插入一条消息,同时MessageQueue的next()方法执行将这条消息返回给Looper(Looper通过loop()方法一直在MessageQueue中获取消息),Looper接收到消息就开始处理,最终消息交给Handler处理,即Handler.dispatchMessage()方法被调用,此时Handler进入处理消息阶段,我们可以打开Handler的源码看看dispatchMessage()方法:
public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
这个方法中首先检查Message的callback对象(一个Runnable对象,就是Handler的post()方法传递的Runnable参数),不为空使用handleCallback(msg)方法处理消息。如果没有传递Runnable参数,也就是msg.callback为空,那么就接着判断mCallback是否为空,Callback是一个空接口,其源码定义如下:
public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/boolean handleMessage(@NonNull Message msg);}
通常我们在开发过程中,创建Handler的方式是派生一个Handler的子类并重写其handlerMessage()方法来进行具体的消息处理。当我们不想派生子类时,就可以通过Callback这样创建一个Handler对象Handler handler=new Handler(callback)
,这样我们就不需要派生Handler的子类。
其实上面代码不是特别重要,重要的是handleMessage(msg)
这个方法。Handler的dispatchMessage()方法最终,调用handleMessage()方法来进行具体的消息处理。
总结
最后我们总结一下上面讲到的几个方法的相互之间的调用:
- Handler通过sendMessage()方法向MessageQueue中添加Message(MessageQueue的enqueueMessage()方法执行)
- Looper通过loop()方法,不停检查MessageQueue中是否有新消息(除非调用了Looper的quit()方法或quitSafely()方法),有就执行消息(MessageQueue的next()方法执行)
- 处理消息时Looper调用Handler的dispatchMessage()方法,将消息交给Handler的HandleMessage()方法执行
最终我们就可以实现在子线程执行耗时操作,再在子线程通过dispatchMessage()方法通知主线程执行handleMessage()方法,在主线程更新UI。
分享到此结束,共勉!
相关文章:

【Android】Handler消息机制
文章目录 前言概述核心组件概述Android消息机制概述 Android消息机制分析ThreadLocal的工作原理ThreadLocal基础ThreadLocal实现原理 MessageQueueLooperHandler的工作原理总结 前言 本文用于记录Android的消息机制,主要是指Handler的运行机制。部分内容参考自《An…...

大数据必懂知识点:Parquet、ORC还是Avro作为数据存储格式,哪种在性能和压缩率上更优
目录 第一章 相关理论 1.1 Parquet格式介绍 1.1.1 起源与发展 1.1.2 特点与优势 1.2 ORC格式介绍 1.3 Avro格式介绍 1.3.1 跨语言支持 1.3.2 动态映射 1.3.3 丰富的数据模式 1.3.4 数据模式灵活性 第二章 种格式性能比较 2.1 读写性能对比 2.2 查询性能对比 2.3 压…...

P1387 最大正方形
题目描述 在一个nm 的只包含 0 和 1 的矩阵里找出一个不包含 0 的最大正方形,输出边长。 输入格式 输入文件第一行为两个整数n,m(1≤n,m≤100),接下来 n 行,每行 m 个数字,用空格隔开,0 或 1。 输出格式 一个整数…...

Python知识点:如何使用Multiprocessing进行并行任务管理
开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! 如何在Python中使用Multiprocessing进行并行任务管理 在现代编程中,…...

React常见优化问题
在React开发中,性能优化是一个重要且持续的过程,旨在提升应用的响应速度和用户体验。以下是一些常见的React优化问题详解,并附上相应的代码示例。 1. 避免不必要的组件渲染 React组件的渲染是由其props或state的变化触发的。但是,…...

css 简单网页布局——浮动(一)
1. 三种布局方式 1.1 标准流 1.2 浮动的使用 1.3 简述浮动 1.3.1 浮动三大特性 <style>.out {border: 1px red solid;width: 1000px;height: 500px;}.one {background-color: aquamarine;width: 200px;height: 100px;}.two {background-color: blueviolet;width: 200px;h…...

设计模式(3)builder
需求: 对于复杂的对象,我们只需要 通过 设置一些参数,就可以得到相对应的 实例。 简单来说, 需求就是用一个类 通过方法返回一个 新建的对象,而且可以通过方法去设置这个对象 public interface CarBuilder {void se…...

Day01-MySQL数据库介绍及部署
Day01-MySQL数据库介绍及部署 1、数据库服务概述介绍1.1 企业中为什么需要数据库?1.2 数据库服务作用1.3 数据库服务分类 2、数据库服务安装部署2.1 数据库版本应用2.2 数据库服务程序下载2.3 数据库软件安装方式2.3.1 二进制安装步骤 3、数据库服务初始化介绍3.1 安…...

分享一个餐饮连锁店点餐系统 餐馆食材采购系统Java、python、php三个版本(源码、调试、LW、开题、PPT)
💕💕作者:计算机源码社 💕💕个人简介:本人 八年开发经验,擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等,大家有这一块的问题可以一起交流&…...

解决跨域问题
第一种 让后端解决 第二种 通过代理来解决 首先可以先搭建后端接口 解决则参照vue-cli官网 首先新建一个vue.config.js文件 然后在项目的根目录新建两个文件夹 开发环境和生产环境 然后可以使用环境变量 系统会自动识别你是生产环境还是开发环境 然后在封装的axios中配…...

面试知识储备-多线程
1.线程的概念 线程使得在一个程序中可以同时执行多个任务。在 Java 应用程序中,多个线程可以同时运行,例如一个线程可以处理用户输入,另一个线程可以进行后台数据处理。 2.创建线程的方式 (1)重写thread类中的run方法…...

边缘计算插上AI的翅膀会咋样?
人工智能(Artificial Intelligence,AI)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学,是新一轮产业革命的重要驱动力量。2022年底发布的ChatGPT将人工智能技术上升到了一个新的高度。如今&#x…...

脉冲神经网络(SNN)论文阅读(六)-----ECCV-2024 脉冲驱动的SNN目标检测框架:SpikeYOLO
原文链接:CSDN-脉冲神经网络(SNN)论文阅读(六)-----ECCV-2024 脉冲驱动的SNN目标检测框架:SpikeYOLO Integer-Valued Training and Spike-Driven Inference Spiking Neural Network for High-performance …...

周报_2024/10/6
周报 时间 2024/9/30——2024/10/6 科研进展 写项目标书 实验了不同比例的标签加噪,模型效果随着标签加噪比例增加下降明显 下周计划 构造概念漂移数据集 借鉴其他文章中应对标签加噪的做法...

[深度学习][python]yolov11+bytetrack+pyqt5实现目标追踪
【算法介绍】 YOLOv11、ByteTrack和PyQt5的组合为实现高效目标追踪提供了一个强大的解决方案。 YOLOv11是YOLO系列的最新版本,它在保持高检测速度的同时,通过改进网络结构、优化损失函数等方式,提高了检测精度,能够同时处理多个…...

如何使用ssm实现基于Web的穿戴搭配系统的设计与实现+vue
TOC ssm784基于Web的穿戴搭配系统的设计与实现vue 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展,用了短短的几十年时间就风靡全球,使得全球各个行业都进行了互联网的改造升级,标志着互联网浪潮的来临。在这个新的时代&…...

JavaScript的设计模式
JavaScript设计模式是指在面向对象编程中,通过对类和对象进行抽象和泛化,提取出一些通用的设计思路和解决方案,以解决常见的软件设计问题。这些设计模式可以分为以下几类进行详细介绍: 一、创建型模式 1. 工厂模式(F…...

CIKM 2024 | 时空数据(Spatial-temporal)论文总结
CIKM 2024于10月21号-10月25号在美国爱达荷州博伊西举行(Boise, Idaho, USA) 本文总结了CIKM 2024有关时空数据(spatial-temporal data)的相关论文,主要包含交通预测,插补,事故预测,…...

计算机毕业设计 网上体育商城系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...

【数据结构】什么是哈希表(散列表)?
🦄个人主页:修修修也 🎏所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 📌哈希表的概念 📌哈希函数的构造方法 🎏直接定址法 🎏除留余数法 🎏平方取中法 🎏折叠法 &#x…...

【优选算法】(第二十三篇)
目录 快速选择算法(medium) 题目解析 讲解算法原理 编写代码 最⼩的k个数(medium) 题目解析 讲解算法原理 编写代码 快速选择算法(medium) 题目解析 1.题目链接:. - 力扣(L…...

Java.数据结构.HashSet
目录 1 基本概念 2 数据结构 3 常用操作 3.1 add(E e):向HashSet中添加元素 3.2 remove(Object o):从HashSet中移除元素 3.3 contains(Object o):判断HashSet中是否包含指定元素 3.4 size():返回HashSet中元素的个数 3.5 …...

关于懒惰学习与渴求学习的一份介绍
在这篇文章中,我将介绍些懒惰学习与渴求学习的算法例子,会介绍其概念、优缺点以及其python的运用。 一、渴求学习 1.1概念 渴求学习(Eager Learning)是指在训练阶段构建出复杂的模型,然后在预测阶段运用这个构建出的…...

sed 环境配置
参考项目来自这里: https://github.com/DCASE-REPO/DESED_task/tree/master/recipes/dcase2023_task4_baseline 1. 更新自己的 conda 避免一些包在旧的conda 环境中不存在; conda update conda使用conda 指定安装 对应版本 # CUDA 11.7 conda instal…...

黑神话:仙童,数据库自动反射魔法棒
黑神话:仙童,数据库自动反射魔法棒 Golang 通用代码生成器仙童发布了最新版本电音仙女尝鲜版十一及其介绍视频,视频请见:https://www.bilibili.com/video/BV1ET4wecEBk/ 此视频介绍了使用最新版的仙童代码生成器,将 …...

香江电器冲刺港交所上市:投资方提前撤资退出,因对赌协议而赔偿
近日,湖北香江电器股份有限公司(X.J. ELECTRICS (HU BEI) CO., LTD,下称“香江电器”)披露招股书,准备在港交所主板上市,国金证券为其独家保荐人。据贝多财经了解,香江电器曾计划在A股上市&…...

SpringSecurity实现自定义登录接口
SpringSecurity实现自定义登录接口 1、配置类 ConfigClazz(SpringSecuriey的) //首先就是要有一个配置类Resourceprivate DIYUsernamePasswordAuthenticationFilter diyUsernamePasswordAuthenticationFilter;/*SpringSecurity配置*/Beanpublic Securit…...

深度解析:Tkinter 界面布局与优化技巧
目录 深度解析:Tkinter 界面布局与优化技巧1. Tkinter 布局管理简介如何选择合适的布局管理器 2. pack() 布局管理详解嵌套布局 3. grid() 布局管理详解行列合并 4. place() 精确布局详解5. Tkinter 界面优化技巧自适应布局响应式布局资源管理 6. 项目示例ÿ…...

RCE_无回显
<aside> 💡 无回显 </aside> 写文件 **curl -o shell.php <http://xxxxxx.txt> wget -O shell.php <http://xxxxxx.txt>**请求带出 **curl <http://requestbin.net/r/1kiej1p1?pcat> /flag|base64 curl xxd -p /flag.xxxxxx.dnslo…...

文心一言智能体——绿色生活管家
最近,我在参加文心一言智能体大赛,这是我的智能体地址绿色生活管家,点击即可访问,大家可以去向我的智能体提问,提五个问题左右即可,真的非常感谢大家!好人一生平安🌼🌼&a…...