Android 9系统源码_音频管理(一)按键音效源码解析

前言

当用户点击Android智能设备的按钮的时候,如果伴随有按键音效的话,会给用户更好的交互体验。本期我们将会结合Android系统源码来具体分析一下控件是如何发出按键音效的。

一、系统加载按键音效资源

1、在TV版的Android智能设备中,我们可以通过调节设置页面的开关来控制按键音效的有无,该设置页面对应的系统源码如下所示。

packages/apps/TvSettings/Settings/src/com/android/tv/settings/device/sound/SoundFragment.java

public class SoundFragment extends PreferenceControllerFragment implementsPreference.OnPreferenceChangeListener {private AudioManager mAudioManager;private Map<Integer, Boolean> mFormats;public static SoundFragment newInstance() {return new SoundFragment();}@Overridepublic void onAttach(Context context) {mAudioManager = context.getSystemService(AudioManager.class);mFormats = mAudioManager.getSurroundFormats();super.onAttach(context);}//用户的点击行为首先触发此方法@Overridepublic boolean onPreferenceTreeClick(Preference preference) {if (TextUtils.equals(preference.getKey(), KEY_SOUND_EFFECTS)) {final TwoStatePreference soundPref = (TwoStatePreference) preference;//调用setSoundEffectsEnabled来设置按键音的开启与关闭setSoundEffectsEnabled(soundPref.isChecked());}return super.onPreferenceTreeClick(preference);}//获取按键音效是否开启public static boolean getSoundEffectsEnabled(ContentResolver contentResolver) {return Settings.System.getInt(contentResolver, Settings.System.SOUND_EFFECTS_ENABLED, 1)!= 0;}//设置是否开启按键音效private void setSoundEffectsEnabled(boolean enabled) {if (enabled) {//如果开启按键音,则调用AudioManager的loadSoundEffects方法来加载按键音效资源mAudioManager.loadSoundEffects();} else {mAudioManager.unloadSoundEffects();}Settings.System.putInt(getActivity().getContentResolver(),Settings.System.SOUND_EFFECTS_ENABLED, enabled ? 1 : 0);}}

我们在设置页面点击按键音效开关按钮,最终会触发SoundFragment的setSoundEffectsEnabled方法,该方法会判断是否开启按键音,如果开启,则调用AudioManager的loadSoundEffects方法来加载按键音效资源,反之则会调用unloadSoundEffects方法不加载音效资源。

2、AudioManager的loadSoundEffects方法如下所示。

frameworks/base/media/java/android/media/AudioManager.java

public class AudioManager {//获取AudioService的代理对象private static IAudioService getService(){if (sService != null) {return sService;}IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);sService = IAudioService.Stub.asInterface(b);return sService;}public void loadSoundEffects() {final IAudioService service = getService();try {//调用AudioService服务的loadSoundEffects方法service.loadSoundEffects();} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}public void unloadSoundEffects() {final IAudioService service = getService();try {service.unloadSoundEffects();} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
}

AudioManager的首先通过getService方法获取了音频服务AudioService的代理对象,然后调用该对象的具体方法。

3、AudioService的loadSoundEffects方法如下所示。

public class AudioService extends IAudioService.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {private AudioHandler mAudioHandler;//加载音效资源public boolean loadSoundEffects() {int attempts = 3;LoadSoundEffectReply reply = new LoadSoundEffectReply();synchronized (reply) {//调用sendMsg发送消息给mAudioHandler。sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0);while ((reply.mStatus == 1) && (attempts-- > 0)) {try {reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS);} catch (InterruptedException e) {Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded.");}}}return (reply.mStatus == 0);}//不加载音效资源public void unloadSoundEffects() {sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0);}private static void sendMsg(Handler handler, int msg,int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {if (existingMsgPolicy == SENDMSG_REPLACE) {handler.removeMessages(msg);} else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {return;}synchronized (mLastDeviceConnectMsgTime) {long time = SystemClock.uptimeMillis() + delay;if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||msg == MSG_BTA2DP_DOCK_TIMEOUT) {if (mLastDeviceConnectMsgTime >= time) {// add a little delay to make sure messages are ordered as expectedtime = mLastDeviceConnectMsgTime + 30;}mLastDeviceConnectMsgTime = time;}//调用handler的sendMessageAtTime方法handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);}}private class AudioHandler extends Handler {//加载音效private boolean onLoadSoundEffects() {...代码暂时省略...}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {...代码省略...case MSG_UNLOAD_SOUND_EFFECTS:onUnloadSoundEffects();//不加载音效break;case MSG_LOAD_SOUND_EFFECTS://加载音效boolean loaded = onLoadSoundEffects();//调用onLoadSoundEffects加载音效,并将加载结果赋值给loadedif (msg.obj != null) {LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj;synchronized (reply) {reply.mStatus = loaded ? 0 : -1;reply.notify();}}break;...代码省略...}}
}

这里我们只要分析一下AudioService的加载音效资源的loadSoundEffects方法,该方法会调用sendMsg,发送类型为MSG_UNLOAD_SOUND_EFFECTS的msg给mAudioHandler。然后会进一步触发AudioHandler的handleMessage方法,该消息最终会触发onLoadSoundEffects方法。

4、AudioHandler的onLoadSoundEffects方法如下所示。

public class AudioService extends IAudioService.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {//音效资源文件名称private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();private class AudioHandler extends Handler {//加载音效private boolean onLoadSoundEffects() {int status;synchronized (mSoundEffectsLock) {//如果系统未启动完毕直接返回if (!mSystemReady) {Log.w(TAG, "onLoadSoundEffects() called before boot complete");return false;}//如果mSoundPool不为空直接返回if (mSoundPool != null) {return true;}//加载触摸音效loadTouchSoundAssets();...代码暂时省略...}//加载按键音效资源private void loadTouchSoundAssets() {XmlResourceParser parser = null;//如果音效资源文件列表不为空直接返回if (!SOUND_EFFECT_FILES.isEmpty()) {return;}//加载按键默认的音效资源loadTouchSoundAssetDefaults();...代码省略...}private void loadTouchSoundAssetDefaults() {//在类型为List<String>的SOUND_EFFECT_FILES中添加默认的按键音效资源Effect_Tick.oggSOUND_EFFECT_FILES.add("Effect_Tick.ogg");for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) {SOUND_EFFECT_FILES_MAP[i][0] = 0;SOUND_EFFECT_FILES_MAP[i][1] = -1;}}
}

onLoadSoundEffects方法首先判断系统是否已经启动完毕,如果未启动直接返回;然后判断mSoundPool是否空,如果不为空则直接返回;
然后首先会调用一个关键方法loadTouchSoundAssets,该方法首先判断音效资源文件列表SOUND_EFFECT_FILES是否为空,不为空直接返回。如果以上判断都不成立,则会调用loadTouchSoundAssetDefaults方法加载按键默认的音效资源,该方法首先在SOUND_EFFECT_FILES的添加音效资源Effect_Tick.ogg。

5、继续往下看AudioHandler的onLoadSoundEffects方法。

public class AudioService extends IAudioService.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {//音效资源文件名称private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>();private class AudioHandler extends Handler {//加载音效private boolean onLoadSoundEffects() {int status;synchronized (mSoundEffectsLock) {if (!mSystemReady) {Log.w(TAG, "onLoadSoundEffects() called before boot complete");return false;}if (mSoundPool != null) {return true;}//加载触摸音效loadTouchSoundAssets();//创建SoundPool对象mSoundPool = new SoundPool.Builder().setMaxStreams(NUM_SOUNDPOOL_CHANNELS).setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build()).build();...代码省略...int numSamples = 0;for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) {// Do not load sample if this effect uses the MediaPlayerif (SOUND_EFFECT_FILES_MAP[effect][1] == 0) {continue;}if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) {//获取音效资源文件路径String filePath = getSoundEffectFilePath(effect);//使用SoundPool加载音效资源文件int sampleId = mSoundPool.load(filePath, 0);if (sampleId <= 0) {Log.w(TAG, "Soundpool could not load file: "+filePath);} else {SOUND_EFFECT_FILES_MAP[effect][1] = sampleId;poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId;numSamples++;}} else {SOUND_EFFECT_FILES_MAP[effect][1] =poolId[SOUND_EFFECT_FILES_MAP[effect][0]];}}...代码省略...}//获取音效资源文件路径,默认返回的音效资源文件路径为/system/media/audio/ui/Effect_Tick.oggprivate String getSoundEffectFilePath(int effectType) {//  /product + /media/audio/ui/ + Effect_Tick.oggString filePath = Environment.getProductDirectory() + SOUND_EFFECTS_PATH+ SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);if (!new File(filePath).isFile()) {//   /system + /media/audio/ui/ + Effect_Tick.oggfilePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH+ SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]);}return filePath;}}}

onLoadSoundEffects先是调用loadTouchSoundAssets方法加载默认的音效资源文件名称,然后构建SoundPool实例对象,随后调用getSoundEffectFilePath获取按键音效资源文件路径,默认返回的音效资源文件路径为/system/media/audio/ui/Effect_Tick.ogg,并调用SoundPool加载该音效资源。

6、简单回顾一下以上步骤。
系统加载按键音效资源

二、点击控件,播放音效资源

在系统开启按键音效之后,当我们点击任意控件之后,都会触发按键音效。接下来我们将会结合View的系统源码来梳理该流程。

1、当我们点击一个控件的时候,首先会触发该View的performClick方法。

frameworks/base/core/java/android/view/View.java

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {public boolean performClick() {// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);//播放按键点击音效li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result;}public void playSoundEffect(int soundConstant) {//判断mAttachInfo.mRootCallbacks是否为空,以及系统是否开启了按键音效if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {return;}//调用mAttachInfo.mRootCallbacks的playSoundEffect方法mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);}}

View的performClick方法会调用playSoundEffect方法,playSoundEffect方法首先判断mAttachInfo.mRootCallbacks是否为空,以及系统是否开启了按键音效,然后调用mAttachInfo.mRootCallbacks的playSoundEffect方法。我们知道WindowManager在将View添加到窗口的过程中,都需要用到ViewRootImpl这个类,具体请参考Android 9.0系统源码_窗口管理(二)WindowManager对窗口的管理过程这篇文章。

2、mAttachInfo最初是在ViewRootImpl的构造方法中被创建的。

frameworks/base/core/java/android/view/ViewRootImpl.java

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {public ViewRootImpl(Context context, Display display) {mContext = context;mWindowSession = WindowManagerGlobal.getWindowSession();mDisplay = display;mBasePackageName = context.getBasePackageName();mThread = Thread.currentThread();mLocation = new WindowLeaked(null);mLocation.fillInStackTrace();mWidth = -1;mHeight = -1;mDirty = new Rect();mTempRect = new Rect();mVisRect = new Rect();mWinFrame = new Rect();mWindow = new W(this);mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;mViewVisibility = View.GONE;mTransparentRegion = new Region();mPreviousTransparentRegion = new Region();mFirst = true; // true for the first time the view is addedmAdded = false;//创建AttachInfo对象,倒数第二个参数就是View的playSoundEffect方法所用到的回调对象mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);...代码省略...}
}

3、看完ViewRootImpl的构造方法,再来看下AttachInfo的构造方法。

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {final static class AttachInfo {//关键回调接口interface Callbacks {//播放音效void playSoundEffect(int effectId);boolean performHapticFeedback(int effectId, boolean always);}final Callbacks mRootCallbacks;AttachInfo(IWindowSession session, IWindow window, Display display,ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,Context context) {mSession = session;mWindow = window;mWindowToken = window.asBinder();mDisplay = display;mViewRootImpl = viewRootImpl;mHandler = handler;mRootCallbacks = effectPlayer;//View的playSoundEffect方法所用到的回调对象就是这个mTreeObserver = new ViewTreeObserver(context);}}}

AttachInfo构造方法的最后一个参数很关键,因为View的playSoundEffect方法所调用的对象就是这个,结合ViewRootImpl的代码我们可以知道是ViewRootImpl实现了这个回调。

4、ViewRootImpl的playSoundEffect方法如下所示。

public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {public void playSoundEffect(int effectId) {checkThread();//检测是否是UI线程try {final AudioManager audioManager = getAudioManager();switch (effectId) {case SoundEffectConstants.CLICK://播放按键点击音效audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);return;case SoundEffectConstants.NAVIGATION_DOWN:audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);return;case SoundEffectConstants.NAVIGATION_LEFT:audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);return;case SoundEffectConstants.NAVIGATION_RIGHT:audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);return;case SoundEffectConstants.NAVIGATION_UP:audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);return;default:throw new IllegalArgumentException("unknown effect id " + effectId +" not defined in " + SoundEffectConstants.class.getCanonicalName());}} catch (IllegalStateException e) {// Exception thrown by getAudioManager() when mView is nullLog.e(mTag, "FATAL EXCEPTION when attempting to play sound effect: " + e);e.printStackTrace();}}
}

ViewRootImpl的playSoundEffect方法首先会检测一下当前线程是不是UI线程,然后会根据传入的effectId类型来判断要播放那种音效。因为View的performClick方法传入的是SoundEffectConstants.CLICK,所以会触发audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK)。

4、AudioManager的playSoundEffect方法如下所示。

public class AudioManager {public void  playSoundEffect(int effectType) {//检测音效类型是否合规if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {return;}//确定音效是否可用if (!querySoundEffectsEnabled(Process.myUserHandle().getIdentifier())) {return;}//获取AudioService服务final IAudioService service = getService();try {//调用服务的playSoundEffect方法service.playSoundEffect(effectType);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}/*** Settings has an in memory cache, so this is fast.*/private boolean querySoundEffectsEnabled(int user) {return Settings.System.getIntForUser(getContext().getContentResolver(),Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;}
}

AudioManager的playSoundEffect会做一些校验,如果校验通过则会获取AudioService服务对象,并调用该对象的playSoundEffect方法进行音效播放。

5、AudioService和playSoundEffect相关的代码如下所示。

public class AudioService extends IAudioService.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {/*** 播放音效* @param effectType*/public void playSoundEffect(int effectType) {playSoundEffectVolume(effectType, -1.0f);}public void playSoundEffectVolume(int effectType, float volume) {// do not try to play the sound effect if the system stream is mutedif (isStreamMutedByRingerOrZenMode(STREAM_SYSTEM)) {return;}if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) {Log.w(TAG, "AudioService effectType value " + effectType + " out of range");return;}//发送播放音效的消息给mAudioHandlersendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE,effectType, (int) (volume * 1000), null, 0);}private class AudioHandler extends Handler {//加载音效private boolean onLoadSoundEffects() {...代码暂时省略...}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {...代码省略...case MSG_UNLOAD_SOUND_EFFECTS:onUnloadSoundEffects();//不加载音效break;case MSG_LOAD_SOUND_EFFECTS://加载音效boolean loaded = onLoadSoundEffects();//调用onLoadSoundEffects加载音效,并将加载结果赋值给loaded...代码省略...break;case MSG_PLAY_SOUND_EFFECT://播放音效onPlaySoundEffect(msg.arg1, msg.arg2);break;...代码省略...}}
}

AudioService的playSoundEffect方法进一步调用playSoundEffectVolume,该方法会发送播放音效的消息MSG_PLAY_SOUND_EFFECT给mAudioHandler,最终会触发onPlaySoundEffect方法。

6、AudioService的onPlaySoundEffec方法如下所示。

public class AudioService extends IAudioService.Stubimplements AccessibilityManager.TouchExplorationStateChangeListener,AccessibilityManager.AccessibilityServicesStateChangeListener {private void onPlaySoundEffect(int effectType, int volume) {synchronized (mSoundEffectsLock) {onLoadSoundEffects();//加载音效if (mSoundPool == null) {return;}float volFloat;// use default if volume is not specified by callerif (volume < 0) {volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20);} else {volFloat = volume / 1000.0f;}if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) {//通过SoundPool播放音效mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1],volFloat, volFloat, 0, 0, 1.0f);} else {//通过MediaPlayer播放音效MediaPlayer mediaPlayer = new MediaPlayer();try {String filePath = getSoundEffectFilePath(effectType);mediaPlayer.setDataSource(filePath);mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM);mediaPlayer.prepare();mediaPlayer.setVolume(volFloat);mediaPlayer.setOnCompletionListener(new OnCompletionListener() {public void onCompletion(MediaPlayer mp) {cleanupPlayer(mp);}});mediaPlayer.setOnErrorListener(new OnErrorListener() {public boolean onError(MediaPlayer mp, int what, int extra) {cleanupPlayer(mp);return true;}});mediaPlayer.start();} catch (IOException ex) {Log.w(TAG, "MediaPlayer IOException: "+ex);} catch (IllegalArgumentException ex) {Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex);} catch (IllegalStateException ex) {Log.w(TAG, "MediaPlayer IllegalStateException: "+ex);}}}}}

7、简单回顾一下以上步骤。
点击控件,播放音效资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/18122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux - gcc/g++工具使用

gcc/g是用于编译C/C程序的编译器 1.编译过程 1. 预处理&#xff08;头文件展开&#xff0c;条件编译&#xff0c;进行宏替换&#xff0c;去注释等) 2. 编译&#xff08;C语言汇编语言) 3. 汇编&#xff08;汇编->可重定位目标二进制文件&#xff0c;不可以被执行的&#xff…

【LLM系列之指令微调】长话短说大模型指令微调的“Prompt”

1 指令微调数据集形式“花样”太多 大家有没有分析过 prompt对模型训练或者推理的影响&#xff1f;之前推理的时候&#xff0c;发现不加训练的时候prompt&#xff0c;直接输入模型性能会变差的&#xff0c;这个倒是可以理解。假如不加prompt直接训练&#xff0c;是不是测试的时…

kafka集群搭建(Linux环境)

zookeeper搭建&#xff0c;可以搭建集群&#xff0c;也可以单机&#xff08;本地学习&#xff0c;没必要搭建zookeeper集群&#xff0c;单机完全够用了&#xff0c;主要学习的是kafka&#xff09; 1. 首先官网下载zookeeper&#xff1a;Apache ZooKeeper 2. 下载好之后上传到…

IP 监控软件

IP 监控软件可帮助管理员主动监控网络资源。随着各种设备连接到网络&#xff0c;监控设备和接口可能很复杂&#xff0c;为管理员提供这些设备的IP监控&#xff0c;了解其各种性能指标和问题。 使用有效的 IP 监控软件的优势 使用有效的 IP 监控系统和一套全面的 IP 监控工具&…

qt子进程和父进程读写数据通信

进程A&#xff08;例如主程序&#xff09;创建了一个QProcess B&#xff0c;这个B就称为A的子进程&#xff0c;而A称为B的父进程。 这也称为进程间通信&#xff0c;有多种方式&#xff1a; TCP/IPLocal Server/Socket共享内存D-Bus &#xff08;Unix库&#xff09;QProcess会…

Moonbeam:开发者的多链教科书

了解波卡的技术架构&#xff0c;只需掌握3个关键词&#xff1a; Relay Chain&#xff08;中继链&#xff09;&#xff1a;Polkadot将自身视作多核计算机&#xff0c;承载区块链底层安全架构的辐射中心。Parachain&#xff08;平行链&#xff09;&#xff1a;在“Layer 0”架构…

Postman如何导出接口的几种方法

本文主要介绍了Postman如何导出接口的几种方法&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;具有一定的参考价值&#xff0c;感兴趣的小伙伴们可以参考一下 前言&#xff1a; 我的文章还是一贯的作风&#xff0c;简确用风格&#xff08;简单确实有用&#xff09;&am…

【从零开始学习JAVA | 三十四篇】IO流

目录 前言&#xff1a; IO流介绍&#xff1a; IO流的常见方法&#xff1a; 1.字节流类&#xff1a; 2.字符流类&#xff1a; 总结&#xff1a; 前言&#xff1a; IO流就是存入和读取数据的解决方案&#xff0c;并且他是一个知识点很多的章节&#xff0c;因此我们关于IO流…

Jmeter —— jmeter接口自动化测试操作流程

在企业使用jmeter开展实际的接口自动化测试工具&#xff0c;建议按如下操作流程&#xff0c; 可以使整个接口测试过程更规范&#xff0c;更有效。 接口自动化的流程&#xff1a; 1、获取到接口文档&#xff1a;swagger、word、excel ... 2、熟悉接口文档然后设计测试用例&am…

ES开启身份认证

文章目录 X-Pack简介之前的安全方案ES开启认证ES服务升级https协议开启集群节点之间的证书认证 X-Pack简介 X-Pack是Elastic Stack扩展功能&#xff0c;提供安全性&#xff0c;警报&#xff0c;监视&#xff0c;报告&#xff0c;机器学习和许多其他功能。 X-Pack的发展演变&am…

Pytest学习教程_测试报告生成pytest-html(三)

前言 pytest-html 是一个用于生成漂亮的 HTML 测试报告的 pytest 插件。它可以方便地将 pytest 运行的测试结果转换为易于阅读和理解的 HTML 报告&#xff0c;提供了丰富的测试结果展示功能和交互性。 一、安装 # 版本查看命令 pytest版本&#xff1a; pytest --version pyte…

VGN N75pro说明书

VGN N75pro说明书 1、封面和最后一页 2、第01、02、03 3、第04 4、第05

面试总结-Redis篇章(十)——Redis哨兵模式、集群脑裂

Redis哨兵模式、集群脑裂 哨兵模式哨兵的作用服务状态监控 Redis集群&#xff08;哨兵模式&#xff09;脑裂解决办法 哨兵模式 为了保证Redis的高可用&#xff0c;Redis提供了哨兵模式 哨兵的作用 服务状态监控 Redis集群&#xff08;哨兵模式&#xff09;脑裂 假设由于网络原…

344.翻转字符串+387.字符串中的第一个唯一字符

目录 一、翻转字符串 二、字符串中的第一个唯一字符 一、翻转字符串 344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void reverseString(vector<char>& s) {int start0;int end s.size()-1;while(start < end){swap(s[sta…

3ds MAX 绘制喷泉

首先绘制一个小圆柱体当做喷头&#xff1a; 在粒子系统内选择【超级喷射】&#xff0c;并设置对应的参数&#xff1a; 轴和平面的扩散代表了我们看到的水柱能占据多大的面积 另外比较重要的参数是粒子运动和粒子计时 前者是粒子移动的时间也就是喷射的速度 后者代表了喷射出的…

构建语言模型:BERT 分步实施指南

学习目标 了解 BERT 的架构和组件。了解 BERT 输入所需的预处理步骤以及如何处理不同的输入序列长度。获得使用 TensorFlow 或 PyTorch 等流行机器学习框架实施 BERT 的实践知识。了解如何针对特定下游任务(例如文本分类或命名实体识别)微调 BERT。为什么我们需要 BERT? 正…

【VTK】基于读取出来的 STL 模型,当用户点击鼠标左键时,程序将获取点击位置的点,显示其坐标,并设置它为模型的旋转原点

知识不是单独的&#xff0c;一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏&#xff1a;Visual Studio。 文章目录 class PointPickedSignal : public QObjectclass MouseInteractorCommand : public vtkCommandvoid A::on_pushButtonSelected_clicked()void A::on…

vue3搭建(vite+create-vue)

目录 前提条件 输入命令 对于Add an End-to-End Testing Solution nightwatch和Cypress 和 Playwright 运行 前提条件 熟悉命令行已安装 16.0 或更高版本的 Node.js &#xff08;node -v查看版本&#xff09; 输入命令 npm init vuelatest 这一指令将会安装并执行 create-…

Meta AI研究团队新AI模型: Llama 2 大语言模型

Llama是Facebook Research团队开发的基础语言模型集&#xff0c;旨在提供广泛的语言理解能力。它基于转换器架构&#xff0c;参数范围从7B到65B。通过使用Llama模型&#xff0c;研究人员和开发人员可以构建更先进的自然语言处理系统。您可以在GitHub上找到相关的代码和资源&…

kotlin 编写一个简单的天气预报app(四)增加界面显示

编写界面来显示返回的数据 用户友好性&#xff1a;通过界面设计和用户体验优化&#xff0c;可以使天气信息更易读、易理解和易操作。有效的界面设计可以提高用户满意度并提供更好的交互体验。 增加城市名字的TextView <TextViewandroid:id"id/textViewCityName"…