Android 实现 Service 中的后台录像功能 —— 项目详解与实战教程
本文将带你一步步实现一个支持在后台运行的摄像头录像功能,即使应用最小化、熄屏甚至切换到其他 App,录像仍可继续进行。
一、项目介绍
在一些特定场景下,如行车记录仪、隐私安防、视频监控、执法记录仪等,Android 设备需要具备在后台持续录像的能力。
这种功能的关键是:即使应用退到后台,也能持续使用摄像头并保存录像文件,甚至可以实现无界面(Headless)运行。
本项目的目标是:
构建一个在
Service
中运行的摄像头后台录像模块,启动后自动进行摄像头初始化与录制,用户可在通知栏控制录像启停,支持前台服务提升系统存活性。
二、相关技术知识详解
1. Camera API 概览
Android 提供两套摄像头 API:
API | 说明 | 优点 | 缺点 |
---|---|---|---|
Camera(Camera1) | 较旧,Android 5.0 以下默认使用 | 简单直接,易于实现 | 功能少,不支持新硬件能力 |
Camera2(推荐) | Android 5.0+ 引入,功能强大 | 高度可控,支持帧率、手动曝光等 | 编码复杂,学习曲线陡峭 |
本项目使用 Camera2 API,配合 MediaRecorder
实现录制视频。
2. MediaRecorder 组件
-
用于录音、录像等媒体采集
-
与
Camera
结合可录制音视频 -
支持输出格式设置(如 MPEG_4)
3. Foreground Service(前台服务)
-
Android 8.0+ 后系统限制后台服务,需使用前台服务防止被杀死
-
需要常驻通知栏
-
使用
startForeground()
启动
4. 权限需求
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
动态申请:
-
CAMERA
-
RECORD_AUDIO
-
WRITE_EXTERNAL_STORAGE
5. Surface + SurfaceTexture
-
Surface 是摄像头输出图像的目标
-
在后台无法使用 UI 显示
SurfaceView
,因此通过SurfaceTexture
+Surface
绕过可视化限制
三、项目实现思路
-
创建 ForegroundService,常驻后台并启动摄像头录像
-
使用 Camera2 API 初始化摄像头并连接 MediaRecorder
-
配置 MediaRecorder 录制音视频
-
利用 SurfaceTexture 作为虚拟图像输出目标
-
持续保存录像文件到本地
-
在通知栏提供控制按钮:开始 / 停止录像
四、整合代码实现(含详细注释)
项目包结构如下:
com.example.videobackground
│
├── MainActivity.kt // 启动入口
├── VideoService.kt // 后台录像前台服务
├── CameraHelper.kt // 封装 Camera2 + MediaRecorder 逻辑
└── utils/
└── FileUtils.kt // 生成文件名工具
1. MainActivity.kt
package com.example.videobackground
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 请求必要权限
ActivityCompat.requestPermissions(
this,
arrayOf(
android.Manifest.permission.CAMERA,
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
),
100
)
// 启动后台录像服务
startService(Intent(this, VideoService::class.java))
finish()
}
}
2. VideoService.kt
package com.example.videobackground
import android.app.*
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
class VideoService : Service() {
private lateinit var cameraHelper: CameraHelper
override fun onCreate() {
super.onCreate()
// 启动前台通知,避免被系统杀死
createNotificationChannel()
val notification = NotificationCompat.Builder(this, "video_channel")
.setContentTitle("后台录像进行中")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build()
startForeground(1, notification)
// 启动摄像头并录像
cameraHelper = CameraHelper(this)
cameraHelper.startRecording()
}
override fun onDestroy() {
super.onDestroy()
cameraHelper.stopRecording()
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
"video_channel",
"后台录像服务",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
}
3. CameraHelper.kt
package com.example.videobackground
import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.camera2.*
import android.media.MediaRecorder
import android.os.Environment
import android.util.Size
import android.view.Surface
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class CameraHelper(private val context: Context) {
private lateinit var cameraDevice: CameraDevice
private lateinit var mediaRecorder: MediaRecorder
private lateinit var cameraSession: CameraCaptureSession
private val cameraManager: CameraManager =
context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
private lateinit var surface: Surface
fun startRecording() {
setupMediaRecorder()
openCamera()
}
fun stopRecording() {
cameraSession.close()
cameraDevice.close()
mediaRecorder.stop()
mediaRecorder.release()
}
private fun setupMediaRecorder() {
mediaRecorder = MediaRecorder()
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
mediaRecorder.setOutputFile(FileUtils.getOutputMediaFile().absolutePath)
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
mediaRecorder.setVideoSize(1280, 720)
mediaRecorder.setVideoFrameRate(30)
mediaRecorder.setVideoEncodingBitRate(5 * 1024 * 1024)
mediaRecorder.prepare()
}
private fun openCamera() {
val cameraId = cameraManager.cameraIdList[0] // 默认使用后摄
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
cameraDevice = device
startSession()
}
override fun onDisconnected(device: CameraDevice) {
device.close()
}
override fun onError(device: CameraDevice, error: Int) {
device.close()
}
}, null)
}
private fun startSession() {
val texture = SurfaceTexture(10) // 虚拟Surface,无需显示
texture.setDefaultBufferSize(1280, 720)
surface = Surface(texture)
val recorderSurface = mediaRecorder.surface
cameraDevice.createCaptureSession(
listOf(recorderSurface, surface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
cameraSession = session
val builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
builder.addTarget(recorderSurface)
session.setRepeatingRequest(builder.build(), null, null)
mediaRecorder.start()
}
override fun onConfigureFailed(session: CameraCaptureSession) {}
}, null
)
}
}
4. FileUtils.kt
package com.example.videobackground.utils
import android.os.Environment
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
object FileUtils {
fun getOutputMediaFile(): File {
val mediaStorageDir = File(Environment.getExternalStorageDirectory(), "VideoRecords")
if (!mediaStorageDir.exists()) {
mediaStorageDir.mkdirs()
}
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
return File(mediaStorageDir.path + File.separator + "VID_$timeStamp.mp4")
}
}
五、方法解读
-
MainActivity::onCreate
请求权限并启动VideoService
。 -
VideoService::onCreate
创建前台服务并初始化CameraHelper
。 -
CameraHelper::startRecording()
设置MediaRecorder
,打开摄像头,创建会话并开始录制。 -
setupMediaRecorder()
配置音视频编码、分辨率、帧率、输出路径。 -
openCamera()
使用Camera2
打开摄像头,监听回调。 -
startSession()
创建CameraCaptureSession
并绑定MediaRecorder
输出,启动录像。
六、项目总结与拓展
✅ 项目优势
-
兼容 Android 5.0 以上系统
-
使用
Camera2 + MediaRecorder
实现高质量录像 -
支持后台运行与熄屏持续录像
-
使用
ForegroundService
提高任务存活率 -
支持本地保存录像文件,方便后期查看/上传
🔧 拓展建议
-
支持选择前/后摄
-
添加 UI 控制录像按钮
-
接入后台上传录像功能
-
自动分段录像(每隔10分钟一个文件)
-
添加错误重试机制(摄像头失效自动重启)
-
结合 Room 保存录像文件元数据
-
支持定时停止 / 定时启动
七、结语
后台录像是一个对权限、系统资源管理要求较高的技术点,本项目通过使用 Camera2 + MediaRecorder + ForegroundService 的组合,为 Android 设备构建了一个稳定、可扩展的后台录像解决方案。
这不仅适用于安全监控、行车记录,也为未来的 AI 视觉采集、智能录像等方向打下坚实基础。
如果你喜欢这篇项目文章,欢迎点赞、收藏和留言提问。后续我还将继续推出更多项目实战系列(例如:后台音频录制、分段推流、视频实时上传等)。
如需我将此项目整理为 Markdown 或用于博客发布的版本,也可以告诉我哦!🚀