
“安卓后台录像”这个需求最唬人的不是录像而是“后台”两个字。息屏录存片段、行车用听起来不过是手机往支架上一放、点一下开始、黑着屏自己录真到实现的时候你才会意识到身上同时压着系统保活、厂商定制、发热掉电、文件损坏这一串山。这篇文章不是扫文档式的介绍而是我做类似项目时的完整思路和踩坑实录把前台服务保活、Camera2 MediaCodec录制链路、片段循环存储、碰撞锁定这些核心环节全部拆开讲清楚。适合正在做行车记录类App、停车监控或者家庭监控方向的Android开发者也适合想自己写一个“行车搭档”工具的极客朋友。1. 这个需求到底在解决什么问题1.1 先把需求拆明白项目标题只有一句话“安卓后台录像APP息屏录存片段行车用”。但这句话背后其实压着四层问题不做技术拆解很难发现第一层是任务生存问题App退到后台、屏幕熄灭之后进程还能不能活着系统随时可能把服务杀了这一层是所有后台录像App的生死线。第二层是相机采集问题息屏状态下摄像头是否还能持续出帧部分国产ROM会在锁屏后释放相机设备这一层的坑最多。第三层是编码存储问题连续视频不能一个文件录到底必须分段、循环覆盖、坏文件可控还要考虑断电和高温。第四层是行车场景特有的东西碰撞锁定、紧急保存、停车监控、移动侦测这些功能决定了App不是“能录”就行而是“录得聪明”。如果你只把“后台录像”当成一个MediaRecorder调用那大概率第一轮真机测试就直接翻车。我见过不少人把Demo跑通了结果锁屏五分钟再看进程已经没了。所以这篇文章的逻辑就是按照上面四层来展开的一层层解决。1.2 技术选型为什么我放弃MediaRecorder选了Camera2 MediaCodec安卓录像主流有三条路MediaRecorder、CameraX VideoCapture、Camera2 MediaCodec。很多人问为什么不直接用最简单的MediaRecorder毕竟它内部把采集、编码、封装全包了几行代码就能出一个MP4。但我的判断是能用但不够用。我做过对比三条路的实际处境大概是这样的方案上手难度定制能力后台稳定性适合场景MediaRecorder最低低拿不到帧数据中Surface输出模式还行快速原型、简单录像CameraX VideoCapture低中扩展在升级中框架封装了很多兼容普通录制AppCamera2 MediaCodec高高帧级控制高行车记录、后台长时录制行车场景有几个硬性要求需要做碰撞检测那就要拿加速度计数据同时知道当前录到哪一段需要做移动侦测停车监控那就要对帧内容做分析需要精细控制码率和I帧间隔避免夜间画面糊成一片需要在息屏状态下稳定跑几小时不闪退。这些需求往后延伸MediaRecorder那点封装就不够了。Camera2负责把相机输出直接引导到MediaCodec的输入Surface上编码后的数据我们自己写进MediaMuxer所有环节都捏在手里。还有一条路值得一提如果你的行车环境里已经有支持RTSP协议的摄像头把App做成纯拉流端、在安卓端缓存RTSP流也是一种常用解法。它避开了相机被系统释放的问题但增加了一条网络链路的复杂度。我自己做的时候还是优先选择本机相机直录毕竟少一个环节少一个故障点。1.3 整体架构的设计思路我的工程结构是围绕一个前台服务展开的核心是RecorderService它同时管理CameraManager、VideoEncoder、SegmentManager和SensorMonitor。摄像头采集流走Surface进编码器编码器输出走MediaMuxer写文件传感器数据单独一条线程跑碰撞时直接通知SegmentManager锁定文件UI层只负责启动服务和展示状态。一句话概括设计原则就是录制链路尽量不跨进程全局只有一个常驻服务持锁。这样做的好处是崩溃面小、权限集中调试时也能一条log追到底。后文的所有代码片段基本都围绕这个结构。2. 后台保活与息屏录制最脏最累的活儿2.1 前台服务是底线不是可选项安卓从8.0开始后台Service的生存时间被大幅压缩不加前台服务的后台录像App基本活不过几分钟。所以第一步就是做成前台服务并且在通知栏给用户一个常驻可见的播放状态。服务声明和启动方式没什么玄学的但有几个关键点值得注意。第一个关键点是Android 14开始对前台服务类型做了严格限制如果App targetSdk是34及以上在Manifest里给服务声明类型时要带上camera或microphone。行车录像通常要录音所以我会同时声明这两个类型。要是不声明运行时会直接抛ForegroundServiceStartNotAllowedException这个坑我踩过一次非常惨。第二个关键点是服务在进程被回收后的自启。onStartCommand里一定要返回START_STICKY这样系统在资源紧张把服务杀掉后有机会重建服务并传入null intent。注意不能依赖START_STICKY完事因为很多国产ROM会从“自启动管理”层面拦截所以还需要在onTaskRemoved里主动重启服务再配合AlarmManager心跳巡检确保服务万一彻底挂了能重新拉起来。第三个关键点是引导用户忽略电池优化。调用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS请求加入白名单这一步不是万能的但能显著减少被系统省电策略误杀的概率。实测下来小米HyperOS上即使加了白名单后台录像还是可能被清后来还要再加一步“后台弹出界面”和“权限自启动”的引导。2.2 息屏不等于停止录像“炭屏模式”实测有用息屏录像最大的难点不是代码而是部分厂商的电源策略。我最初天真地以为只要摄像头在跑、WakeLock拿着PARTIAL_WAKE_LOCK息屏就不是问题。结果在华为和部分小米设备上锁屏之后几分钟摄像头直接被系统强制释放Log里只剩一行“Camera is disconnected”。原因很直接系统锁屏后认为App已经不可见优先回收相机这类高功耗资源。后来我学到一个方案做一个真正的黑屏Activity用黑色主题、关闭过渡动画、窗口亮度调到0同时加FLAG_KEEP_SCREEN_ON。用户视觉上看到的是一片黑和息屏没区别但系统层面认为有一个可见Activity正持着屏幕相机不会被认为是闲置资源。这个方案有个副作用就是屏幕背光其实没有真正关闭耗电会比真实息屏略高。但用USB车载供电的行车场景根本不在乎这点功耗反而稳定性大幅提高。我把它称为“炭屏模式”后来做了开关停车监控这种需要长时间低功耗的场景可以选真息屏WakeLock行车时则默认开炭屏模式。如果你坚持做真息屏技术上也并非无解。需要同时持有WakeLock、保持摄像头Session不关闭还要处理锁屏广播。但我要说明的是这套方案在国产ROM面前是看运气的有的版本能跑几小时有的版本锁屏即断。稳妥起见我最后做的是炭屏和真息屏双方案切换默认炭屏。2.3 功耗、发热与降级策略行车记录类App一录就是好几个小时发热是绕不开的坎。手机在车里晒着前面是玻璃阳光直射后面是摄像头和编码器满负荷工作机身温度超过50度并不稀奇。所以必须做温度监控和动态降级策略。我的做法是每30秒读一次热传感器温度低于45度时正常1080P/30fps45到50度降到25fps超过50度就降到720P。虽然画面质量在极端场景会下降但总比热保护的直接中断强。另外很多芯片发热主要源于电池充电和录像叠加有条件的话在代码里控制充电电流不现实但至少要在日志里把温度趋势打出来便于真机调参。编码格式的选择和功耗也强相关。H.264是通用性最好的选择但换成H.265可以在同等码率下提升画质、降低存储压力。行车App我不建议一上来就上H.265因为老旧播放器和剪辑工具兼容性差关键时刻视频打不开反而麻烦。第一版老老实实H.264后续再考虑用H.265做可选高清档。3. 分段录存与行车场景功能3.1 片段切分与循环覆盖的实现为什么行车录像一定要分段这背后有实际事故处理逻辑如果发生碰撞系统需要把碰撞瞬间前后各一段视频锁定保护不能被后续录像覆盖。要是只有一个长文件锁定操作就很麻烦而且一旦文件损坏就是整段全废。我的方案是每5分钟生成一个文件超过时长就滚动切换到下一个新文件。文件命名用时间戳加序号比如DAS_20250311_142530_0001.mp4方便在文件列表里排序查找。分段切分在MediaCodec方案里要靠自己控制。关键点是有一个segment线程在计时到时间点后通知MediaMuxer stop并释放再重新创建新文件new Muxer。这里有个细节MediaMuxer不支持同一个文件重复start所以每次切段时要重新addTrack、writeHeader再把编码器的trackIndex换新。循环覆盖也不能简单“删最老文件”。我维护一个已录制文件的滑动窗口在录制前就检查存储剩余空间低于预留阈值时优先删除最早的不受保护文件。假如一段行车路走了两小时最终只保留最近50GB或者最近7天内容剩余空间全部预留给锁定文件。这一套逻辑我用的是SegmentManager模块统一管理每次切段后调一次清理。我遇到过有人把文件直接存在App私有目录这在小内存手机上很快爆盘。Android 10以后我建议写入MediaStore.Video好处是系统统一管理用户可以在相册里看见也方便导出。老SD卡方案则要申请MANAGE_EXTERNAL_STORAGE权限审核时会麻烦一些所以新版本一律走MediaStore。3.2 碰撞锁定与事件保护行车记录仪的核心价值在某一次碰撞里你不仅要证明自己没闯红灯还得在事故后半小时内把证据拿出来。碰撞锁定功能的实现依赖的是加速度计而不是画面分析。我注册了TYPE_ACCELEROMETER传感器每20毫秒读一组三轴加速度值通过计算矢量模与重力加速度g的偏差来判断碰撞强度。判断逻辑大致是取当前加速度矢量模 sqrt(ax^2 ay^2 az^2)减掉9.8后取绝对值如果这个值超过2.5g持续50毫秒就触发一次碰撞事件。阈值这个东西不能拍脑袋我建议做成可调配置因为不同车型悬挂、不同手机固定方式差异很大。我实测过正常过减速带大概是1.2g到1.5g急刹车2g上下只有真正碰撞才会稳过2.5g。碰撞触发后当前正在录制的文件不能破坏还需要把前一个文件一起标记为“受保护”。为什么是前一个因为碰撞瞬间可能恰好处于两段文件的交界事故发生时刻还没有来得及写进当前文件的概率并不低所以锁前一段、当前段以及触发后的一段是最稳妥的处理。被保护文件会被移动到lock文件夹循环清理逻辑直接跳过这个目录。除自动碰撞外我还加了手动SOS按钮用户觉得情况不对时一键把最近1分钟到此刻的视频打上高优先级标记并且同时把GPS坐标写进元数据。事故后从App导出时这份带着坐标的事件列表对交警和保险公司都有说服力。3.3 停车监控与移动侦测很多行车记录APP不只是行车时开停车后也干“哨兵”的活。但停车监控和行车录制完全是两种逻辑。如果是纯连续录制一晚上16小时能生产几十个GB的数据大部分内容还是一堵墙所以必须做移动侦测。移动侦测最朴素的思路是定期抓帧做差。我采用低功耗方案平时把Camera2的重复请求帧率压到1fps用一个ImageReader接收一帧YUV转换成灰度图后与前帧做逐块差分。差分面积超过画面5%时判定为有移动发生立刻切换到30fps正常录制移动停止3分钟后回到1fps待机。这个方案功耗比始终录着低很多但也不是零成本。夜间光线不好的时候帧差检测容易误报我会自动把灵敏度调低并且加入“检测到光源切换”的过滤。停车时段如果检测到碰撞级别的加速度事件移动侦测还会额外触发一次紧急录像。如果要把停车监控的延时录像合并成一段正常倍速的视频可以用Video Transcoder类的工具在App内做后期合成。我没把这个功能放进第一版但对想把视频发给别人的用户来说很实用后续可以作为高级功能加上。4. 实操过程一个可跑通的最小工程4.1 权限配置权限是做这块App的基础少一个都可能直接闪退。Manifest里我会这么写uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.RECORD_AUDIO / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_CAMERA / uses-permission android:nameandroid.permission.FOREGROUND_SERVICE_MICROPHONE / uses-permission android:nameandroid.permission.WAKE_LOCK / uses-permission android:nameandroid.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION /权限清单里最容易漏的是FOREGROUND_SERVICE_CAMERA。Android 14以后如果不声明前台服务启动直接异常。定位权限主要用来在视频上叠加坐标水印不加也能录但行车场景建议保留。还有一项要注意运行时权限必须动态申请尤其是Android 6.0以后的CAMERA和RECORD_AUDIO不能只写在Manifest里。4.2 录制主体代码结构工程里核心类是RecorderService它在onStartCommand里启动一个HandlerThread作为后台线程避免在Main线程操作Camera。音频不是必需我先给一个纯视频的最小版本private fun configureEncoder(width: Int, height: Int): MediaCodec { val format MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height) format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000) format.setInteger(MediaFormat.KEY_FRAME_RATE, 30) format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) val codec MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) return codec }MediaCodec配置完成后通过createInputSurface拿到一个Surface这个Surface直接交给Camera2作为输出目标。然后Camera2的CaptureSession绑定这个Surface再设置TEMPLATE_RECORD请求加上连续自动对焦和固定帧率。整个链路完全在后台线程跑不依赖任何View预览。编码器输出那一端要开一个while循环不断取出编码后的数据用MediaMuxer写文件。注意writeSampleData的buffer需要设置position和limit这个细节很多人漏了写入端会报错甚至静默丢数据val index codec.dequeueOutputBuffer(info, TIMEOUT) if (index 0) { val buffer codec.getOutputBuffer(index) if (buffer ! null info.size 0) { buffer.position(info.offset) buffer.limit(info.offset info.size) muxer?.writeSampleData(trackIndex, buffer, info) } codec.releaseOutputBuffer(index, false) }Muxer分段时我会在segment线程里先通知主循环停止写入然后muxer.stop、muxer.release紧接着new出一个新Muxer重新addTrack并start。这个过程不能和编码输出线程并发操作同一个muxer所以我用了一个锁对象包住整个切换逻辑。4.3 关键参数速查表我自己调来调去第一版最稳的参数组合是下面这套直接抄作业可以少走很多弯路参数取值说明分辨率1920x1080性价比最高2K对热量和存储压力大帧率30fps行车足够夜间可以降25fps视频码率4Mbps1080P下画质与体积的平衡点音频码率128kbps人声清晰文件体积不大I帧间隔1秒回放拖动时间轴时更流畅分段时长5分钟事故锁定粒度合理文件损坏影响小存储清理阈值50GB低于阈值开始循环覆盖最早文件这里特别提一句码率。很多人喜欢开满结果一个视频文件动辄1GB存储和回放都压力山大。行车记录的核心诉求是“看得清车牌”4Mbps对1080P白天场景足够了。夜间如果噪点多可以开夜间模式把码率提到6Mbps但夜枭模式的权重优先级要靠自己调预览参数优先保证曝光时间。4.4 开发调试时的建议调试这种应用最怕在模拟器上跑。安卓虚拟机不是不能用但大部分模拟器的虚拟摄像头对Camera2接口的模拟太残缺很多标准的setRepeatingRequest调用在虚拟机里表现完全不一样更别说什么帧率控制。我自己是直接真机测试。如果你非得折腾虚拟机先把镜像的联网问题搞定再把虚拟摄像头驱动配好否则连CameraManager.getCameraIdList都返回空列表。真机调试省下的时间足够你多测三遍息屏逻辑。另外一个容易被忽略的习惯一定要用adb logcat抓崩溃日志。后台服务一旦被系统杀少数情况有日志多数情况根本没异常只是ActivityManager在某个瞬间把进程静默回收了。这时候去抓前台服务相关的ActivityManager标签比盲猜有用得多。5. 常见问题与排查实录5.1 刚录几分钟服务就被系统杀掉这类问题最常见的现象是录了五分钟回来一看通知没了进程没了但Logcat里看不到崩溃堆栈。第一嫌疑是系统省电策略第二嫌疑是后台服务没有正确持锁。排查步骤是先用下面的命令确认服务当时的运行状态adb shell dumpsys activity processes | grep -A 20 Record看到服务状态为“cached”甚至直接消失就是被系统回收了。这时候检查清单前台服务是否启动成功、通知是否常驻、电池优化是否白名单、厂商后台管理里是否允许自启动。如果这些都过了再考虑START_STICKY重启逻辑是否生效。还有一个很隐蔽的问题部分手机在锁屏界面解锁后广播Receiver把服务当作“自动清理任务”杀掉了。我最后的对策是提供一个“息屏录制并保持黑屏”的炭屏模式紧急情况下用户锁屏前先启动黑屏Activity让系统认为App一直处于前台可见状态从根源避开后台清理。5.2 视频文件损坏回放不了行车场景最怕的就是出事后视频打不开。我用MediaMuxer写MP4时遇到过一个真实案例车辆断电瞬间文件正好在写入关键头结果整段文件几百MB全废。这属于MP4容器结构本身的弱点断电丢最后一点数据会连累整个文件。对策是多管齐下一是把分段时长缩短到5分钟损坏粒度小二是录制过程中定期对当前文件做一次“刷新”把关键帧和moov信息尽量前置三是再加一道保险录制时同步生成一个低码率的代理文件关键时刻至少能看个大概。真正重要的证据文件还可以用ffprobe这类工具离线检测损坏情况但这属于事后处理了。5.3 掉帧、花屏、音画不同步后台录像步调一旦乱套最先坏的是录制质量。掉帧多半是编码器吃不住实时负载我会把帧率从30降到25测试同时看logcat里有没有MediaCodec的“no output buffer available”提示。如果是这类字眼那就是输出buffer耗尽说明码率设置得太高或者设备芯片太弱。花屏的情况更多出现在H.264码流I帧间隔设置过大时。I帧间隔设为1秒能明显改善拖动进度条后出现的花屏和搜索卡顿。音画不同步则基本是音频和视频的PTS基准不一致我最后统一用SystemClock.elapsedRealtime()作为两份数据的基准时间问题就消失了。5.4 版本差异与应用市场审核后台录像类应用在Google Play和应用商店都会遇到敏感权限审核。前台服务类型、后台摄像头权限、存储读写权限都要在隐私政策里明确说明否则很容易被驳回。国内应用市场还会要求你说明常驻通知的用途这里一定要把“行车记录、停车监控、紧急事件锁定”讲得清清楚楚。适配时还要留意Android 13后的通知权限Android 14后的前台服务启动限制以及即将要考虑的Android 16对后台传感器变化的限制。每一个新版本发布前用一台高版本真机跑一圈检查“锁屏-亮屏-锁屏”循环是最低成本的测试方案。还有一个心得市场里很多人会把这类App包装成“防碰瓷记录仪”但从开发角度这本质上是一个需要长时间侵占摄像头资源的应用必须把用户告知和停止录制的流量入口做得足够醒目。宁可多一步确认弹窗也别让用户以为闪退而留下差评。结语说说我在这个项目上最大的体会。做之前我以为难的是编码和分段做之后才发现真正的难点是Android系统本身对后台资源越来越严格的围堵以及各家手机厂商五花八门的电源策略。第一版完全可以使用MediaRecorder把录制链路跑通等稳定了再替换成Camera2 MediaCodec千万不要上来就想把碰撞检测、移动侦测全塞进去。凡是能在后版本补的功能都会帮你少失眠几个晚上。最后再分享一个实用技巧调试息屏录像时别一直插着电脑盯着屏幕因为一旦用USB连接电脑部分机器会强制改变电源策略跑出来的结果不代表用户真实场景。我把手机固定到支架上、用充电宝供电、关闭开发者选项里的“不锁定屏幕”连跑一整晚第二天看录像覆盖记录和温度曲线才能还原最真实的行车工况。