直面原理:5 张图彻底了解 Android TextToSpeech 机制

ChatGPT如此火爆,但它的强悍在于NLU(自然语言理解)、DM(对话管理)和NLG(自然语言生成)这三块,而Recognition识别和TTS播报这两块是缺失的。假使你的 App 接入了 ChatGPT,但如果需要播报出来的话,TextToSpeech机制就可以派上用场了。
1 前言
关于语音方面的交互,Android SDK 提供了用于语音交互的 VoiceInteraction 机制、语音识别的 Recognition 接口、语音播报的 TTS 接口。
前者已经介绍过,本次主要聊聊第 3 块即 TTS,后续会分析下第 2 块即 Android 标准的 Recognition 机制。
通过 TextToSpeech 机制,任意 App 都可以方便地采用系统内置或第三方提供的 TTS Engine 进行播放铃声提示、语音提示的请求,Engine 可以由系统选择默认的 provider 来执行操作,也可由 App 具体指定偏好的目标 Engine 来完成。
默认 TTS Engine 可以在设备设置的路径中找到,亦可由用户手动更改:Settings -> Accessibility -> Text-to-speech ouput -> preferred engine

TextToSpeech 机制的优点有很多:
- 对于需要使用 TTS 的请求 App 而言:无需关心 TTS 的具体实现,通过
TextToSpeechAPI 即用即有 - 对于需要对外提供 TTS 能力的实现 Engine 而言,无需维护复杂的 TTS 时序和逻辑,按照
TextToSpeechService框架的定义对接即可,无需关心系统如何将实现和请求进行衔接
本文将会阐述 TextToSpeech 机制的调用、Engine 的实现以及系统调度这三块,彻底梳理清楚整个流程。
2 TextToSpeech 调用

TextToSpeech API 是为 TTS 调用准备,总体比较简单。
最主要的是提供初始化 TTS 接口的 TextToSpeech() 构造函数和初始化后的回调 OnInitListener,后续的播放 TTS 的 speak() 和播放铃声的 playEarcon()。
比较重要的是处理播放请求的 4 种回调结果,需要依据不同结果进行 TTS 播报开始的状态记录、播报完毕后的下一步动作、抑或是在播报出错时对音频焦点的管理等等。
之前的 OnUtteranceCompletedListener 在 API level 18 时被废弃,可以使用回调更为精细的 UtteranceProgressListener。
// TTSTest.kt
class TTSTest(context: Context) {private val tts: TextToSpeech = TextToSpeech(context) { initResult -> ... }init {tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {override fun onStart(utteranceId: String?) { ... }override fun onDone(utteranceId: String?) { ... }override fun onStop(utteranceId: String?, interrupted: Boolean) { ... }override fun onError(utteranceId: String?) { ... }})}fun testTextToSpeech(context: Context) {tts.speak("你好,汽车",TextToSpeech.QUEUE_ADD,Bundle(),"xxdtgfsf")tts.playEarcon(EARCON_DONE,TextToSpeech.QUEUE_ADD,Bundle(),"yydtgfsf")}companion object {const val EARCON_DONE = "earCon_done"}
}
3 TextToSpeech 系统调度
3.1 init 绑定

首先从 TextToSpeech() 的实现入手,以了解在 TTS 播报之前,系统和 TTS Engine 之间做了什么准备工作。
-
其触发的
initTTS()将按照如下顺序查找需要连接到哪个 Engine:- 如果构造 TTS 接口的实例时指定了目标 Engine 的 package,那么首选连接到该 Engine
- 反之,获取设备设置的 default Engine 并连接,设置来自于
TtsEngines从系统设置数据SettingsProvider中 读取TTS_DEFAULT_SYNTH而来 - 如果 default 不存在或者没有安装的话,从 TtsEngines 获取第一位的系统 Engine 并连接。第一位指的是从所有 TTS Service 实现 Engine 列表里获得第一个属于 system image 的 Engine
-
连接的话均是调用
connectToEngine(),其将依据调用来源来采用不同的Connection内部实现去connect():-
如果调用不是来自 system,采用
DirectConnection- 其 connect() 实现较为简单,封装 Action 为
INTENT_ACTION_TTS_SERVICE的 Intent 进行bindService(),后续由AMS执行和 Engine 的绑定,这里不再展开
- 其 connect() 实现较为简单,封装 Action 为
-
反之,采用
SystemConnection,原因在于系统的 TTS 请求可能很多,不能像其他 App 一样总是创建一个新的连接,而是需要 cache 并复用这种连接-
具体是直接获取名为
texttospeech、管理 TTS Service 的系统服务TextToSpeechManagerService的接口代理并直接调用它的createSession()创建一个 session,同时暂存其指向的ITextToSpeechSession代理接口。该 session 实际上还是
AIDL机制,TTS 系统服务的内部会创建专用的TextToSpeechSessionConnection去 bind 和 cache Engine,这里不再赘述
-
-
无论是哪种方式,在 connected 之后都需要将具体的 TTS Eninge 的
ITextToSpeechService接口实例暂存,同时将 Connection 实例暂存到 mServiceConnection,给外部类接收到 speak() 的时候使用。而且要留意,此刻还会启动一个异步任务SetupConnectionAsyncTask将自己作为 Binder 接口ITextToSpeechCallback返回给 Engine 以处理完之后回调结果给 Request
-
-
connect 执行完毕并结果 OK 的话,还要暂存到
mConnectingServiceConnection,以在结束 TTS 需求的时候释放连接使用。并通过dispatchOnInit()传递SUCCESS给 Request App- 实现很简单,将结果 Enum 回调给初始化传入的
OnInitListener接口
- 实现很简单,将结果 Enum 回调给初始化传入的
-
如果连接失败的话,则调用
dispatchOnInit()传递ERROR
// TextToSpeech.java
public class TextToSpeech {public TextToSpeech(Context context, OnInitListener listener) {this(context, listener, null);}private TextToSpeech( ... ) {...initTts();}private int initTts() {// Step 1: Try connecting to the engine that was requested.if (mRequestedEngine != null) {if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {if (connectToEngine(mRequestedEngine)) {mCurrentEngine = mRequestedEngine;return SUCCESS;}...} else if (!mUseFallback) {...dispatchOnInit(ERROR);return ERROR;}}// Step 2: Try connecting to the user's default engine.final String defaultEngine = getDefaultEngine();...// Step 3: Try connecting to the highest ranked engine in the system.final String highestRanked = mEnginesHelper.getHighestRankedEngineName();...dispatchOnInit(ERROR);return ERROR;}private boolean connectToEngine(String engine) {Connection connection;if (mIsSystem) {connection = new SystemConnection();} else {connection = new DirectConnection();}boolean bound = connection.connect(engine);if (!bound) {return false;} else {mConnectingServiceConnection = connection;return true;}}
}
Connection 内部类和其两个子类的实现:
// TextToSpeech.java
public class TextToSpeech {...private abstract class Connection implements ServiceConnection {private ITextToSpeechService mService;...private final ITextToSpeechCallback.Stub mCallback =new ITextToSpeechCallback.Stub() {public void onStop(String utteranceId, boolean isStarted)throws RemoteException {UtteranceProgressListener listener = mUtteranceProgressListener;if (listener != null) {listener.onStop(utteranceId, isStarted);}};@Overridepublic void onSuccess(String utteranceId) { ... }@Overridepublic void onError(String utteranceId, int errorCode) { ... }@Overridepublic void onStart(String utteranceId) { ... }...};@Overridepublic void onServiceConnected(ComponentName componentName, IBinder service) {synchronized(mStartLock) {mConnectingServiceConnection = null;mService = ITextToSpeechService.Stub.asInterface(service);mServiceConnection = Connection.this;mEstablished = false;mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask();mOnSetupConnectionAsyncTask.execute();}} ...}private class DirectConnection extends Connection {@Overrideboolean connect(String engine) {Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);intent.setPackage(engine);return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);}...}private class SystemConnection extends Connection {...boolean connect(String engine) {IBinder binder = ServiceManager.getService(Context.TEXT_TO_SPEECH_MANAGER_SERVICE);...try {manager.createSession(engine, new ITextToSpeechSessionCallback.Stub() {...});return true;} ...}...}
}
3.2 speak 播报

后面看看重要的 speak(),系统做了什么具体实现。
-
首先将 speak() 对应的调用远程接口的操作封装为 Action 接口实例,并交给 init() 时暂存的已连接的 Connection 实例去调度。
// TextToSpeech.java public class TextToSpeech {...private Connection mServiceConnection;public int speak(final CharSequence text, ... ) {return runAction((ITextToSpeechService service) -> {...}, ERROR, "speak");}private <R> R runAction(Action<R> action, R errorResult, String method) {return runAction(action, errorResult, method, true, true);}private <R> R runAction( ... ) {synchronized (mStartLock) {...return mServiceConnection.runAction(action, errorResult, method, reconnect,onlyEstablishedConnection);}}private abstract class Connection implements ServiceConnection {public <R> R runAction( ... ) {synchronized (mStartLock) {try {...return action.run(mService);}...}}} } -
Action的实际内容是先从mUtterancesMap 里查找目标文本是否有设置过本地的 audio 资源:- 如有设置的话,调用用 TTS Engine 的
playAudio()直接播放 - 反之调用 text 转 audio 的接口
speak()
// TextToSpeech.java public class TextToSpeech {...public int speak(final CharSequence text, ... ) {return runAction((ITextToSpeechService service) -> {Uri utteranceUri = mUtterances.get(text);if (utteranceUri != null) {return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,getParams(params), utteranceId);} else {return service.speak(getCallerIdentity(), text, queueMode, getParams(params),utteranceId);}}, ERROR, "speak");}... }后面即是 TextToSpeechService 的实现环节。
- 如有设置的话,调用用 TTS Engine 的
4 TextToSpeechService 实现

-
TextToSpeechService 内接收的实现是向内部的
SynthHandler发送封装的 speak 或 playAudio 请求的SpeechItem。SynthHandler 绑定到 TextToSpeechService 初始化的时候启动的、名为 “SynthThread” 的 HandlerThread。
- speak 请求封装给 Handler 的是
SynthesisSpeechItem - playAudio 请求封装的是
AudioSpeechItem
// TextToSpeechService.java public abstract class TextToSpeechService extends Service {private final ITextToSpeechService.Stub mBinder =new ITextToSpeechService.Stub() {@Overridepublic int speak(IBinder caller,CharSequence text,int queueMode,Bundle params,String utteranceId) {SpeechItem item =new SynthesisSpeechItem(caller,Binder.getCallingUid(),Binder.getCallingPid(),params,utteranceId,text);return mSynthHandler.enqueueSpeechItem(queueMode, item);}@Overridepublic int playAudio( ... ) {SpeechItem item =new AudioSpeechItem( ... );...}...};... } - speak 请求封装给 Handler 的是
-
SynthHandler 拿到 SpeechItem 后根据 queueMode 的值决定是 stop() 还是继续播放。播放的话,是封装进一步 play 的操作 Message 给 Handler。
// TextToSpeechService.javaprivate class SynthHandler extends Handler {...public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {UtteranceProgressDispatcher utterenceProgress = null;if (speechItem instanceof UtteranceProgressDispatcher) {utterenceProgress = (UtteranceProgressDispatcher) speechItem;}if (!speechItem.isValid()) {if (utterenceProgress != null) {utterenceProgress.dispatchOnError(TextToSpeech.ERROR_INVALID_REQUEST);}return TextToSpeech.ERROR;}if (queueMode == TextToSpeech.QUEUE_FLUSH) {stopForApp(speechItem.getCallerIdentity());} else if (queueMode == TextToSpeech.QUEUE_DESTROY) {stopAll();}Runnable runnable = new Runnable() {@Overridepublic void run() {if (setCurrentSpeechItem(speechItem)) {speechItem.play();removeCurrentSpeechItem();} else {speechItem.stop();}}};Message msg = Message.obtain(this, runnable);msg.obj = speechItem.getCallerIdentity();if (sendMessage(msg)) {return TextToSpeech.SUCCESS;} else {if (utterenceProgress != null) {utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE);}return TextToSpeech.ERROR;}}...} -
play() 具体是调用 playImpl() 继续。对于 SynthesisSpeechItem 来说,将初始化时创建的
SynthesisRequest实例和SynthesisCallback实例(此处的实现是PlaybackSynthesisCallback)收集和调用onSynthesizeText()进一步处理,用于请求和回调结果。// TextToSpeechService.javaprivate abstract class SpeechItem {...public void play() {synchronized (this) {if (mStarted) {throw new IllegalStateException("play() called twice");}mStarted = true;}playImpl();}}class SynthesisSpeechItem extends UtteranceSpeechItemWithParams {public SynthesisSpeechItem(...String utteranceId,CharSequence text) {mSynthesisRequest = new SynthesisRequest(mText, mParams);...}...@Overrideprotected void playImpl() {AbstractSynthesisCallback synthesisCallback;mEventLogger.onRequestProcessingStart();synchronized (this) {...mSynthesisCallback = createSynthesisCallback();synthesisCallback = mSynthesisCallback;}TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {synthesisCallback.done();}}...} -
onSynthesizeText() 是 abstract 方法,需要 Engine 复写以将 text 合成 audio 数据,也是 TTS 功能里最核心的实现。
- Engine 需要从
SynthesisRequest中提取 speak 的目标文本、参数等信息,针对不同信息进行区别处理。并通过SynthesisCallback的各接口将数据和时机带回: - 在数据合成前,通过
start()告诉系统生成音频的采样频率,多少位pcm格式音频,几通道等等。PlaybackSynthesisCallback的实现将会创建播放的SynthesisPlaybackQueueItem交由AudioPlaybackHandler去排队调度 - 之后,通过
audioAvailable()接口将合成的数据以 byte[] 形式传递回来,会取出 start() 时创建的 QueueItem put 该 audio 数据开始播放 - 最后,通过
done()告知合成完毕
// PlaybackSynthesisCallback.java class PlaybackSynthesisCallback extends AbstractSynthesisCallback {...@Overridepublic int start(int sampleRateInHz, int audioFormat, int channelCount) {mDispatcher.dispatchOnBeginSynthesis(sampleRateInHz, audioFormat, channelCount);int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);synchronized (mStateLock) {...SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(mAudioParams, sampleRateInHz, audioFormat, channelCount,mDispatcher, mCallerIdentity, mLogger);mAudioTrackHandler.enqueue(item);mItem = item;}return TextToSpeech.SUCCESS;}@Overridepublic int audioAvailable(byte[] buffer, int offset, int length) {SynthesisPlaybackQueueItem item = null;synchronized (mStateLock) {...item = mItem;}final byte[] bufferCopy = new byte[length];System.arraycopy(buffer, offset, bufferCopy, 0, length);mDispatcher.dispatchOnAudioAvailable(bufferCopy);try {item.put(bufferCopy);}...return TextToSpeech.SUCCESS;}@Overridepublic int done() {int statusCode = 0;SynthesisPlaybackQueueItem item = null;synchronized (mStateLock) {...mDone = true;if (mItem == null) {if (mStatusCode == TextToSpeech.SUCCESS) {mDispatcher.dispatchOnSuccess();} else {mDispatcher.dispatchOnError(mStatusCode);}return TextToSpeech.ERROR;}item = mItem;statusCode = mStatusCode;}if (statusCode == TextToSpeech.SUCCESS) {item.done();} else {item.stop(statusCode);}return TextToSpeech.SUCCESS;}... }上述的 QueueItem 的放置 audio 数据和消费的逻辑如下,主要是 put 操作触发 Lock 接口的 take Condition 恢复执行,最后调用 AudioTrack 去播放。
// SynthesisPlaybackQueueItem.java final class SynthesisPlaybackQueueItem ... {void put(byte[] buffer) throws InterruptedException {try {mListLock.lock();long unconsumedAudioMs = 0;...mDataBufferList.add(new ListEntry(buffer));mUnconsumedBytes += buffer.length;mReadReady.signal();} finally {mListLock.unlock();}}private byte[] take() throws InterruptedException {try {mListLock.lock();while (mDataBufferList.size() == 0 && !mStopped && !mDone) {mReadReady.await();}...ListEntry entry = mDataBufferList.poll();mUnconsumedBytes -= entry.mBytes.length;mNotFull.signal();return entry.mBytes;} finally {mListLock.unlock();}}public void run() {...final UtteranceProgressDispatcher dispatcher = getDispatcher();dispatcher.dispatchOnStart();if (!mAudioTrack.init()) {dispatcher.dispatchOnError(TextToSpeech.ERROR_OUTPUT);return;}try {byte[] buffer = null;while ((buffer = take()) != null) {mAudioTrack.write(buffer);}} ...mAudioTrack.waitAndRelease();dispatchEndStatus();}void done() {try {mListLock.lock();mDone = true;mReadReady.signal();mNotFull.signal();} finally {mListLock.unlock();}} } - Engine 需要从
-
上述 PlaybackSynthesisCallback 在通知 QueueItem 的同时,会通过 UtteranceProgressDispatcher 接口将数据、结果一并发送给 Request App。
// TextToSpeechService.javainterface UtteranceProgressDispatcher {void dispatchOnStop();void dispatchOnSuccess();void dispatchOnStart();void dispatchOnError(int errorCode);void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);void dispatchOnAudioAvailable(byte[] audio);public void dispatchOnRangeStart(int start, int end, int frame);}事实上该接口的实现就是 TextToSpeechService 处理 speak 请求的 UtteranceSpeechItem 实例,其通过缓存着各
ITextToSpeechCallback接口实例的 CallbackMap 发送回调给 TTS 请求的 App。(这些 Callback 来自于 TextToSpeech 初始化时候通过 ITextToSpeechService 将 Binder 接口传递来和缓存起来的。)private abstract class UtteranceSpeechItem extends SpeechItemimplements UtteranceProgressDispatcher {...@Overridepublic void dispatchOnStart() {final String utteranceId = getUtteranceId();if (utteranceId != null) {mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);}}@Overridepublic void dispatchOnAudioAvailable(byte[] audio) {final String utteranceId = getUtteranceId();if (utteranceId != null) {mCallbacks.dispatchOnAudioAvailable(getCallerIdentity(), utteranceId, audio);}}@Overridepublic void dispatchOnSuccess() {final String utteranceId = getUtteranceId();if (utteranceId != null) {mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);}}@Overridepublic void dispatchOnStop() { ... }@Overridepublic void dispatchOnError(int errorCode) { ... }@Overridepublic void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount) { ... }@Overridepublic void dispatchOnRangeStart(int start, int end, int frame) { ... }}private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {...public void dispatchOnStart(Object callerIdentity, String utteranceId) {ITextToSpeechCallback cb = getCallbackFor(callerIdentity);if (cb == null) return;try {cb.onStart(utteranceId);} ...}public void dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer) {ITextToSpeechCallback cb = getCallbackFor(callerIdentity);if (cb == null) return;try {cb.onAudioAvailable(utteranceId, buffer);} ...}public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {ITextToSpeechCallback cb = getCallbackFor(callerIdentity);if (cb == null) return;try {cb.onSuccess(utteranceId);} ...}...} -
ITextToSpeechCallback 的执行将通过 TextToSpeech 的中转抵达请求 App 的 Callback,以执行“TextToSpeech 调用”章节提到的进一步操作
// TextToSpeech.java public class TextToSpeech {...private abstract class Connection implements ServiceConnection {...private final ITextToSpeechCallback.Stub mCallback =new ITextToSpeechCallback.Stub() {@Overridepublic void onStart(String utteranceId) {UtteranceProgressListener listener = mUtteranceProgressListener;if (listener != null) {listener.onStart(utteranceId);}}...};} }// TTSTest.kt class TTSTest(context: Context) {init {tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {override fun onStart(utteranceId: String?) { ... }override fun onDone(utteranceId: String?) { ... }override fun onStop(utteranceId: String?, interrupted: Boolean) { ... }override fun onError(utteranceId: String?) { ... }})}.... }
5 使用和实现上的注意
对于 TTS 请求方有几点使用上的建议:
- TTS 播报前记得申请对应 type 的音频焦点
- TTS Request App 的 Activity 或 Service 生命周期销毁的时候,比如 onDestroy() 等时候,需要调用 TextToSpeech 的
shutdown()释放连接、资源 - 可以通过 addSpeech() 指定固定文本的对应 audio 资源(比如说语音里常用的几套唤醒后的欢迎词 audio),在后续的文本请求时直接播放该 audio,免去文本转语音的过程、提高效率
对于 TTS Engine 提供方也有几点实现上的建议:
-
TTS Engine 的各实现要和 TTS 的
SynthesisCallback做好对接,要留意只能在该 callback 已经执行了 start() 并未结束的条件下调用 done()。否则 TTS 会发生如下两种错误:- Duplicate call to done()
- done() was called before start() call
-
TTS Engine 核心作用是将 text 文本合成 speech 音频数据,合成到数据之后 Engine 当然可以选择直接播报,甚至不回传音频数据。但建议将音频数据回传,交由系统 AudioTrack 播报。一来交由系统统一播报;二来 Request App 亦可以拿到音频数据进行 cache 和分析
6 结语
可以看到 Request App 不关心实现、只需通过 TextToSpeech 几个 API 便可完成 TTS 的播报操作。而且 TTS 的实现也只需要按照 TextToSpeechService 约定的框架、回调实现即可,和 App 的对接工作由系统完成。
我们再花点时间梳理下整个过程:

流程:
- TTS Request App 调用
TextToSpeech构造函数,由系统准备播报工作前的准备,比如通过Connection绑定和初始化目标的 TTS Engine - Request App 提供目标 text 并调用
speak()请求 - TextToSpeech 会检查目标 text 是否设置过本地的 audio 资源,没有的话回通过 Connection 调用
ITextToSpeechServiceAIDL 的 speak() 继续 TextToSpeechService收到后封装请求SynthesisRequest和用于回调结果的SynthesisCallback实例- 之后将两者作为参数调用核心实现
onSynthesizeText(),其将解析 Request 并进行 Speech 音频数据合成 - 此后通过 SynthesisCallback 将合成前后的关键回调告知系统,尤其是
AudioTrack播放 - 同时需要将 speak 请求的结果告知 Request App,即通过
UtteranceProgressDispatcher中转,实际上是调用ITextToSpeechCallbackAIDL - 最后通过
UtteranceProgressListener告知 TextToSpeech 初始化时设置的各回调
7 语音文章推荐
- 如何打造车载语音交互:Google Voice Interaction 给你答案
8 参考 / 源码
- OnUtteranceCompletedListener 已废弃
- TextToSpeech.java
- TextToSpeechManagerService.java
- TextToSpeechService.java
相关文章:
直面原理:5 张图彻底了解 Android TextToSpeech 机制
ChatGPT 如此火爆,但它的强悍在于 NLU(自然语言理解)、DM(对话管理)和 NLG (自然语言生成)这三块,而 Recognition 识别和 TTS 播报这两块是缺失的。假使你的 App 接入了 ChatGPT&…...
Ruby Socket 编程
Ruby提供了两个级别访问网络的服务,在底层你可以访问操作系统,它可以让你实现客户端和服务器为面向连接和无连接协议的基本套接字支持。 Ruby 统一支持应用程序的网络协议,如FTP、HTTP等。 不管是高层的还是底层的。ruby提供了一些基本类&a…...
Vue3+ElementPlus+koa2实现本地图片的上传
一、示例图二、实现过程利用Koa2书写提交图片的后台接口这个模块是我写的项目中的其中一个板块——上传图片,这个项目的后台接口主要是是使用了后端的Koa2框架,前端小伙伴想要试着自己书写一些增删改查的接口可以从这个入手,Koa2用来了解后端…...
常见漏洞之 Fastjson
数据来源 01 Fastjson相关介绍 》Fastjson概述 》Fastjson历史漏洞 02 Fastson的识别与漏洞发现 》Fastjson寻找 》Fastjson漏洞发现(利用 dnslog) 03 修复建议 建议1:使用fastjson1.2.83版本; Github地址:https:…...
绕过Nginx Host限制
目录绕过Nginx Host限制SNI第三种方法:总结绕过Nginx Host限制 SNI SNI(Server Name Indication)是 TLS 的扩展,这允许在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。 作用:用来解决一个服务器拥有…...
Visual Studio 2022 常用快捷键,记录一下别忘记~
Visual Studio 2022 常用快捷键,记录一下别忘记~ CtrlEC 注释代码 CtrlEU 取消注释代码 CtrlED 格式化全部代码 CtrlShiftA 新建类 CtrlRG 删除无效Using CtrlH 批量替换 CtrlG 跳转到指定行 CtrlEE 在交互窗口中运行选中代码(很实用) AltEnter 快速引用 …...
软件测试回顾---重点知识
软件测试重点知识回顾 8.1.1软件测试的目的是 尽可能的发现程序中的错误并不是发现所有的错误并不是证明程序是错误的也不是为了调试程序8.1.2白盒测试根据什么设置测试用例?黑盒测试根据什么设置测试用例? 白盒测试根据内部逻辑来设计的黑盒测试根据的是…...
2D图像处理:2D Shape_Base_Matching_缩放_旋转_ICP_显示ROI
文章目录 调试结果参考调试说明问题0:并行运行问题问题1:模板+Mask大小问题问题2:组合缩放和旋转问题3:可以直接将计算边缘的代码删除问题4:如何在原始图像上显示匹配到的ROI问题5:计算的原始旋转角度不需要判断,直接可以在ICP中使用问题6:绘制坐标轴问题7:绘制ROI调试…...
HTTP、HTTPS
目录 1.HTTP 1.1.概述 1.2.报文结构 1.2.1.请求报文 1.2.2.响应报文 1.3.方法 2.HTTPS 1.HTTP 1.1.概述 HTTP,超文本传输协议,WEB体系选用了该协议作为应用层协议。 1.2.报文结构 1.2.1.请求报文 HTTP的请求报文(request࿰…...
计算机网络之http03:HTTPS RSA握手解析
不同的秘钥交换算法,握手过程可能略有差别 上文对HTTPS四次握手的学习 SSL/TLS Secure Sockets Layer/Transport Layer Security 协议握手过程 四次通信:请求服务端公钥 2次 秘钥协商 2次 (1)ClientHello请求 客户端向服务端发送client…...
一款针对EF Core轻量级分表分库、读写分离的开源项目
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 在项目开发中,如果数据量比较大,比如日志记录,我们往往会采用分表分库的方案;为了提升性能,把数据库查询与更新操作分开,这时候就要采用读写…...
Linux环境变量讲解
目录 环境变量 alias命令 type命令 变量分类 Linux最主要的全局环境变量 环境变量 变量是计算机系统用于保存可变数值的数据类型 在Linux中,一般变量都是大写,命令是小写 在Linux中,变量直接使用,不需要定义(更快…...
iptables和nftables的使用
文章目录前言iptable简介iptable命令使用iptables的四表五链nftables简介nftables命令的时候nftables与iptables的区别iptables-legacy和iptables-nft实例将指定protocol:ip:port的流量转发到本地指定端口前言 本文展示了,iptables和nftable命令的使用。 # 实验环…...
中小学信息学相关编程比赛清单及报名网站汇总(C++类)
1、NOI系列比赛(CSP-J CSP-S NOIP NOI APIO CTSC IOI ISIJ等) NOI官网 NOI全国青少年信息学奥林匹克竞赛https://www.noi.cn/ 2、蓝桥杯青少年创意编程大赛 https://www.lanqiaoqingshao.cn/home 3、中国电子协会考评中心...
06Makefile
Makefile 1、Makefile简介 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂…...
【C++】模板初阶
🍅讨厌废话,直接上车 ☃️1.泛型编程 void Swap(int& left, int& right) { int temp left; left right; right temp; } void Swap(double& left, double& right) { double temp left; left right; right temp; } void Swap(char&…...
vue+nodejs考研资料分享系统vscode - Visual Studio Code
前端技术:nodejsvueelementui,视图层其实质就是vue页面,通过编写vue页面从而展示在浏览器中,编写完成的vue页面要能够和控制器类进行交互,从而使得用户在点击网页进行操作时能够正常。 Express 框架于Node运行环境的Web框架, 目 …...
LeetCode_单周赛_332
6354. 找出数组的串联值 题意 将数组首尾元素接在一起,就是串联值。 串联之后删除,如果只剩下一个元素,加上这个元素即可 双指针,从首和尾向中间移动即可 code **注意:**用 long 没看题目用了 int wa了一发 clas…...
[LeetCode周赛复盘] 第 332 场周赛20230212
[LeetCode周赛复盘] 第 332 场周赛20230212 一、本周周赛总结二、 [Easy] 6354. 找出数组的串联值1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6355. 统计公平数对的数目1. 题目描述2. 思路分析3. 代码实现四、[Medium] 6356. 子字符串异或查询1. 题目描述2. 思路分析3. 代…...
C++轻量级RPC库RpcCore
C轻量级的RPC库,可用于任何项目中,甚至单片机。 方便平台直接相互进行功能调用。 基于asio的实现 asio_net 也可用在esp32适用于ESP32/ESP8266的实现 esp_rpc 目前也有一些轻量的库,参考了protobuf(或者依赖它)&…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
