Android13音频录制适配
Android13音频录制适配
前言:
之前写过一篇音频录制的文章,当时是在Android10以下的手机可以成功录制和播放,但是Android10及以上手机提示创建文件失败,最近做过Android13的适配,索性一起把之前的录音也适配了,记录一下适配的过程。
1.Manifest添加Android13文件读写适配:
<!--存储图像或者视频权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" android:maxSdkVersion="32"/><!--录制音频权限-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
2.Android13文件读写权限请求:
private void requestBasicPermission() {final RxPermissions rxPermissions = new RxPermissions(this);StringBuilder rationaleSb = new StringBuilder();StringBuilder deniedSb = new StringBuilder();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {permissions = new String[]{Manifest.permission.READ_MEDIA_AUDIO,Manifest.permission.READ_MEDIA_VIDEO,Manifest.permission.READ_MEDIA_IMAGES,Manifest.permission.RECORD_AUDIO,};} else {permissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE,};}rxPermissions.requestEach(permissions).subscribe(new Observer<Permission>() {@Overridepublic void onSubscribe(@NonNull Disposable d) {}@Overridepublic void onNext(@NonNull Permission permission) {if (permission.granted) {// 用户已经同意该权限Log.d(TAG, "权限:" + permission.name + " 已开启");} else if (permission.shouldShowRequestPermissionRationale) {// 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时。还会提示请求权限的对话框Log.d(TAG, "权限:" + permission.name + " 权限拒绝,但没有选中 不再询问");if (rationaleSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {return;}rationaleSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));rationaleSb.append("、");} else {// 用户拒绝了该权限,而且选中『不再询问』Log.d(TAG, "权限:" + permission.name + " 权限拒绝,并且选中 不再询问");if (deniedSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {return;}deniedSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));deniedSb.append("、");}}@Overridepublic void onError(@NonNull Throwable e) {Log.d(TAG, "permission onError");}@Overridepublic void onComplete() {if (TextUtils.isEmpty(rationaleSb) && TextUtils.isEmpty(deniedSb)) {Log.d(TAG, "permission.name ,权限已经允许");startAudioRecord();} else {if (!TextUtils.isEmpty(deniedSb)) {showTipDialog(deniedSb, 0);} else if (!TextUtils.isEmpty(rationaleSb)) {showTipDialog(rationaleSb, 1);}}}});
}
3.权限请求弹框:
private void showPermissionDialog(StringBuilder permissionName, int permissionType) {if (null != mPermissionDialog && mPermissionDialog.isShowing()) {mPermissionDialog.dismiss();mPermissionDialog = null;}if (0 == permissionType) {mPermissionDialog = new PermissionDialog(MainActivity.this,"请授权相关权限以确保相关功能能正常运行:" + permissionName.toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,"确定", "知道了",null, this::startAudioRecord);mPermissionDialog.show();} else if (1 == permissionType) {mPermissionDialog = new PermissionDialog(MainActivity.this,"请授权相关权限以确保相关功能能正常运行:" + permissionName.toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,"取消", "知道了",null, this::startAudioRecord);mPermissionDialog.show();}}
4.完整的权限请求dialog类:
package com.example.audiorecorddemo.dialog;import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.widget.TextView;import com.example.audiorecorddemo.R;/*** 权限请求弹框*/
public class PermissionDialog extends Dialog implements View.OnClickListener {private TextView mTvTitle, mTvTip, mTvLeftBtn, mTvRightBtn;private View mLineTip;private Window mWindow;private String mTileStr, mTipStr;private int mBtnSelectType;private String mLeftStr, mRightStr;private DialogTipLeftClickListener mLeftClickListener;private DialogTipRightClickListener mRightClickListener;// 左右button显示public static final int BUTTON_BOTH_FLAG = 1;// 仅左button显示public static final int BUTTON_LEFT_FLAG = 2;// 仅右button显示public static final int BUTTON_RIGHT_FLAG = 3;private boolean needShowCheck = false;public PermissionDialog(Context context, String titleStr, String tipStr, int buttonType, String leftStr,String rightStr, DialogTipLeftClickListener leftClickListener,DialogTipRightClickListener rightClickListener) {super(context);mWindow = getWindow();mTileStr = titleStr;mTipStr = tipStr;mBtnSelectType = buttonType;mLeftStr = leftStr;mRightStr = rightStr;mLeftClickListener = leftClickListener;mRightClickListener = rightClickListener;}public PermissionDialog(Context context, String tipStr, int buttonType, String leftStr,String rightStr, DialogTipLeftClickListener leftClickListener,DialogTipRightClickListener rightClickListener) {this(context, "", tipStr, buttonType, leftStr, rightStr, leftClickListener, rightClickListener);}public PermissionDialog(Context context, String tipStr, String leftStr, String rightStr,DialogTipLeftClickListener leftClickListener,DialogTipRightClickListener rightClickListener) {this(context, "", tipStr, BUTTON_BOTH_FLAG, leftStr, rightStr, leftClickListener, rightClickListener);}public PermissionDialog(Context context, String tipStr, String rightStr,DialogTipLeftClickListener leftClickListener,DialogTipRightClickListener rightClickListener) {this(context, "", tipStr, BUTTON_BOTH_FLAG, "", rightStr, leftClickListener, rightClickListener);}public PermissionDialog(Context context, String tipStr, DialogTipLeftClickListener leftClickListener,DialogTipRightClickListener rightClickListener) {this(context, "", tipStr, BUTTON_BOTH_FLAG, "", "", leftClickListener, rightClickListener);}public PermissionDialog(Context context, String titleStr, String tipStr, int buttonType, String leftStr,String rightStr, DialogTipLeftClickListener leftClickListener,DialogTipRightClickListener rightClickListener, boolean needShowCheck) {this(context, titleStr, tipStr, buttonType, leftStr, rightStr, leftClickListener, rightClickListener);this.needShowCheck = needShowCheck;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_permiss_dialog);setCancelable(false);initView();}private void initView() {mTvTitle = findViewById(R.id.tv_dialog_corner_title);mTvTip = findViewById(R.id.tv_dialog_corner_tip);mLineTip = findViewById(R.id.view_dialog_corner_line);mTvLeftBtn = findViewById(R.id.tv_dialog_corner_left);mTvRightBtn = findViewById(R.id.tv_dialog_corner_right);// 初始化监听initListener();// 初始化数据initData();}private void initData() {// titleif (!TextUtils.isEmpty(mTileStr)) {mTvTitle.setText(mTileStr);mTvTitle.setVisibility(View.VISIBLE);} else {mTvTitle.setVisibility(View.GONE);}// 提示if (!TextUtils.isEmpty(mTipStr)) {mTvTip.setText(mTipStr);mTvTip.setVisibility(View.VISIBLE);} else {mTvTip.setVisibility(View.GONE);}// 左边按钮if (!TextUtils.isEmpty(mLeftStr)) {mTvLeftBtn.setText(mLeftStr);}// 右边按钮if (!TextUtils.isEmpty(mRightStr)) {mTvRightBtn.setText(mRightStr);}// 按钮状态setButtonSelect(mBtnSelectType);}private void initListener() {mTvLeftBtn.setOnClickListener(this);mTvRightBtn.setOnClickListener(this);}private void setButtonSelect(int selectType) {if (mTvLeftBtn == null || mTvRightBtn == null || mLineTip == null) {return;}switch (selectType) {case BUTTON_LEFT_FLAG:mTvLeftBtn.setVisibility(View.VISIBLE);mTvRightBtn.setVisibility(View.GONE);mLineTip.setVisibility(View.GONE);break;case BUTTON_RIGHT_FLAG:mTvLeftBtn.setVisibility(View.GONE);mTvRightBtn.setVisibility(View.VISIBLE);mLineTip.setVisibility(View.GONE);break;case BUTTON_BOTH_FLAG:default:mTvLeftBtn.setVisibility(View.VISIBLE);mTvRightBtn.setVisibility(View.VISIBLE);mLineTip.setVisibility(View.VISIBLE);break;}}@Overridepublic void onClick(View view) {int viewId = view.getId();if (R.id.tv_dialog_corner_left == viewId) {if (mLeftClickListener != null) {mLeftClickListener.onTipLeftClick();}if (isShowing()) {dismiss();}} else if (R.id.tv_dialog_corner_right == viewId) {if (mRightClickListener != null) {mRightClickListener.onTipRightClick();}if (isShowing()) {dismiss();}}}public interface DialogTipLeftClickListener {void onTipLeftClick();}public interface DialogTipRightClickListener {void onTipRightClick();}
}
5.文件管理类:
package com.example.audiorecorddemo.utils;import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;import androidx.annotation.NonNull;import com.example.audiorecorddemo.app.MyApp;import java.io.File;/*** @author: njb* @date: 2023/8/15 17:13* @desc:*/
public class FileManager {// 媒体模块根目录private static final String SAVE_MEDIA_ROOT_DIR = Environment.DIRECTORY_DCIM;// 媒体模块存储路径private static final String SAVE_MEDIA_DIR = SAVE_MEDIA_ROOT_DIR + "/RecordManager";private static final String SAVE_MEDIA_PHOTO_DIR = SAVE_MEDIA_DIR + "/photo";private static final String SAVE_MEDIA_AUDIO_DIR = SAVE_MEDIA_DIR + "/audio";private static final String SAVE_MEDIA_VIDEO_DIR = SAVE_MEDIA_DIR + "/video";// JPG后缀public static final String JPG_SUFFIX = ".jpg";// PNG后缀public static final String PNG_SUFFIX = ".png";// MP4后缀public static final String MP4_SUFFIX = ".mp4";/*** 保存图片到系统相册** @param context* @param file*/public static String saveImage(Context context, File file) {ContentResolver localContentResolver = context.getContentResolver();ContentValues localContentValues = getImageContentValues(context, file, System.currentTimeMillis());localContentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, localContentValues);Intent localIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");final Uri localUri = Uri.fromFile(file);localIntent.setData(localUri);context.sendBroadcast(localIntent);return file.getAbsolutePath();}public static ContentValues getImageContentValues(Context paramContext, File paramFile, long paramLong) {ContentValues localContentValues = new ContentValues();localContentValues.put("title", paramFile.getName());localContentValues.put("_display_name", paramFile.getName());localContentValues.put("mime_type", "image/jpeg");localContentValues.put("datetaken", Long.valueOf(paramLong));localContentValues.put("date_modified", Long.valueOf(paramLong));localContentValues.put("date_added", Long.valueOf(paramLong));localContentValues.put("orientation", Integer.valueOf(0));localContentValues.put("_data", paramFile.getAbsolutePath());localContentValues.put("_size", Long.valueOf(paramFile.length()));return localContentValues;}/*** 获取App存储根目录*/public static String getAppRootDir() {String path = getStorageRootDir();FileUtil.createOrExistsDir(path);return path;}/*** 获取文件存储根目录*/public static String getStorageRootDir() {File filePath = MyApp.getInstance().getExternalFilesDir("");String path;if (filePath != null) {path = filePath.getAbsolutePath();} else {path = MyApp.getInstance().getFilesDir().getAbsolutePath();}return path;}/*** 图片地址*/public static String getCameraPhotoPath() {return getFolderDirPath(SAVE_MEDIA_PHOTO_DIR);}/*** 视频地址*/public static String getCameraVideoPath() {return getFolderDirPath(SAVE_MEDIA_VIDEO_DIR);}public static String getCameraAudioPath() {return getSaveDir(SAVE_MEDIA_AUDIO_DIR);}public static String getFolderDirPath(String dstDirPathToCreate) {File dstFileDir = new File(Environment.getExternalStorageDirectory(), dstDirPathToCreate);if (!dstFileDir.exists() && !dstFileDir.mkdirs()) {Log.e("Failed to create file", dstDirPathToCreate);return null;}return dstFileDir.getAbsolutePath();}/*** 获取具体模块存储目录*/public static String getSaveDir(@NonNull String directory) {String path = "";if (TextUtils.isEmpty(directory) || "/".equals(directory)) {path = "";} else if (directory.startsWith("/")) {path = directory;} else {path = "/" + directory;}path = getAppRootDir() + path;FileUtil.createOrExistsDir(path);return path;}
}
6.录音方法:
主要就是文件的生成和创建,由于Android10以后不能随意创建私有文件,所以生成的audio文件放到系统的DCIM、MUSIC、Download目录下,并且是项目自己包名下:
public void startRecord(WeakReference<Context> weakReference) {this.weakReference = weakReference;LogUtils.e(TAG, "开始录音");//生成PCM文件String fileName = DateFormat.format("yyyy-MMdd-HHmmss", Calendar.getInstance(Locale.getDefault())) + ".pcm";File file = new File(FileManager.getCameraAudioPath(), "/ACC音频/");if (!file.exists()) {file.mkdir();}String audioSaveDir = file.getAbsolutePath();LogUtils.e(TAG, audioSaveDir);recordFile = new File(audioSaveDir, fileName);LogUtils.e(TAG, "生成文件" + recordFile);//如果存在,就先删除再创建if (recordFile.exists()) {recordFile.delete();LogUtils.e(TAG, "删除文件");}try {recordFile.createNewFile();LogUtils.e(TAG, "创建文件");} catch (IOException e) {LogUtils.e(TAG, "未能创建");throw new IllegalStateException("未能创建" + recordFile.toString());}if (filePathList.size() == 2) {filePathList.clear();}filePathList.add(recordFile);try {//输出流OutputStream os = new FileOutputStream(recordFile);BufferedOutputStream bos = new BufferedOutputStream(os);DataOutputStream dos = new DataOutputStream(bos);int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding);if (ActivityCompat.checkSelfPermission(weakReference.get(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {return;}audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, AudioFormat.CHANNEL_IN_STEREO, audioEncoding, bufferSize);short[] buffer = new short[bufferSize];audioRecord.startRecording();LogUtils.e(TAG, "开始录音");isRecording = true;while (isRecording) {int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);for (int i = 0; i < bufferReadResult; i++) {dos.writeShort(buffer[i]);}}audioRecord.stop();dos.close();} catch (Exception e) {e.printStackTrace();LogUtils.e(TAG, "录音失败");showToast("录音失败");}
}
7.启动录音:
btnRecord.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {buttonEnabled(false, true, true);startAudioRecord();}
});
8.停止录音:
btnStop.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Handler().post(new Runnable() {@Overridepublic void run() {buttonEnabled(true, true, false);mHandler.post(() -> Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_LONG).show());AudioManagerUtils.getInstance().pauseAudio();}});}
});
9.开始播放:
btnPlay.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {AudioManagerUtils.getInstance().playPcm(true);buttonEnabled(false, false, true);}
});
10.完整的测试代码:
package com.example.audiorecorddemo;import android.Manifest;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import com.blankj.utilcode.util.LogUtils;
import com.example.audiorecorddemo.dialog.PermissionDialog;
import com.example.audiorecorddemo.utils.AudioManagerUtils;
import com.example.audiorecorddemo.utils.StringUtils;
import com.tbruyelle.rxpermissions3.Permission;
import com.tbruyelle.rxpermissions3.RxPermissions;import java.lang.ref.WeakReference;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;public class MainActivity extends AppCompatActivity {private Button btnRecord, btnPlay, btnStop;ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5,1, TimeUnit.MINUTES,new LinkedBlockingDeque<>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());private String[] permissions = null;private PermissionDialog mPermissionDialog = null;/*** 被用户拒绝的权限列表*/private static final int MY_PERMISSIONS_REQUEST = 1001;private final String TAG = MainActivity.this.getClass().getSimpleName();private Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);requestBasicPermission();initView();}private void requestBasicPermission() {final RxPermissions rxPermissions = new RxPermissions(this);StringBuilder rationaleSb = new StringBuilder();StringBuilder deniedSb = new StringBuilder();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {permissions = new String[]{Manifest.permission.READ_MEDIA_AUDIO,Manifest.permission.READ_MEDIA_VIDEO,Manifest.permission.READ_MEDIA_IMAGES,Manifest.permission.RECORD_AUDIO,};} else {permissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE,};}rxPermissions.requestEach(permissions).subscribe(new Observer<Permission>() {@Overridepublic void onSubscribe(@NonNull Disposable d) {}@Overridepublic void onNext(@NonNull Permission permission) {if (permission.granted) {LogUtils.d(TAG, "权限:" + permission.name + " 已开启");} else if (permission.shouldShowRequestPermissionRationale) {LogUtils.d(TAG, "权限:" + permission.name + " 权限拒绝,但没有选中 不再询问");if (rationaleSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {return;}rationaleSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));rationaleSb.append("、");} else {LogUtils.d(TAG, "权限:" + permission.name + " 权限拒绝,并且选中 不再询问");if (deniedSb.toString().contains(StringUtils.getPermissionName(MainActivity.this,permission.name))) {return;}deniedSb.append(StringUtils.getPermissionName(MainActivity.this,permission.name));deniedSb.append("、");}}@Overridepublic void onError(@NonNull Throwable e) {LogUtils.d(TAG, "permission onError");}@Overridepublic void onComplete() {if (TextUtils.isEmpty(rationaleSb) && TextUtils.isEmpty(deniedSb)) {LogUtils.d(TAG, "permission.name ,权限已经允许");startAudioRecord();} else {if (!TextUtils.isEmpty(deniedSb)) {showPermissionDialog(deniedSb, 0);} else if (!TextUtils.isEmpty(rationaleSb)) {showPermissionDialog(rationaleSb, 1);}}}});}private void showPermissionDialog(StringBuilder permissionName, int permissionType) {if (null != mPermissionDialog && mPermissionDialog.isShowing()) {mPermissionDialog.dismiss();mPermissionDialog = null;}if (0 == permissionType) {mPermissionDialog = new PermissionDialog(MainActivity.this,"请授权相关权限以确保相关功能能正常运行:" + permissionName.toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,"确定", "知道了",null, this::startAudioRecord);mPermissionDialog.show();} else if (1 == permissionType) {mPermissionDialog = new PermissionDialog(MainActivity.this,"请授权相关权限以确保相关功能能正常运行:" + permissionName.toString().substring(0, permissionName.length() - 1), PermissionDialog.BUTTON_RIGHT_FLAG,"取消", "知道了",null, this::startAudioRecord);mPermissionDialog.show();}}private void initView() {btnRecord = findViewById(R.id.btn_record);btnPlay = findViewById(R.id.btn_play);btnStop = findViewById(R.id.btn_stop);btnRecord.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {buttonEnabled(false, true, true);startAudioRecord();}});btnPlay.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {AudioManagerUtils.getInstance().playPcm(true);buttonEnabled(false, false, true);}});btnStop.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Handler().post(new Runnable() {@Overridepublic void run() {buttonEnabled(true, true, false);mHandler.post(() -> Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_LONG).show());AudioManagerUtils.getInstance().pauseAudio();}});}});}private void startAudioRecord() {threadPoolExecutor.execute(() -> {mHandler.post(() -> Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_LONG).show());AudioManagerUtils.getInstance().startRecord(new WeakReference<>(getApplicationContext()));});}private void buttonEnabled(boolean record, boolean play, boolean stop) {btnRecord.setEnabled(record);btnPlay.setEnabled(play);btnStop.setEnabled(stop);}@Overrideprotected void onStop() {super.onStop();AudioManagerUtils.getInstance().pauseAudio();}@Overrideprotected void onDestroy() {super.onDestroy();AudioManagerUtils.getInstance().releaseAudio();}}
11.实现的效果如下:
12.总结:
- 以上就是今天的内容,录制音频时适配Android13.
- Android13文件读写细分为三个权限 READ_MEDIA_AUDIO、READ_MEDIA_VIDEO、READ_MEDIA_IMAGES.
- Android10以上文件创建和生成需要在公共目录,不能随意创建和读写.
13.项目的源码地址:
https://gitee.com/jackning_admin/audio-record-demo-a
相关文章:

Android13音频录制适配
Android13音频录制适配 前言: 之前写过一篇音频录制的文章,当时是在Android10以下的手机可以成功录制和播放,但是Android10及以上手机提示创建文件失败,最近做过Android13的适配,索性一起把之前的录音也适配了&#…...

【Python】—— 如果使用matplotlib做数据可视化
matplotlib做数据可视化 相关知识掌握matplotlib的基本使用方法1. 折线图2. 散点图3. 柱状图4. 饼图5. 直方图6. 等高线图7. 图形定制 掌握数据处理的基本方法1. 数据筛选2. 缺失值处理3. 异常值处理 理解数据可视化的原则和方法1. 选择合适的图表类型2. 避免数据混淆3. 突出重…...
【MyBatis-Plus】多数据源分页配置(低版本暂时就支持一种(可选),高版本多支持)
【转载】一、Mybatis Plus 3.4 版本之后分页插件的变化 1、地址 Mybatis Plus 3.4版本之后分页插件的变化 2、内容 1、MybatisPlusInterceptor 从 Mybatis Plus 3.4.0 版本开始,不再使用旧版本的 PaginationInterceptor,而是使用 MybatisPlusInterce…...

Linux 特殊符号
目录 1. # 注释 2. ;命令分隔符 3. .. 上级目录 4. . 当前目录 5. " " 换行,解析变量 6. 换行,不解析变量 7. \ 和 / 8. !历史命令调用,取反 9. * 通配符 10. $ 调用变量 11. | 管道 12. || …...
TDengine 签约中船九院,助力航运业智能化转型升级
在大数据时代背景下,船舶智能化已经成为船舶制造与航运领域发展的必然趋势。智能船舶作为《中国制造 2025》中明确重点发展的领域,代表了船舶未来的方向,对于航运业的转型升级至关重要。其中,大数据的处理和运用成为船舶智能化转型…...

upload-labs笔记
简介 upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关,每一关都包含着不同上传方式。 文件上传漏洞是指: Web 服务器允许用户将文件上传至其…...

Android Studio好用的插件推荐
目录 一、插件推荐 二、如何下载 1.点击File—>Settings 2.点击Plugins然后进行搜索下载 三、Android Studio 模板 一、插件推荐 这个插件可以为您自动生成Parcelable代码。Parcelable是一种用于在Android组件之间传递自定义对象的机制,但手动编写Parcela…...
第三十九章 其他特殊主题 - 映射 IRIS ID 以供导出
文章目录 第三十九章 其他特殊主题 - 映射 IRIS ID 以供导出控制导出时的命名空间前缀XMLPREFIX 第三十九章 其他特殊主题 - 映射 IRIS ID 以供导出 当在顶层映射 IRIS 对象(而不是作为另一个对象的属性)时,其内部 ID、OID 和全局唯一 ID 不…...

文件操作(下)
标题的顺序是接着之前写的,希望这篇博客对你有帮助 七. 随机读写函数 实际上,无论是读还是写,在一次调用顺序读写函数,文件指针会移到已经读过或者写过的下一个位置,从那个位置开始下一次读和写(在文件没有…...
面试必问-vue3中ref与这个reactive的区别
ref和reactive是Vue 3中两种不同的响应式数据处理方式。 ref:ref函数可以将普通的Javascript值转换为一个响应式引用。它返回一个包含.value属性的对象,可以通过读取或修改.value来操作引用的值。当引用的值发生变化时,Vue会自动追踪依赖并更…...

网络(九)三层路由、DHCP以及VRRP协议介绍
目录 一、三层路由 1. 定义 2. 交换原理 3. 操作演示 3.1 图示 3.2 LSW1新建vlan10、20、30,分别对应123接口均为access类型,接口4为trunkl类型,允许所有vlan通过 3.3 LSW2新建vlan10、20、30,配置接口1为trunk类型&…...

深度学习 Day19——P8YOLOv5-C3模块实现
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 | 接辅导、项目定制 文章目录 前言1 我的环境2 代码实现与执行结果2.1 前期准备2.1.1 引入库2.1.2 设置GPU(如果设备上支持GPU就使用GPU,否则使用C…...

轻量封装WebGPU渲染系统示例<51>- 视差贴图(Parallax Map)(源码)
视差纹理是一种片段着色阶段增强材质表面凹凸细节的技术。 这里在WebGPU的实时渲染材质管线中实现了视差贴图计算,以便增强相关的纹理细节表现力。 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/Para…...

YOLOv8改进 | 2023主干篇 | 华为最新VanillaNet主干替换Backbone实现大幅度长点
一、本文介绍 本文给大家来的改进机制是华为最新VanillaNet网络,其是今年最新推出的主干网络,VanillaNet是一种注重极简主义和效率的神经网络架构。它的设计简单,层数较少,避免了像深度架构和自注意力这样的复杂操作(需要注意的是…...

Leetcode 376 摆动序列
题意理解: 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 如果是摆动序列,前后差值呈正负交替出现 为保证摆动序列尽可能的长,我们可以尽可能的保留峰值,,删除上下坡的中间值&…...

51单片机控制1602LCD显示屏输出自定义字符二
51单片机控制1602LCD显示屏输出自定义字符二 1.概述 1602LCD除了内置的字符外还提供自定义字符功能,当内置的字符中没有我们想要输出的字符时,我们就可以自己创造字符让他显示,下面介绍1602如何创建自定义字符。 2.1602LCD创建字符原理 自…...

HarmonyOS自学-Day2(@Builder装饰器)
目录 文章声明⭐⭐⭐让我们开始今天的学习吧!Builder装饰器:自定义构建函数Builder介绍Builder使用说明自定义组件中创建自定义构建函数全局自定义构建函数 Builder参数传递规则按引用传递参数按值传递参数 文章声明⭐⭐⭐ 该文章为我(有编程…...

bottom-up-attention-vqa-master 成功复现!!!
代码地址 1、create_dictionary.py 建立词典和使用预训练的glove向量 (1)create_dictionary() 遍历每个question文件取出所关注的question部分,qs 遍历qs,对每个问题的文本内容进行分词,并将分词结果添加到字典中&…...
BigDecimal中divide方法详解
BigDecimal中divide方法详解 大家好,我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天,让我们一起深入探讨Java中BigDecimal的divide方法,揭开这个…...

视频推拉流EasyDSS互联网直播/点播平台构建户外无人机航拍直播解决方案
一、背景分析 近几年,国内无人机市场随着航拍等业务走进大众,出现爆发式增长。无人机除了在民用方面的应用越来越多,在其他领域也已经开始广泛应用,比如公共安全、应急搜救、农林、环保、交通 、通信、气象、影视航拍等。无人机使…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...

【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...

【大模型】RankRAG:基于大模型的上下文排序与检索增强生成的统一框架
文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构C.1 指令微调阶段C.2 排名与生成的总和指令微调阶段C.3 RankRAG推理:检索-重排-生成 D 实验设计E 个人总结 A 论文出处 论文题目:RankRAG:Unifying Context Ranking…...