📖 全文约 1964 字 | 阅读约 5 分钟
我有一个很日常但很折磨人的痛点——看书、刷文章、走路通勤的时候,脑子经常蹦出一些想法。等我掏出手机,打开备忘录,开输入法,几个字打一半,想法已经跑了。
所以我就想要个极简的东西:桌面上一个悬浮球,点一下开始录音,再点一下停止。录完自动把语音转成文字,按日期归档。每天的灵感文字合并成一页笔记,想复制的时候一键拷走。
听起来很简单对吧?
做起来花了半天。
第一版很快跑通了——Android 的 !!MediaRecorder!! API 其实很稳。录是能录,但问题马上就来了:
用 XML 的 layer-list 叠形状,渲染出来椭圆了。后来干脆改最简单的 oval 单层,外面容器锁死正方形,搞定。
问题 2:录下来的文件打不开,播放器提示「媒体错误」
Debug 一圈发现是文件名没带后缀,MIME 类型也写错了。我以为随便扔个路径就行,结果 Android 媒体服务不认。统一改成 ==.m4a + audio/m4a==,终于能播了。
问题 3:录音文件没存到系统录音目录
原代码写死了一个私有目录路径。改成公共 Music/InspirationRecorder,再调一下 MediaStore 让系统扫一圈——文件管理器也能看到了。
这一阶段差不多半天。说真的——==功能越简单,环境细节越容易卡人==。
这个 App 最值钱的就是「语音转文字」。我接的是火山引擎!!豆包!!的语音识别 API。
官方文档新版旧版参数和请求头完全不搭。一开始把 API Key 直接塞 X-Api-Key,返回 401。Debug 到后面才反应过来——我用的是 ==豆包大模型的 Key,根本不是语音识别的 Key==,两条产品线,两套东西。
问题 5:文件托管连环翻车(卡我最久的一块)
豆包 ASR 需要你给一个公网直链让它下载音频。也就是说,我先得把录好的 m4a 传到某个公网服务器,拿到 URL 再发给豆包。
我试了多少个服务?数一下:
❌ catbox.moe → stream was reset: PROTOCOL_ERROR(HTTP/2 握手失败)
❌ tmpfiles.org → Connection reset
❌ file.io → 返回的 JSON 其实是错误页面
❌ 0x0.st → 503 Service Unavailable
❌ transfer.sh → 连不上
最后靠三招稳住:
==强制走 HTTP/1.1==。 OkHttpClient.Builder().protocols(listOf(Protocol.HTTP_1_1))——这一步把大量奇怪的协议错误直接干掉每次上传建新 client,不复用连接池,避免「已死连接被复用」 每个服务设短超时,失败立刻跳到下一个,不卡整个转写流程 catbox 请求体必须带 reqtype=fileupload——少了这个字段直接失败
这块前前后后改了大概 6 次代码才稳定下来,是整个项目最耗时间的一环。
问题 6:URL 空字符串的坑
有一次我把 URL 写成了空字符串 ""——在 Kotlin 里写了 val url = "" ?: fallbackUrl。结果 "" 不是 null,fallback 永远不触发。报错:Expected URL scheme 'http' or 'https' but no scheme was found for。
改成 url.trim().ifEmpty { fallbackUrl } 才过。==这种「看起来是 null 检查,其实是空字符串检查」的坑,语言特性里最容易踩的一类==。
到第二阶段收工,App 已经能跑通:录音 → 上传 → 转写 → 显示文字。
但用起来很别扭:
「查看录音」按钮跳去文字列表,点一条就开始播放——不是我要的 转写结果没法一键复制,还得手动长按选 录音文件越堆越多,没有删除功能
后来加了四个东西:
跟手机自带录音 App 一样:一条条列出来,左边播放按钮、右边删除按钮,中间显示时长和文件大小。删除时同步干掉数据库记录 + 本地文件,Flow 自动刷新其他页面。
「每日灵感」卡片加一键复制
按日期分组展示,卡片右上角一个小复制按钮,点一下当天全部灵感进剪贴板。
「全部记录」回归纯粹的文字浏览
只留文字展示 + 单条复制。播放录音统一去「录音管理页」。
API Key 配置页
首页右上角齿轮按钮,弹窗分「新版控制台」和「旧版控制台」两种格式输入,配置存在本地 SharedPreferences。
关键文件位置:
悬浮球与录音: FloatingRecorderService.kt文件上传 fallback 链: FileUploader.kt豆包 ASR 调用: DoubaoAsrClient.kt数据库: InspirationRepository.kt录音管理页: AudioListActivity.kt
这次搞下来,有两件事我特别想聊。
第一件:==AI 把「动手做」的门槛打下来了,但「做好」的门槛一点没降==。搭个能录音的 App 一小时,做到「录完就出正确文字」——十几个小时。
第二件:==真实项目里 80% 的 bug 出在接口和边界条件==。协议版本、服务通不通、编码格式、权限声明、字段取不取得到、空串还是 null——这些东西教程不会重点讲,但真干起来卡你最久。
如果你也有类似的「小想法」,我的建议就一句:
==别想太多,先做出来再说。==
哪怕第一版长得丑、跑得慢,至少你知道它能不能真正解决你的问题。后面再迭代,永远比一开始追求完美高效得多。
如果你也有「脑子里一直想做但觉得不完美就没动手」的小工具,欢迎留言聊聊——说不定你的痛点就是下一个半天能跑通的 App 原型。
我在这里分享 AI 时代普通人动手造东西的真实过程,不包装、不贩卖焦虑。觉得有用的话,点个关注,下次更新不迷路。
夜雨聆风