public inline fun File.outputStream(): FileOutputStream {
return FileOutputStream(this)
}
use()
是一个 Closeable 的扩展方法,不管发生了什么,最终use()
都会调用close()
来关闭资源。这就避免了流操作的模板代码,降低了代码的复杂度:
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
var exception: Throwable? = null
try {
// 在 try 代码块中执行传入的 lambda
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
// 在 finally 中执行 close()
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {}
}
}
}
- IO操作时耗时的,读写音频数据的代码应该在非UI线程中执行。而是否继续录制应该由用户动作触发,即UI线程触发。这里有多线程安全问题,需要一个线程安全的布尔值来控制音频录制:
var isRecording = AtomicBoolean(false) // 线程安全的布尔变量
val audioRecord: AudioRecord
// 是否继续录制
fun continueRecord(): Boolean {
return isRecording.get() &&
audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING
}
// 停止录制音频(供业务层调用以停止录音的 while 循环)
fun stop() {
isRecording.set(false)
}
解耦抽象
将对 AudioRecord 的所有操作都抽象在一个接口中:
interface Recorder {
var outputFormat: String // 输出音频格式
fun isRecording(): Boolean // 是否正在录制
fun getDuration(): Long // 获取音频时长
fun start(outputFile: File, maxDuration: Int) // 开始录制
fun stop() // 停止录制
fun release() // 释放录制资源
}
这个接口提供了录制音频的抽象能力。当上层类和这组接口打交道时,不需要关心录制音频的实现细节,即不和 AudioRecord 耦合。
为啥要多一层这样的抽象?因为具体实现总是易变的,万一哪天业务层需要直接生成 AAC 文件,就可以通过添加一个Recorder
的实例方便地地替换原有实现。
给出 AudioRecord 对于Recorder
接口的实现:
class AudioRecorder(override var outputFormat: String) : Recorder {
private var bufferSize: Int = 0 // 音频字节缓冲区大小
private var isRecording = AtomicBoolean(false) // 用于控制音频录制的线程安全布尔值
private var startTime = 0L // 记录音频开始录制时间
private var duration = 0L // 音频时长
// AudioRecord 实例
private val audioRecord by lazy {
bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_IN_MONO, ENCODING_PCM_16BIT)
AudioRecord(SOURCE, SAMPLE_RATE, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, bufferSize)
}
// 是否正在录制
override fun isRecording(): Boolean = isRecording.get()
// 获取音频时长
override fun getDuration(): Long = duration
// 开始音频录制
override fun start(outputFile: File, maxDuration: Int) {
if (audioRecord.state == AudioRecord.STATE_UNINITIALIZED) return
isRecording.set(true) // 在异步线程中标记开始录制
startTime.set(SystemClock.elapsedRealtime()) // 在异步线程中记录开始时间
// 创建文件输出流
outputFile.outputStream().use { outputStream ->
// 开始录制
audioRecord.startRecording()
val audioData = ByteArray(bufferSize)
// 持续读取音频数据到字节数组, 再将字节数组写入文件
while (continueRecord(maxDuration)) {