安卓执法仪录像之进程间共享内存
Posted John0551
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓执法仪录像之进程间共享内存相关的知识,希望对你有一定的参考价值。
之前我在文章里说过,我们的录像模块是跨进程的.为什么要这样设计,是因为录像这个过程是一个长期执行的过程,中间不能有断开.为了保证稳定性,通过进程隔离将录像业务同主业务分离.
通常情况下,如果在录像过程中APP崩溃了,就会导致录像文件损坏,可能前面录的数据就丢了.
但是如果做了进程隔离的话,主进程发生异常崩溃,不影响录像进程.而录像进程功能相对单一简单,不会导致崩溃.当主进程崩溃时,录像进程可以检测到,这时及时停止当前的录像,录像正常结束,就可以播放.
安卓端实现进程隔离是相对容易的,大体上为:
- 创建一个
Service
- 在
Manifest
里面给这个Service
配置process
属性,表示该服务将运行在独立进程,其进程ID
就是process
的值.比如:<service android:name=".logic.impl.FFMuxerService" android:process="com.android.mpu.ff" />
- 主进程启动通过
startService
启动这个服务,并且通过bindService
与服务绑定,这样子进程也就启动了. - 主进程通过
IPC
(binder
)与子进程进行数据交互.交互过程主要有:- 开始录像
- 录像采集/编码视频帧传递给录像进程
- 停止录像.
其跨进程的架构图如下所示:
实际实现过程中又不可避免地带来一些问题,主要有:
- 跨进程
Binder
大数据量传输限制(TransactionTooLargeException
).安卓内部对于Binder
的数据传输做了限制,最多不超过1M,但是媒体帧数据量通常比较大,有可能超过了1M.这样通过Binder
传递的话,效率不高,而且还容易出现TransactionTooLargeException
异常.
可通过共享内存来解决这个问题.安卓平台有个MemoryFile
可以用来共享内存,相比直接传输媒体帧,主进程可创建一个MemoryFile
,并将其fd
通过binder
传递给子进程,子进程得到fd
后,取出内存数据.这样可以有效避免大数据量传输的问题,且效率也更高.
MemoryFile
相关知识点,可以参考这篇文章的介绍:MemoryFile
主进程侧关键代码片段如下:
录像进程侧:// 把内存写入MemoryFile对象 mf.writeBytes(buffer, 0, 0, info.size) param.frameType = AVMEDIA_TYPE_VIDEO param.size = info.size // 取得MemoryFile对应的FileDescriptor param.pfd = muxCon.getFD(mf) param.timeStampleUs = info.presentationTimeUs ... paramArg.writeParcelable(param, 0) // 通过binder传递给子进程 val success = muxCon.binder!!.transact(CODE_WRITE_FRAME, paramArg, replay, 0)
// 从binder里面解析输入参数. val writeParam = data?.readParcelable<WriteParam>(WriteParam::class.java.classLoader)!! // 创建buffer,从filediscripter里面取出媒体帧. var buffer = ByteArray(writeParam.size) val ins = FDIO(writeParam.pfd!!.fileDescriptor, writeParam.size) val readBytes = ins.readBytes(buffer, 0, 0, writeParam.size)
- 录像进程对主进程的崩溃检测.上面说了录像过程中如果主进程崩溃了, 那录像进程应该及时停止当前的录像,确保在录文件能尽快正常停止以免损坏.所以主进程崩溃后,录像进程需要捕获到这个信息.
Service
提供了onBind
和onUnBind
方法,这两个回调可满足需求.onBind
表示服务被其它组件绑定(bind
)了,onUnBind
表示服务被其它组件释放了.也就是说主进程启动录像进程时,就会bind
,主进程停止,录像进程就会onUnbind
.@Override override fun onBind(intent: Intent): IBinder // 服务被绑定.啥都不用干.打印个日志 Timber.i("service bind to:$intent") return binder
override fun onUnbind(intent: Intent?): Boolean // 服务被释放了.及时停止录像 Timber.i("service unbind to:$intent") // 每一路都需要停下来. muxers.valueIterator().forEach it.apply muxer?.close() Timber.i("muxer of transId:$transId,path:$path closed.duration:$(SystemClock.elapsedRealtime() - mStartMillis) / 1000 / 60min") muxers.clear() return false
- 多路录像的问题,由于
Service
无法多实例启动,所以当同时录多路的话,没有办法通过多个Service
来实现,我们在这个唯一的Service
内部,通过主进程传入不通的实例ID
来做区分.
录像进程保留多个录像实例,由一个唯一ID
来对应,主进程可传入这个ID
.override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean // 先取实例ID. val transId = data.readInt() return try doTransact(transId, code, data, reply, flags) catch (e: Throwable) // 发生异常,及时停止录像. // muxers保存一系列录像实例,可通过transId取出当前录像实例关闭之. muxers[transId]?.muxer?.close() muxers.remove(transId) Timber.e(e, "muxer of transId:$transId 录像进程异常..") false
经过我们测试分进程之后,录像基本不会碰到损坏的情况了. 这个问题得到完美解决.
但是分进程也不是万能的,比如录像过程中暴力强制关机,拆电池,那分进程也是没辙的,这种情况下就是无解,遇到后只能想办法通过后期视频修复工具来进行修复了.
参考资料
以上是关于安卓执法仪录像之进程间共享内存的主要内容,如果未能解决你的问题,请参考以下文章