新闻详情

新闻详情

首页 / 资讯中心 / 详情

安卓录音机开发实战:MediaRecorder与权限适配全解析

发布时间:2026/9/3 4:58:47来源:尧图网络
安卓录音机开发实战:MediaRecorder与权限适配全解析
简介这是一份面向Java开发者的Android录音机完整项目源码旨在帮助Android初学者与中级开发者系统掌握移动端录音功能开发的核心流程。项目虽小但脉络完整共41个文件压缩包仅146KB以Java源码3个、XML配置与布局15个、Gradle构建脚本3个及PNG界面资源10个为主也包含权限配置和ProGuard混淆规则可直接导入Android Studio构建运行适合作为课程设计或毕业设计的起步代码。录音功能基于MediaRecorder类实现从AndroidManifest权限声明开始逐一展示了音频源选择、输出格式与编码器设置、采样率和比特率调节、内部或外部文件存储等关键步骤并配套了录制按钮的UI交互、非主线程操作、错误监听与释放资源等工程实现。开发者可以通过阅读项目代码具体理解15个高频技术难点如暂停恢复录音、多片段合并以及Android 6.0以上动态权限适配等省去边查资料边调试的时间。目前已获得313人学习关注无论用于个人练习还是技术复盘这份浓缩的录音项目都能带来直观的参考价值。1. 需求拆解录音机不是“点一下开始”那么简单安卓录音机这个项目我最初接到的时候以为是个练手小项目真正做下来才发现里面藏着不少值得琢磨的东西。先别急着写代码把需求彻底拆一拆后面能少踩一半的坑。1.1 核心需求解析用户侧的“录音机”一般就三个功能录音、回放、文件管理。但如果你是自己开发一个正式App藏在背后的是这几层需求录制质量可控支持不同采样率44.1kHz/48kHz、不同编码格式AAC/PCM能切换音质档位这是消费级录音App的基础盘。录音过程可感知实时显示录音时长最好有音量波形或分贝指示让用户知道“它确实在录”。文件持久化与管理录音文件要存到公共媒体库MediaStore里让其他文件管理器、音乐播放器也能看到要能按时间倒序展示支持重命名和删除。后台与异常处理来电打断、锁屏、内存不足、存储空间不足这些都要做兜底否则用户录了半小时发现文件损坏体验直接崩。1.2 技术选型MediaRecorder还是AudioRecord这是第一个岔路口也是面试里高频出现的问题。我的建议是项目核心用MediaRecorder专业场景再上AudioRecord。MediaRecorder是Android封装好的高层录音API内部把音频采集、编码、封装全串好了调用逻辑简单输出的文件是带格式的m4a/aac/mp3直接就能播放。AudioRecord则是PCM裸流要自己处理编码和封装能拿到原始音频数据可以做实时频谱、降噪、变声等功能但工作量大得多。我这版录音机以MediaRecorder为主力原因很现实开发周期短、系统兼容性好、文件可直接播放。如果你后续想做专业录音、波形可视化和音频分析再在架构上预留一个AudioRecord的接口替换空间不冲突。1.3 你几乎绕不开的权限准备做录音机麦克风权限RECORD_AUDIO是跑不掉的。但要命的是不同Android版本对权限的索要时机和方式都有讲究Android 6.0API 23起麦克风属于危险权限必须运行时申请不能只写在Manifest里。Android 11API 30起分区存储强制开启不能再直接用File路径往外部存储里写必须走MediaStore。Android 13API 33起新增了通知权限POST_NOTIFICATIONS如果你的录音App有前台服务通知后台录音必备这个也得申请。权限设计上我建议进入App时先申请录音权限把拒绝后的引导逻辑写好不要在主流程里现申请现崩溃。实际开发中我见过太多因为用户拒绝一次权限再进页面就空指针崩溃的情况——都死在没做权限被拒后的状态处理。2. 数据层设计从文件路径到MediaStore的迁移Android录音机的数据管理经历过Android 10之前的“随意写文件”时代和之后的“分区存储”时代。你要是现在才开始做新项目直接按MediaStore方案来别走弯路。2.1 为什么必须拥抱分区存储Android 10API 29之后的版本系统强制要求App把媒体文件写入到公共媒体目录时通过MediaStore接口而不能再直接拿Environment.getExternalStorageDirectory()拼路径去写。好处是很明显的录音文件能统一进系统媒体库用户在其他音乐播放器、文件管理器里也能找到。App无需申请存储权限减少权限弹窗用户心理负担小。卸载App时自己写入的媒体文件可保留也可以删除系统帮我们管理归属关系。对应在代码上保存录音文件的逻辑就变成val values ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, displayName) // 文件名如record_20250101_153000.m4a put(MediaStore.Audio.Media.MIME_TYPE, audio/mp4) put(MediaStore.Audio.Media.RELATIVE_PATH, Music/MyRecorder) // 相对路径Android 10 put(MediaStore.Audio.Media.IS_PLAYBACK, 1) put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis() / 1000) } val uri contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)这里的核心就是先通过ContentResolver插入一条空记录拿到URI然后打开这个URI的输出流写入录音数据。注意RELATIVE_PATH这个字段Android 10以上必须要设置否则文件会存到默认位置导致列表查不到。2.2 录音列表查询与排序的正确姿势录音文件列表的展示我推荐直接“查系统媒体库”而不是维护自己的数据库。原因很直接如果你维护了一个DB记录文件路径一旦用户在系统相册/文件管理器里删除了某些录音你的DB和实际文件就对不上了还要额外做同步逻辑。查询也很简单一次性把当前App写入的录音捞出来val projection arrayOf( MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.DURATION, MediaStore.Audio.Media.SIZE, MediaStore.Audio.Media.DATE_ADDED ) val selection ${MediaStore.Audio.Media.RELATIVE_PATH} LIKE ? val selectionArgs arrayOf(%MyRecorder%) contentResolver.query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, ${MediaStore.Audio.Media.DATE_ADDED} DESC )这里有个小技巧通过RELATIVE_PATH做筛选只展示本App写的文件不会把系统的其他录音也带进来。如果你想让App更通用比如显示所有录音也可以不要这个筛选条件但通常个人录音机产品只展示自己的文件夹就够了。3. MediaRecorder实操录音全流程的实现细节上面那些是地基这章才是真正的施工。我按“初始化→开始→停止→释放”的完整链路把每一步的关键代码和注意事项都写出来。3.1 MediaRecorder的基本配置与状态机MediaRecorder是典型的状态机模型操作顺序错一步就给你抛异常。核心状态是Idle → Initialized → DataSourceConfigured → Prepared → Recording → Released。常见的一个坑是录音完成后直接再次调用start()会崩——因为状态不对。必须先reset()回到Idle再重新配置才能再次录制。我封装了一个简单的录音管理器关键配置如下fun startRecording(context: Context, fileName: String) { if (recorder ! null) return recorder MediaRecorder(context) recorder.apply { setAudioSource(MediaRecorder.AudioSource.MIC) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setAudioSamplingRate(44100) // 采样率CD音质 setAudioEncodingBitRate(128000) // 比特率128kbps setOutputFile(getOutputFile(context, fileName).toString()) } runCatching { recorder?.prepare() recorder?.start() }.onFailure { releaseRecorder() // 这里要处理prepare失败的情况比如麦克风被其他App占用 } }注意prepare()和start()必须放在try-catch里。prepare()失败多半是Sdcard未就绪或路径非法start()失败大概率是麦克风被占用或权限没给。这两个异常在真机上非常常见。3.2 输出文件的保存MediaStore与File的两手准备刚才说过Android 10以上走MediaStore但为了兼容老版本如果你minSdk设得比较低还是要兼容WRITE_EXTERNAL_STORAGE权限和File直写。我的做法是用一个分支private fun getOutputFile(context: Context, fileName: String): Uri { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { val values ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, fileName) put(MediaStore.Audio.Media.MIME_TYPE, audio/mp4) put(MediaStore.Audio.Media.RELATIVE_PATH, Music/MyRecorder) } context.contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)!! } else { // API 29以下直接写公共目录并发出扫描广播 val dir Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) val file File(dir, MyRecorder/$fileName) file.parentFile?.mkdirs() Uri.fromFile(file) } }这里有个细节我要强调录音时的setOutputFile()传入的是URI不是String路径。MediaRecorder的重载方法setOutputFile(FileDescriptor fd)和setOutputFile(String path)都有但如果你传入Uri.toString()一定是错的得用ParcelFileDescriptor或直接传File路径。3.3 实时时长与音量反馈的实现录音过程中显示时长和分贝波形用户感知会很专业。时长好办开一个定时器每秒钟去拿MediaRecorder的maxAmplitude做分贝计算和时长累加就行val timer Timer() timer.schedule(object : TimerTask() { override fun run() { val amplitude recorder?.maxAmplitude ?: 0 val db if (amplitude 0) { 20 * Math.log10(amplitude / 32768.0) } else { -80.0 // 静音阈值 } runOnUiThread { binding.tvDuration.text formatDuration(durationSeconds) updateWaveView(db) } } }, 0, 1000)maxAmplitude的值范围是0~32767是16位PCM最大值。用对数公式转成dB动态范围大概在-80dB~0dB。波形View可以简单地用一串柱状图实现每秒钟更新一根柱子也能做出像样的视觉效果。4. 播放与文件复用别让录音变成一次性的录音功能做完了紧接着是播放。这块看着不难但你照着官方示例写AudioManager那一套很容易忽略一个舒适度问题录音机里听录音扬声器和听筒的切换。4.1 播放器的选择与音量控制做录音回放我只推荐MediaPlayer或ExoPlayer不推荐SoundPool——SoundPool更偏向短音效不适合长音频播放。MediaPlayer播放录音文件的示例mediaPlayer MediaPlayer().apply { setDataSource(context, uri) setAudioAttributes( AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build() ) prepare() start() }这里有一个很实用的细节录制和播放之前的音频焦点处理。用户在播放录音时如果突然来电话或导航提示播放应该暂停而不是继续吵。我给播放器设置了AudioManager.OnAudioFocusChangeListener在丢失焦点时暂停在恢复焦点时继续val focusRequest AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes( AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build() ) .setOnAudioFocusChangeListener(focusListener) .build() audioManager.requestAudioFocus(focusRequest)这个细节虽然小但做出来之后App的“专业感”会明显不一样。用户录完音后去刷抖音抖音声音不会被录音机卡掉反过来录音机播放时来电话也不会卡住电话铃声。4.2 录音文件的删除与重命名列表页的删除和重命名看起来常规但Android 10以后都要用到MediaStore的API// 删除 contentResolver.delete(uri, null, null) // 重命名 val values ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, newName) } contentResolver.update(uri, values, null, null)只需要操作ContentResolver不需要关心底层文件的物理路径这是分区存储带来的便利。另外要注意的是如果你用File直接删除针对Android 9及以下的旧文件还需要给MediaStore发一个Intent.ACTION_MEDIA_SCANNER_SCAN_FILE广播否则系统图库里还会残留文件信息显示一个“假文件”。5. 真机调试与适配不同系统版本的录音坑我在适配过程中踩了不少系统的坑这里挑几个典型的记录一下吧。5.1 权限相关Android 6/11/13三个分水岭Android权限模型这几年改了三次我得分别适配Android 6API 23没有动态权限Manifest.permission.RECORD_AUDIO只在安装时授予。如果你的minSdk低于23老机器上不会弹权限框但录音会静音。必须显式判断SDK版本低于23直接进录音高于23运行时请求。Android 11API 30分区存储强制生效不能用File写公共目录。如果你的代码里有Environment.getExternalStorageDirectory()的老写法Android 11上一定会写入失败。Android 13API 33新增POST_NOTIFICATIONS权限。如果你的录音服务是前台服务录音过程中屏幕关闭后继续录Android 13上必须显示通知否则系统直接Kill掉你的Service。5.2 真机问题实录我拿手边几台不同厂商的机器测试整理成下表问题现象原因解决方案部分小米/红米机型录音断断续续系统录音权限弹窗后需要等到用户点击允许才继续不要在主线程同步等待权限用registerForActivityResult异步处理华为EMUI后台录音被系统清理省电策略干掉后台Service前台服务通知或在App内引导用户关闭电池优化录音前几秒有滋滋的电流声部分机型麦克风启动需要时间拿到的音频字节是无效的开始录音后延迟500ms~800ms再显示“正在录音”或者舍弃前0.5秒的静音数据部分Android 13设备上MediaRecorder输出文件大小为0存到MediaStore后没有正确关闭输出流在停止录音后确保fileDescriptor.close()被调用第三个电流声问题我一开始也以为是算法问题后来发现是硬件层面的“预热”。我加了一个小延时用户体验上基本无感但文件质量明显提升。5.3 后台录音的保活策略与合理边界后台录音是录音类App的刚需场景但也是最容易被手机系统“管教”的。我在做法上分三层前台服务录音时启动前台服务并显示通知声明foregroundServiceTypemicrophoneAndroid 14要求声明。前台服务类型声明在AndroidManifest里给Service添加android:foregroundServiceTypemicrophone并在运行时动态申请FOREGROUND_SERVICE_MICROPHONE权限。合理降级如果用户明确关闭了通知权限或系统禁止前台服务启动就回退为普通Service弱提醒录音中断时给用户一个通知。这里要强调一个安全边界录音App的保活是为了“用户主动开始录音后App退到后台也能继续把这段录音录完”。绝对不要拿这个能力去做任何后台偷偷录音的事情——既违反用户隐私预期在应用市场审核上也过不了。6. 常见问题排查从表象到根因的定位思路最后这部分我整理一套问题排查逻辑。真机上遇到问题先不要瞎改代码沿着这个思路来定位。6.1 录音失败类问题最典型的报错就是start failed或prepare failed。定位步骤检查运行权限设置里看App有没有麦克风权限。有的厂商ROM在第一次拒绝权限后会进“仅在本次允许”状态杀进程后自动收回权限特别坑。检查是否被其他App占用麦克风微信语音、系统语音助手、甚至某些地图App的语音搜索都会占住麦克风。MediaRecorder的start失败代码是-19EACCES这时候提示用户关闭其他录音应用即可。检查存储空间录了半小时后磁盘满了MediaRecorder的start或write会异常。最好在开始录音前做一次可用空间判断少于100MB时提醒用户。6.2 录音文件不存在或无法播放录音文件生成了但列表里看不到或者播放失败。我的排查路径先看MediaStore查询条件里的RELATIVE_PATH和实际写入路径是否一致。常见错误是写入用“Music/MyRecorder”查询却用“MyRecorder”结果查不到。确认录音停止时是否正确关闭了输出流。停录流程是stop()→reset()→release()顺序不能错。如果直接调用release()而不先stop()MediaRecorder会自己把文件finalize但有时候会生成一个0字节的坏文件。检查录音编码是否被系统播放器支持。AAC/AAC-LC基本所有设备都支持但如果你换了AMR-NB语音通话格式部分音乐播放器识别不了但系统自带的录音机是能放的。6.3 容易被忽略的性能与体验细节最后分享几个体验层面的小优化做完之后整个App的质感会不一样录音计时不准用SystemClock.elapsedRealtime()做计时基准不要用Date因为用户中途改系统时间会导致计时错乱。Dialog与通知的文案录音开始时给用户一个明确的当前状态。我加了双保险前台服务通知页面内红点闪烁动画让用户无论在哪个界面都知道“正在录音”。大文件处理长时间录音2小时以上MediaRecorder会生成几个GB的文件。建议在录音超过一定时长比如1小时时自动分段保存方便管理也减少文件损坏风险。这个录音机做下来我最大的体会是看似简单的系统级功能一旦涉及到设备和系统的碎片化适配里面全是细节。录音的代码量不大但如果不能把状态机、权限适配、MediaStore原理弄明白排起bug来会非常痛苦。希望这篇记录能帮你把这块的路趟得平一点少踩几个隐形的坑。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

从“XQL出发首尔啦”说开去:技术写作必须依托真实输入 2026/9/3 5:52:57

从“XQL出发首尔啦”说开去:技术写作必须依托真实输入

根据当前输入内容,“XQL出发首尔啦” 这个项目标题无法生成符合要求的 CSDN 技术博客。原因很简单:当前任务要求产出的是一篇具备技术深度、环境搭建、代码示例、问题排查和工程建议的 CSDN 技术长文,但这次输入中没有任何技术关键词、项目说…

阅读更多 →
Python自动化脚本实战:从网页抓取到数据处理全流程解析 2026/9/3 5:52:57

Python自动化脚本实战:从网页抓取到数据处理全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Replit免费模式调整背后:AI编程与云IDE的成本博弈 2026/9/3 5:52:57

Replit免费模式调整背后:AI编程与云IDE的成本博弈

如果你最近关注 Replit,大概率会看到两类声音:一类是说它“变了”“免费用户越来越难用”,另一类是感叹它的 AI Agent 一键生成应用确实强。这两种感受其实指向同一件事:Replit 的免费模式正在发生结构性调整,不再是早…

阅读更多 →
基于OpenPose与行为分析的老年人跌倒检测系统:从原理到工程实践 2026/9/3 5:52:57

基于OpenPose与行为分析的老年人跌倒检测系统:从原理到工程实践

简介:本资源是一套基于OpenPose的人体姿态检测实战项目源码,面向计算机视觉初学者与智慧养老应用开发者,聚焦老年人日常行为监护场景,支持站立、坐姿、躺卧及摔倒等关键状态识别。压缩包共578个文件,含382张标注图像&a…

阅读更多 →
小电容点焊机原理与实战:DIY锂电池焊接安全指南 2026/9/3 5:52:57

小电容点焊机原理与实战:DIY锂电池焊接安全指南

最近想给手头的几个小风扇、蓝牙音箱换个电池,或者DIY个充电宝,你是不是也纠结过:用烙铁焊接电池太危险,专业点焊机又贵又占地方?市面上那些几百块的小电容点焊机,到底能不能用,是不是“玩具”&…

阅读更多 →
多级CIC滤波器Verilog实现:从原理到FPGA工程实践 2026/9/3 5:49:56

多级CIC滤波器Verilog实现:从原理到FPGA工程实践

简介:本资源是一套基于Verilog实现的多级CIC(积分梳状)滤波器完整工程,面向数字信号处理工程师、FPGA开发初学者及通信系统设计人员,解决采样率转换中高效低开销滤波器的设计与硬件实现问题。压缩包含169个文件&#x…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞