privatevariRecorder: IRecorder? = nullprivatevarcurrentRecorderResult: RecorderResult = RecorderResultprivatevarcurrentWeight: Int= - 1
privatevalremoteCallbackList: RemoteCallbackList = RemoteCallbackList
privatevalmBinder: IRecorderService.Stub = object: IRecorderService.Stub {
overridefunstartRecording(recorderConfig: RecorderConfig){startRecordingInternal(recorderConfig)}
overridefunstopRecording(recorderConfig: RecorderConfig){if(recorderConfig.recorderId == currentRecorderResult.recorderId)stopRecordingInternalelse{notifyCallBack {it.onException("Cannot stop the current recording because the recorderId is not the same as the current recording",currentRecorderResult)}}}
overridefungetActiveRecording: RecorderResult? {returncurrentRecorderResult}
overridefunisRecording(recorderConfig: RecorderConfig?): Boolean{returnif(recorderConfig?.recorderId == currentRecorderResult.recorderId)iRecorder?.isRecording ?: falseelsefalse}
overridefunregisterCallback(callBack: IRecorderCallBack){remoteCallbackList.register(callBack)}
overridefununregisterCallback(callBack: IRecorderCallBack){remoteCallbackList.unregister(callBack)}
}
overridefunonBind(intent: Intent?): IBinder? {returnmBinder}
@SynchronizedprivatefunstartRecordingInternal(recorderConfig: RecorderConfig){
valwillStartRecorderResult =RecorderResultBuilder.aRecorderResult.withRecorderFile(recorderConfig.recorderFile).withRecorderId(recorderConfig.recorderId).build
if(ContextCompat.checkSelfPermission(this@RecorderService,android.Manifest.permission.RECORD_AUDIO)!= PackageManager.PERMISSION_GRANTED) {logD( "Record audio permission not granted, can't record")notifyCallBack {it.onException("Record audio permission not granted, can't record",willStartRecorderResult)}return}
if(ContextCompat.checkSelfPermission(this@RecorderService,android.Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {logD( "External storage permission not granted, can't save recorded")notifyCallBack {it.onException("External storage permission not granted, can't save recorded",willStartRecorderResult)}return}
if(isRecording) {
valweight = recorderConfig.weight
if(weight < currentWeight) {logD( "Recording with weight greater than in recording")notifyCallBack {it.onException("Recording with weight greater than in recording",willStartRecorderResult)}return}
if(weight > currentWeight) {//只要权重大于当前权重,立即停止当前。stopRecordingInternal}
if(weight == currentWeight) {if(recorderConfig.recorderId == currentRecorderResult.recorderId) {notifyCallBack {it.onException("The same recording cannot be started repeatedly",willStartRecorderResult)}return} else{stopRecordingInternal}}
startRecorder(recorderConfig, willStartRecorderResult)
} else{
startRecorder(recorderConfig, willStartRecorderResult)
}
}
privatefunstartRecorder(recorderConfig: RecorderConfig,willStartRecorderResult: RecorderResult){logD( "startRecording result ${willStartRecorderResult.toString}")
iRecorder = when(recorderConfig.recorderOutFormat) {RecorderOutFormat.MPEG_4, RecorderOutFormat.AMR_WB -> {JLMediaRecorder}RecorderOutFormat.PCM -> {JLAudioRecorder}}
valresult = iRecorder?.startRecording(recorderConfig)
if(!result.isNullOrEmpty) {logD( "startRecording result $result")notifyCallBack {it.onException(result, willStartRecorderResult)}} else{currentWeight = recorderConfig.weightnotifyCallBack {it.onStart(willStartRecorderResult)}currentRecorderResult = willStartRecorderResult}}
privatefunisRecording: Boolean{returniRecorder?.isRecording ?: false}
@SynchronizedprivatefunstopRecordingInternal{logD( "stopRecordingInternal")iRecorder?.stopRecordingcurrentWeight = - 1iRecorder = nullMediaScannerConnection.scanFile(this,arrayOf(currentRecorderResult.recorderFile?.absolutePath),null,null)notifyCallBack {it.onStop(currentRecorderResult)}}
privatefunnotifyCallBack(done: ( IRecorderCallBack)-> Unit) {valsize = remoteCallbackList.beginBroadcastlogD( "recorded notifyCallBack size $size")( 0until size).forEach {done(remoteCallbackList.getBroadcastItem(it))}remoteCallbackList.finishBroadcast}
}
这里需要注意的几点:
因为是跨进程服务,启动录音的时候有可能是多个app在同一时间启动,还有可能在一个App录音的同时,另一个App调用停止的功能,所以这里维护好当前currentRecorderResult对象的维护,还有一个currentWeight字段也很重要,这个字段主要是维护优先级的问题,只要有比当前优先级高的指令,就按新的指令操作录音服务。
notifyCallBack 在合适时候调用AIDL回调,通知App做相应的操作。
RecorderManager 实现
step 1
服务注册,这里按主App的包名来启动,所有App都是以这种方式启动
funinitialize(context: Context?, serviceConnectState: (( Boolean)-> Unit)? = null) {mApplicationContext = context?.applicationContextif(!isServiceConnected) {this.mServiceConnectState = serviceConnectStatevalserviceIntent = IntentserviceIntent.` package` = "com.julive.recorder"serviceIntent.action = "com.julive.audio.service"valisCanBind = mApplicationContext?.bindService(serviceIntent,mConnection,Context.BIND_AUTO_CREATE) ?: falseif(!isCanBind) {logE( "isCanBind: $isCanBind")this.mServiceConnectState?.invoke( false)bindSelfService}}}
isCanBind 是false的情况,就是未发现主App的情况,这个时候就需要启动自己的服务
privatefunbindSelfService{valserviceIntent = Intent(mApplicationContext, RecorderService:: class. java)valisSelfBind =mApplicationContext?.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)logE( "isSelfBind: $isSelfBind")}
step 2
连接成功后
privatevalmConnection: ServiceConnection = object: ServiceConnection {overridefunonServiceConnected(name: ComponentName, service: IBinder){mRecorderService = IRecorderService.Stub.asInterface(service)mRecorderService?.asBinder?.linkToDeath(deathRecipient, 0)isServiceConnected = truemServiceConnectState?.invoke( true)}overridefunonServiceDisconnected(name: ComponentName){isServiceConnected = falsemRecorderService = nulllogE( "onServiceDisconnected:name= $name")}}
接下来就可以用mRecorderService 来操作AIDL接口,最终调用RecorderService的实现
//启动funstartRecording(recorderConfig: RecorderConfig?){if(recorderConfig != null)mRecorderService?.startRecording(recorderConfig)}//暂停funstopRecording(recorderConfig: RecorderConfig?){if(recorderConfig != null)mRecorderService?.stopRecording(recorderConfig)}//是否录音中funisRecording(recorderConfig: RecorderConfig?): Boolean{returnmRecorderService?.isRecording(recorderConfig) ?: false}
这样一套完成的跨进程通信就完成了,代码注释很少,经过这个流程的代码展示,应该能明白整体的调用流程。如果有不明白的,欢迎留言区哦。
总结
通过这两天,对这个AIDL实现的录音服务,对跨进程的数据处理有了更加深刻的认知,这里面有几个比较难处理的就是录音的状态维护,还有就是优先级的维护,能把这两点整明白其实也很好处理。不扯了,有问题留言区交流。
欢迎交流:
git 源码:https://github.com/ibaozi-cn/Multi-Process-Audio-Recorder
SegmentFault 思否社区和文章作者展开更多互动和交流。