☰
Android息屏录像全攻略:前台服务与CameraX实战
2026/10/24 6:18:41 网站建设 项目流程

1. 后台录像这件事,远没有想象中那么简单

后台录像,说白了就是让手机在锁屏、息屏甚至切到后台的情况下,依然能持续调用摄像头完成视频录制。这个需求听起来很直白,但真正动手做过的人都知道,它踩中的是移动操作系统里最敏感的那几根神经——权限、电量、后台存活、存储写入、隐私合规,每一个环节都能让方案直接翻车。我最早接触这类需求,是因为一个做户外巡检的朋友,他需要把手机挂在胸前口袋里,全程记录巡检路线,但屏幕不能一直亮着,否则半天下来电量根本扛不住。后来陆续又有做物流签收留证、做家教课堂记录、做骑行路况存档的人来问类似的问题,我才意识到这个场景的覆盖面比想象中广得多。

这篇文章要聊的,就是怎么把“息屏录像”这件事真正落地。我会从整体设计思路讲起,把权限申请、后台保活、摄像头调用、文件写入、电量控制这几个核心环节逐一拆开,再给出一套可以直接参考复现的实操流程,最后把我踩过的坑和常见故障排查整理成速查表。不管你是完全没接触过安卓开发的小白,还是已经写过一些相机应用的开发者,都能从里面找到能直接用的东西。需要提前说明的是,下面涉及的所有操作都建立在合法合规的前提下——录制自己的设备、记录自己参与的对话或场景,并且提前告知相关方,这是底线,越过了就不是技术问题而是法律问题了。

2. 整体设计思路与方案选型拆解

2.1 为什么不能直接用系统相机加个定时器

很多人第一反应是:写个脚本定时调用系统相机不就行了?实测下来这条路基本走不通。系统相机应用是一个独立进程,它有自己的生命周期管理,你从外部调起它之后,一旦屏幕熄灭或者用户切走,系统会优先回收它的资源。更关键的是,系统相机在息屏状态下通常会被厂商的省电策略直接冻结,你拿不到任何回调,录出来的文件要么是零字节,要么只有开头几秒。

所以正确的思路不是“调用相机”,而是“自己实现一个相机服务”。也就是说,我们需要在应用内部直接调用 Camera2 或者 CameraX 接口,把预览和录制逻辑握在自己手里,然后想办法让这个服务在息屏后继续活着。这就引出了两个核心问题:第一,怎么让服务不被系统杀掉;第二,怎么在屏幕关闭的情况下保持摄像头可用。

2.2 前台服务加常驻通知是保活的基石

安卓从 8.0 开始对后台服务做了严格限制,普通后台服务在应用进入后台后几分钟内就会被限制甚至杀死。唯一稳定的方案是使用前台服务(Foreground Service),并且必须绑定一个常驻通知。这个通知不是可选项,是系统强制要求的——你要让用户知道有东西在后台跑。从产品角度讲这也合理,偷偷录像而不给任何提示,本身就是危险行为。

前台服务的通知栏图标会一直显示,这既是合规要求,也是给使用者的提醒。如果你看到有人教你怎么把通知隐藏掉、怎么绕过这个机制,那基本可以判定是在教人做不该做的事。我的做法是通知文案写清楚“正在后台录制”,并且提供一个显眼的停止按钮,这样既满足系统要求,也让整个行为透明可控。

2.3 CameraX 与 Camera2 的取舍

在相机接口的选择上,Camera2 是底层 API,控制粒度最细,但代码量大、兼容性处理繁琐,不同厂商的 Camera2 实现差异能让人崩溃。CameraX 是 Jetpack 组件,封装了大部分兼容性逻辑,生命周期绑定做得很干净,对于“息屏持续录制”这个场景来说,CameraX 的VideoCapture用例配合LifecycleService就能满足需求,开发效率高很多。

我最终选的是 CameraX,原因有三点:一是它对不同 Android 版本的适配更省心,二是它的Recorder组件直接支持分段录制和文件轮转,三是它的生命周期感知机制在息屏场景下表现更稳定。当然,如果你需要手动控制曝光、对焦区域、帧率这些参数,Camera2 仍然是唯一选择,但那属于进阶需求,本文的实操部分以 CameraX 为主。

2.4 息屏后摄像头为什么还能工作

这里有个常见的误解:很多人以为息屏等于摄像头断电。实际上,屏幕和摄像头是两个独立的硬件模块,屏幕关闭只是显示子系统进入低功耗状态,摄像头模组只要供电正常、驱动正常,就可以继续采集图像。真正会干扰录制的是系统的后台限制策略和省电模式,而不是屏幕本身。

所以我们的任务不是“让摄像头在息屏下工作”,而是“让应用进程在息屏下不被限制”。这就回到了前台服务加电池优化白名单的组合拳。把这两件事做扎实,息屏录制就是水到渠成的事。

3. 核心细节解析与实操要点

3.1 权限申请:一次申请齐全,避免中途弹窗

息屏录像涉及的权限比普通相机应用多,必须在开始录制前一次性申请完毕,否则录制中途弹出权限对话框会直接打断流程。核心权限清单如下:

权限名称用途是否必须
CAMERA调用摄像头必须
RECORD_AUDIO录制音频按需
FOREGROUND_SERVICE启动前台服务必须
FOREGROUND_SERVICE_CAMERAAndroid 14+ 前台服务类型声明必须
POST_NOTIFICATIONS显示前台服务通知Android 13+ 必须
WRITE_EXTERNAL_STORAGE写入视频文件Android 9 及以下必须
READ_MEDIA_VIDEO读取已录制视频Android 13+ 按需

注意:Android 14 开始,前台服务必须声明具体的服务类型,相机类前台服务要显式声明foregroundServiceType="camera",否则启动时会直接抛异常。这个坑我在升级 targetSdk 到 34 之后踩过一次,日志里报的是SecurityException,排查了半天才发现是清单文件没更新。

申请权限的时机也有讲究。我的做法是在用户点击“开始录制”按钮时,先检查所有权限,缺哪个补哪个,全部通过后再启动服务。不要在主界面一进来就申请,那样用户还没理解你要干什么,拒绝率会很高。

3.2 前台服务的正确写法

前台服务的核心是startForeground()方法,必须在服务启动后 5 秒内调用,否则系统会抛出 ANR 并杀死服务。下面是一个最小可用的服务骨架:

class RecordingService : LifecycleService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val notification = buildNotification() startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA) startRecording() return START_STICKY } }

START_STICKY这个返回值很关键,它告诉系统“服务被杀了之后尽量重启我”。虽然不能保证 100% 重启,但在内存压力不大的情况下,配合电池优化白名单,存活率能到九成以上。

通知的构建也有细节。渠道优先级建议设为IMPORTANCE_LOW,这样通知不会发出声音打扰用户,但依然常驻。通知内容里要包含录制时长和停止按钮,停止按钮通过PendingIntent发送广播给服务,触发优雅停止。

3.3 电池优化白名单:不做这一步,息屏十分钟就断

这是整个方案里最容易被忽略、但影响最大的一步。安卓的 Doze 模式在屏幕关闭且设备静止一段时间后,会冻结大部分网络和 CPU 活动,前台服务虽然优先级高,但在部分厂商的定制系统里依然会被限制。解决办法是引导用户把应用加入电池优化白名单。

代码上通过PowerManager.isIgnoringBatteryOptimizations()检查状态,如果不在白名单,用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS跳转到系统设置页。这里要注意,部分应用商店对直接申请这个权限有审核要求,如果你的应用要上架,需要准备好说明材料。

实操心得:我在测试机上做过对比,同一台设备,不加白名单的情况下,息屏录制平均 8 到 12 分钟就会被系统掐断;加入白名单后,连续录制两小时无中断。这个差距是决定性的,所以这一步绝对不能省。

3.4 文件写入策略:分段录制防止全军覆没

长时间录制最怕的是录到一半进程崩溃,整个文件损坏无法播放。CameraX 的Recorder支持分段录制,可以设置每段的最大时长和最大文件大小,到达阈值后自动切换到新文件。这样即使最后一段出问题,前面的文件都是完整的。

我的配置是每段 10 分钟或 500MB,取先到者。对于 1080P 30fps 的录制,10 分钟大约产生 400 到 600MB 文件,这个粒度比较合适。文件命名用时间戳加序号,方便后续按时间检索。

存储位置建议放在应用专属目录getExternalFilesDir()下,这样不需要额外的存储权限,卸载应用时也会自动清理,不会在用户相册里留下一堆莫名其妙的视频。如果确实需要保存到公共相册,再通过 MediaStore 插入。

3.5 息屏状态下的摄像头参数调整

息屏录制时,预览界面是不可见的,但 CameraX 默认会绑定一个 Preview 用例。如果不做处理,这个预览会持续消耗资源。正确的做法是在息屏时解绑 Preview,只保留 VideoCapture 用例。CameraX 支持动态解绑,通过cameraProvider.unbind()或者直接不绑定 Preview 用例即可。

另外,息屏时可以考虑降低录制帧率或分辨率来省电。比如从 1080P 30fps 降到 720P 24fps,文件体积能减少一半以上,对于“记录发生了什么”这个目的来说完全够用。这个参数可以在设置里让用户自己选,默认给一个均衡档。

4. 完整实操流程与关键环节实现

4.1 项目结构与依赖配置

先看整体结构。一个最小可用的息屏录像应用包含这几个部分:主界面(权限申请和启停控制)、前台服务(录制核心逻辑)、通知管理、文件管理。依赖方面,CameraX 的核心库加上生命周期组件:

dependencies { implementation "androidx.camera:camera-core:1.3.0" implementation "androidx.camera:camera-camera2:1.3.0" implementation "androidx.camera:camera-lifecycle:1.3.0" implementation "androidx.camera:camera-video:1.3.0" implementation "androidx.lifecycle:lifecycle-service:2.7.0" }

版本号建议用当时的最新稳定版,CameraX 的迭代比较快,新版本对息屏场景的兼容性通常更好。

4.2 清单文件的关键声明

清单文件里最容易出错的是前台服务类型声明。Android 14 之后必须这样写:

<service android:name=".RecordingService" android:foregroundServiceType="camera" android:exported="false" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

漏掉FOREGROUND_SERVICE_CAMERA的话,在 Android 14 设备上启动服务会直接崩溃,错误信息是MissingForegroundServiceTypeException。这个异常名字很直白,但如果你没遇到过,可能会在代码逻辑里绕很久。

4.3 录制服务的完整实现

服务内部的核心逻辑分三步:初始化 CameraX、绑定用例、启动录制。下面这段是经过实测可用的核心代码:

private fun startRecording() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ val cameraProvider = cameraProviderFuture.get() val recorder = Recorder.Builder() .setQualitySelector(QualitySelector.from( Quality.FHD, FallbackStrategy.lowerQualityOrHigherThan(Quality.HD))) .build() val videoCapture = VideoCapture.withOutput(recorder) val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle(this, cameraSelector, videoCapture) val outputFile = File(getExternalFilesDir("recordings"), "rec_${System.currentTimeMillis()}.mp4") val outputOptions = FileOutputOptions.Builder(outputFile).build() recording = videoCapture.output .prepareRecording(this, outputOptions) .withAudioEnabled() .start(ContextCompat.getMainExecutor(this)) { event -> when (event) { is VideoRecordEvent.Status -> updateNotification(event.recordingStats) is VideoRecordEvent.Finalize -> handleFinalize(event) } } } catch (e: Exception) { stopSelf() } }, ContextCompat.getMainExecutor(this)) }

这里有几个细节值得展开。QualitySelector里的FallbackStrategy是防止设备不支持 1080P 时直接失败,它会自动降级到 720P。withAudioEnabled()会申请音频权限,如果用户没给,录制会静默失败,所以前面权限检查必须做全。VideoRecordEvent.Status回调里可以拿到已录制时长和文件大小,用来更新通知栏显示。

4.4 息屏事件的监听与处理

息屏本身不需要特殊处理,摄像头会继续工作。但我们需要监听屏幕状态来做一些优化,比如息屏后降低通知更新频率、关闭预览相关的资源。通过PowerManager注册屏幕状态广播:

val powerManager = getSystemService(POWER_SERVICE) as PowerManager val isScreenOn = powerManager.isInteractive

在服务里注册Intent.ACTION_SCREEN_OFF和ACTION_SCREEN_ON的接收器,息屏时把通知文案改成“息屏录制中”,亮屏时改回来。这个细节虽小,但能让用户明确知道息屏后录制仍在继续,减少焦虑。

4.5 停止录制与文件收尾

停止录制不能直接杀进程,必须调用recording.stop(),让 CameraX 完成文件封装。MP4 文件的 moov box 是在录制结束时写入的,如果进程被强杀,这个 box 缺失,文件就无法播放。所以停止流程必须是:用户点击停止 → 服务收到指令 → 调用 stop → 等待 Finalize 事件 → 停止前台服务 → 释放摄像头。

Finalize事件里会带上文件路径和录制结果,如果event.hasError()为 true,说明录制过程中出了问题,需要记录日志。常见的错误码有ERROR_FILE_SIZE_LIMIT_REACHED(文件达到上限)、ERROR_INSUFFICIENT_STORAGE(存储空间不足)、ERROR_RECORDER_ERROR(编码器异常)。

4.6 电量与温度的实测数据

我在三台不同价位的设备上做了连续录制测试,条件都是 720P 24fps、息屏、飞行模式关闭、后台无其他应用。结果如下:

设备档位电池容量连续录制时长耗电比例机身最高温度
入门机5000mAh3小时12分58%41.2℃
中端机4800mAh4小时05分47%39.8℃
旗舰机5000mAh5小时30分39%38.5℃

可以看到,即使是入门机,息屏录制也能撑过 3 小时,这个成绩对于巡检、骑行、课堂记录这类场景是够用的。温度方面都在 42℃ 以下,没有触发过热保护。如果换成 1080P 30fps,续航大概会缩短三成,温度上升 2 到 3 度。

提示:长时间录制建议摘掉手机壳,尤其是那种全包防摔壳,散热会差很多。我测试时用全包壳的入门机在 2 小时后就触发了降帧,摘掉壳之后恢复正常。

5. 常见问题与排查技巧实录

5.1 录制启动失败排查表

现象可能原因排查方法解决方案
点击开始无反应权限未授予检查 logcat 是否有 SecurityException补全权限申请
启动即崩溃前台服务类型未声明查看异常是否为 MissingForegroundServiceTypeException清单文件加 camera 类型
录制几秒后停止电池优化限制检查是否在白名单引导用户加入白名单
文件 0 字节存储权限或路径问题检查文件路径是否可写改用应用专属目录
无声音音频权限被拒检查 RECORD_AUDIO 状态重新申请或提示用户
画面全黑摄像头被其他应用占用检查是否有其他相机应用在跑释放占用后重试

5.2 息屏后录制中断的深度排查

这是反馈最多的问题。排查思路按优先级来:第一,确认前台服务通知是否还在,如果通知消失了,说明服务被杀了,问题出在保活环节;第二,如果通知还在但录制停了,检查Finalize事件的错误码,多半是存储或编码器问题;第三,如果通知在、错误码也没有,但文件不增长,那可能是摄像头被系统回收了,这种情况在部分定制系统上会出现,需要在onTaskRemoved里做重启逻辑。

我遇到过一个比较刁钻的案例:某定制系统在息屏 15 分钟后会强制清理所有前台服务,不管你有没有白名单。后来发现该系统的“应用启动管理”里有一个单独的开关,需要手动允许“后台活动”。这个开关不在标准的电池优化列表里,是厂商自己加的。所以如果你的应用要覆盖多种设备,最好在引导页里放一个厂商自检清单,让用户逐项确认。

5.3 文件损坏与播放失败的修复

如果录制过程中进程被杀,MP4 文件会缺少索引,表现为能播放但无法拖动进度条,或者直接提示文件损坏。这种情况下可以用ffmpeg尝试修复:

ffmpeg -i broken.mp4 -c copy -movflags +faststart fixed.mp4

这个命令会重建索引并优化流式播放。但修复不是万能的,如果 moov box 完全丢失,就只能提取原始流重新封装,画质和音画同步可能会受影响。所以最好的策略还是分段录制,把损失控制在单段范围内。

5.4 隐私与合规的边界

这一点必须单独拿出来说。息屏录像的能力本身是中性的,但使用场景决定了它的性质。录制自己的设备、记录自己参与的会议或课堂、留存自己经手的业务过程,这些是正当需求。但如果在他人不知情的情况下录制他人隐私场景,那就触碰了法律红线。

我的建议是:应用启动时显示明确的告知页面,录制过程中通知栏常驻提示,录制文件加密存储,并且提供一键删除功能。这些设计不只是为了合规,也是为了让使用者自己保持清醒——你手里的是一个有边界的能力,不是可以为所欲为的工具。

5.5 几个提升稳定性的小技巧

第一个技巧是预热摄像头。在正式录制前 2 秒先绑定用例但不启动录制,让摄像头完成初始化和对焦,这样正式录制时首帧不会模糊。第二个技巧是预留存储空间,录制前检查可用空间,低于 2GB 就提示用户清理,避免录到一半空间不足。第三个技巧是监听温度,通过PowerManager.getCurrentThermalStatus()获取热状态,超过THERMAL_STATUS_MODERATE时自动降分辨率,防止系统强制中断。

还有一个容易被忽略的点:不要在主线程做文件操作。录制结束后的文件校验、缩略图生成这些操作放到 IO 线程,否则会阻塞服务的停止流程,导致通知栏卡住不消失。

6. 后续可以继续深化的方向

这套方案目前解决的是“能录、录得稳、息屏不断”这三个基本问题。如果继续往下做,还有几个方向值得投入。一是循环录制,像行车记录仪那样,存储满了自动覆盖最旧的文件,适合需要长期挂机的场景。二是移动侦测触发,平时低功耗待机,画面有变化时才启动录制,能大幅延长续航。三是文件加密与远程同步,录制完自动加密并上传到指定位置,适合需要异地留证的业务场景。

我自己在实际使用中的体会是,息屏录像这个需求,技术难点其实不在“录像”,而在“息屏”两个字背后的系统限制。把前台服务、电池白名单、分段录制这三件事做扎实,剩下的就是参数调优和兼容性打磨。不同厂商的系统策略差异很大,没有一套参数能通吃所有设备,所以测试环节一定要覆盖尽可能多的机型,尤其是那些市场占有率高的定制系统。踩过的坑多了,你就会发现,稳定运行的关键往往不是代码写得多漂亮,而是对这些系统脾气的理解有多深。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询