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>() {
        @Override
        public void onSubscribe(@NonNull Disposable d) {
        }

        @Override
        public 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("、");
            }
        }

        @Override
        public void onError(@NonNull Throwable e) {
            Log.d(TAG, "permission  onError");
        }

        @Override
        public 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);
                }
            }
        }
    });
}

Android支持多个app同时录音_正常运行

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;
    }

    @Override
    protected 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() {
        // title
        if (!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;
        }
    }

    @Override
    public 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目录下,并且是项目自己包名下:

Android支持多个app同时录音_音视频_02

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() {
    @Override
    public void onClick(View v) {
        buttonEnabled(false, true, true);
        startAudioRecord();
    }
});

8.停止录音:

btnStop.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Handler().post(new Runnable() {
            @Override
            public 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() {
    @Override
    public 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();

    @Override
    protected 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>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {
            }

            @Override
            public 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("、");
                }
            }

            @Override
            public void onError(@NonNull Throwable e) {
                LogUtils.d(TAG, "permission  onError");
            }

            @Override
            public 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() {
            @Override
            public void onClick(View v) {
                buttonEnabled(false, true, true);
                startAudioRecord();
            }
        });

        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AudioManagerUtils.getInstance().playPcm(true);
                buttonEnabled(false, false, true);
            }
        });
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Handler().post(new Runnable() {
                    @Override
                    public 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);
    }

    @Override
    protected void onStop() {
        super.onStop();
        AudioManagerUtils.getInstance().pauseAudio();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        AudioManagerUtils.getInstance().releaseAudio();
    }

}

11.实现的效果如下:

Android支持多个app同时录音_ide_03

12.总结:

  • 以上就是今天的内容,录制音频时适配Android13.
  • Android13文件读写细分为三个权限 READ_MEDIA_AUDIO、READ_MEDIA_VIDEO、READ_MEDIA_IMAGES.
  • Android10以上文件创建和生成需要在公共目录,不能随意创建和读写.