
简介本资源是一个基于Android平台的行车记录仪系统完整实现方案面向移动开发初学者与Android应用实践者解决普通用户利用闲置智能手机替代专用硬件实现行车视频录制与管理的需求。项目采用Java语言开发基于ADT环境构建支持Android 4.5及以上系统具备录像分段、时长自定义、滚动覆盖保存及本地回放等核心功能可直接编译运行。压缩包共1381个文件包含29个Java源文件主逻辑与Activity控制、55个XML布局与配置文件界面与权限定义、260张PNG图标与UI资源、87个编译生成的class文件以及jar库、properties配置和prefs偏好设置等结构完整、模块清晰涵盖从摄像头调用、MediaRecorder封装到文件管理的全链路实现。目前已有128人下载学习适合用于Android多媒体开发实战、课程设计参考或车载类App二次开发基础模板。 做行车记录仪App其实没有想象中那么玄学。一个Android手机、一个车载支架、再加一个充电头配合自研的一套录制系统就能顶替市面上几百块的行车记录仪。这个“基于Android的手机摄像头实现行车记录仪系统”的项目干的就是这件事用手机摄像头完成持续录像、循环覆盖、碰撞锁定、GPS数据记录等核心功能。成本几乎为零代码还能复用对Android开发者来说既是一个练手的好项目也是可以快速落地的车载方案原型。这个项目适合两类人一是想自己动手给车做个备用记录仪的开发者二是准备做车载相关的Android应用、想了解Camera2、MediaRecorder、前台服务这套完整链路的朋友。下面我从需求拆解开始一步步把整个系统的实现思路和技术细节说清楚最后附上我在实际调试中踩过的坑和排查方法。1. 项目本质与需求拆解1.1 行车记录仪真正要解决的核心问题很多人觉得行车记录仪就是“一直录像”其实真要按照这个思路做做出来的东西根本没法用。我从实际使用的角度拆解一下一个合格的记录仪系统必须解决四件事。一是循环录像。车载存储空间是有限的记录仪必须做到满了以后自动覆盖最早的文件而不是录满就停。这就要求在存储策略上做“时间片轮转”每个文件有固定的时长系统定期检查磁盘剩余空间按时间顺序清理最老的录像。二是证据锁定。发生剐蹭或事故的时候这一段视频不能被循环录像覆盖掉。卡车记录仪的解决方案是碰撞传感器触发“事件录像”把当前片段和前一分钟、后一分钟的视频复制到独立目录或者给文件打上标记清理时跳过。手机端的加速度传感器完全可以实现同样的效果。三是断电保护。车载记录仪的点烟器供电在熄火后会断电这时候要干净利落地保存当前录像文件并退出录制不能留下一个损坏的MP4。Android平台上的做法是监听ACTION_POWER_DISCONNECTED广播在断电瞬间完成文件收尾。四是状态可视化。用户需要知道“它正在录”尤其是手机方案里用户本来就对稳定性有疑虑。所以界面上要有明确的录制状态、时间、GPS信号强度最好还能画中画显示方便随时确认。这个项目最终落地的方案就是用前台服务Camera2MediaRecorder传感器位置服务把这几件事完整串起来。1.2 技术选型录制方案和相机API怎么选先说相机API。Android平台有Camera、Camera2、CameraX三条路可选。Camera老API已经废弃对于高分辨率视频和动态切换支持很差直接排除。Camera2是现代Android系统的主推方案灵活性最高代价是代码量大、状态机复杂。CameraX是对Camera2的封装内置生命周期绑定和用例抽象代码量能减少一半左右但细节控制能力弱一些。这个项目最终选了Camera2原因很简单车载场景要精确控制预览尺寸、录制尺寸、帧率和刷新切换CameraX在处理MediaRecorder的Surface连接时偶尔会有底层时序问题而Camera2在这方面的链路是完全可控的。再说录制方案。同样有三条路MediaRecorder、MediaMuxer、MediaCodecMediaMuxer手动编码。MediaRecorder是一个高封装度的终端类底层自动完成编码、封装、文件写入和Camera2的SurfaceMode组合使用代码量非常小稳定性也不错。MediaMuxer适合做多路音视频合成、自定义封装但对实时编码需要自己配合MediaCodec复杂度高很多。MediaCodec裸方案适合要对视频帧做AI处理或者特殊叠加的场景项目里如果只是普通录制属于杀鸡用牛刀。所以方案定为Camera2MediaRecorder的SurfaceMode这也是Android官方推荐的极简录制方案。1.3 业务模块划分我按照功能把系统拆成五个模块摄像头采集模块负责打开相机、选择输出尺寸、提供Surface录制模块负责MediaRecorder的启动、停止、切片和文件落盘持久化服务模块负责前台服务的创建、保活、断电监听传感器模块负责加速度碰撞检测和GPS记录文件管理模块负责循环清理和事件录像的锁定保护。这样划分的用意很直接摄像头和录制是两条相对独立的流水线前面一端崩溃不能影响文件端的完整性传感器和GPS是旁路功能即使摄像头出问题碰撞信息和最后的位置也要保留下来。下面逐个模块说实现细节。2. 摄像头采集与MediaRecorder录制链路2.1 打开摄像头尺寸选择和帧率权衡摄像头的打开逻辑不复杂但参数选择我最想重点说。Camera2里打开摄像头要用CameraManager.openCamera然后通过CameraDevice.createCaptureSession建立会话。关键是选择录制尺寸我实测了大量设备后稳定推荐1280x720分辨率、30帧率。为什么不推荐1080p因为手机摄像头在车载场景下是长期连续工作车内温度高1080p编码会让发热量显著增大。我在夏天车内实测1080p录制半小时后iPhone和多数安卓手机都会触发过热保护而720p能稳定跑完整段路程。而且行车记录仪的主要用途是看清车牌和事故过程720p在白天完全够用夜间稍差但也能分辨主要细节。帧率方面30fps是录制稳定性和流畅度的平衡点。60fps会让码率翻倍存储压力大而且弱光下噪点明显价值不大。选择尺寸的代码逻辑是这样的CameraManager manager (CameraManager) getSystemService(CAMERA_SERVICE); Size bestSize null; MediaCodecInfo.VideoCapabilities caps manager.getCameraCharacteristics(cameraId) .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getVideoSizes(MediaRecorder.VideoSource.SURFACE); // 从所有支持的尺寸中优先找1280x720 for (Size size : caps.getSupportedSizes()) { if (size.getWidth() 1280 size.getHeight() 720) { bestSize size; break; } }这里有个容易踩的坑getVideoSizes返回的不仅是录制尺寸还包括各尺寸对应的帧率范围。判断是否支持某个帧率要用VideoCapabilities.areSizeAndRateSupported(width, height, 30.0)。有些低端设备宣称支持1920x1080但30帧率实际上不支持强行录制会出现文件时间轴加速或慢放的问题。2.2 MediaRecorder核心配置与Surface模式MediaRecorder配合Camera2录制最大的优势是Surface模式下的“零拷贝”。Camera2的输出目标可以是一个SurfaceMediaRecorder通过createInputSurface创建另一个Surface把两个Surface加入同一个CaptureSession视频数据直接从相机传感器流转入编码器中间不经过CPU。关键配置如下MediaRecorder recorder new MediaRecorder(); recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); recorder.setVideoSize(1280, 720); recorder.setVideoFrameRate(30); recorder.setVideoEncodingBitRate(4_000_000); recorder.setAudioEncodingBitRate(96_000); recorder.setAudioSamplingRate(44_100); recorder.setOrientationHint(90); recorder.setOutputFile(filePath); Surface inputSurface recorder.getSurface(); recorder.prepare();参数里有两个最重要的。一个是setOrientationHint(90)手机横屏固定在支架上时如果不设置旋转角度录出来的视频方向就是歪的。取向宏是按照手机固定方向计算出来的横向支架时传感器方向和显示方向相差90度或270度具体值要根据设备实测调整。另一个是码率720p视频我按4Mbps给这个码率下画质清晰且文件体积可控。音频采样率设为44.1kHz兼容性最好48kHz在某些老设备上会有音画不同步问题。2.3 循环录像切片的几种实现方式行车记录仪不能一个文件录到天荒地老必须切成几分钟的片段。切片有三种实现我分别说优缺点。第一种是MediaRecorder自带的setMaxDuration(180000)到时间自动停止录制然后监听onInfo回调再启动下一个文件。这个方案简单但两个文件之间会有两三秒的切换空档关键瞬间容易被漏掉。第二种是Android 8.0之后MediaRecorder提供的setNextOutputFile可以让录制流程无缝衔接。做法是在当前文件录制前提前把下一个文件的file descriptor传进去当前文件到时自动关闭新的文件自动开始。实测切换延迟可以控制在几十毫秒内核心证据基本不会被漏掉是目前最优方案。第三种是MediaMuxer手动封装自由度最高但编码和封装都要自己做稳定性需要大量适配。我最终使用的是第二种方案配合一个“双文件预创建”机制录制启动时同时为当前文件和下一个文件分配路径然后调用setNextOutputFile这样切文件的时候不会卡在文件创建上。循环清理逻辑放在统一的FileManager里每个文件按命名规则带上时间戳REC_20240101_093000.mp4。清理线程每隔一分钟扫描一次存储目录按时间戳升序删除最早的文件直到剩余空间超过设定的阈值。阈值我设为500MB这个值确保在临界状态下还能继续录制至少15分钟。2.4 碰撞事件录像的保护机制碰撞检测触发后要做的不只是弹个提示核心是保护录像文件不被循环清理。我采用的做法是给文件加后缀标记。正常录像文件命名为REC_时间戳.mp4碰撞锁定的文件改名为EVENT_时间戳.mp4清理逻辑中只删除REC开头的文件EVENT文件默认保留90天再清理。这里有个细节如果在切片过程中刚好发生碰撞当前正在写的文件不能直接改名否则MediaRecorder的句柄会出问题。正确做法是记录下来“文件路径碰撞时间”等该文件录制完整停止后再通过renameTo改名。碰撞发生后我会同时把碰撞前一个文件和碰撞后一个文件都锁定这样事故前后的完整证据链都有。2.5 多摄像头支持前后切换行车记录仪还需要支持前摄和后摄切换例如一些人习惯后摄记录下来到车过程中的后方情况。Camera2的CameraManager.getCameraIdList可以列出所有摄像头通过LENS_FACING_FRONT和LENS_FACING_BACK区分方向。切换时先停止当前录制会话释放CameraDevice再重新打开新的摄像头并重启MediaRecorder。我实测切换耗时一般在1.5秒到2秒之间中间会有一段空白但这对行车记录仪场景可以接受。3. 后台常驻与断电保护车载场景的生命周期3.1 前台服务与Android 14的serviceType要求行车记录仪App一旦进入后台系统随时可能回收进程所以必须用前台服务保活。前台服务通过通知栏常驻通知让用户明确知道录制状态。这个项目里前台服务必须在onStartCommand中启动录制并返回START_STICKY这样即使系统杀掉了服务也会在稍后重新创建。这里要特别提醒Android 14targetSdk 34的改动前台服务必须声明foregroundServiceType摄像头和麦克风的类型是camera和microphone。Manifest里要这样写service android:name.RecordingService android:foregroundServiceTypecamera|microphone android:exportedfalse /同时运行时还需要权限if (Build.VERSION.SDK_INT 30) { ServiceCompat.startForeground(service, NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE); } else { service.startForeground(NOTIFICATION_ID, notification); }有个坑是Android 14要求启动camera或microphone类型前台服务时必须先获得对应的运行时权限否则会抛SecurityException。我在项目里把摄像头、麦克风、定位这三个权限做成启动前的强制检查缺一个就在引导页提示。3.2 熄屏录制与WakeLock, 以及后台摄像头限制行车记录仪在车辆行驶中通常是熄屏状态但Android系统进入休眠后会暂停CPU任务录制自然就断了。解决方案是获取PARTIAL_WAKE_LOCKPowerManager pm (PowerManager) getSystemService(POWER_SERVICE); wakeLock pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, DashCam:Recording); wakeLock.acquire();PARTIAL_WAKE_LOCK允许CPU持续运行但不点亮屏幕功耗控制比较理想。实测一个多小时录制CPU耗电在15%到20%左右配合车载充电完全无压力。还有一个隐藏很深的坑Android 9.0开始系统限制后台应用访问摄像头和麦克风。如果应用没有可见的Activity在前台打开摄像头会直接失败。行车记录仪App在前台启动录制后用户切到后台几分钟后会触发这个限制。解决方法是画中画PiP。PiP模式下系统认为应用仍然在前台有视觉组件可以继续访问摄像头。实现方式是录制服务启动后把当前Activity切换到PiP模式界面上显示一个小窗口里面是预览画面。这样既满足了系统限制又给用户提供了监控画面的作用。PictureInPictureParams params new PictureInPictureParams.Builder() .setAspectRatio(new Rational(16, 9)) .build(); enterPictureInPictureMode(params);Android 12之后的版本还需要在Manifest里声明android:supportsPictureInPicturetrue否则PiP按钮和接口会失效。3.3 断电监听干净地收尾断电保护是这个项目里最考验细节的地方。车载USB口在熄火断电的瞬间系统会先收到ACTION_POWER_DISCONNECTED广播然后过几秒才真正关机。这个时间窗口足够做文件收尾。我在Manifest里动态注册广播接收器IntentFilter filter new IntentFilter(); filter.addAction(Intent.ACTION_POWER_DISCONNECTED); filter.addAction(Intent.ACTION_SHUTDOWN); registerReceiver(powerReceiver, filter);收到断电广播后立刻做两件事一是把当前录制文件通过MediaRecorder.stop()落盘二是停止所有后台线程并置位“已断电”状态防止系统关机后又有残余线程写文件导致损坏。实测中从断电广播到文件完全保存需要约1到2秒而手机断电后还有短暂延迟才真正关机这个窗口是够的。需要注意的是很多车的点烟器口熄火后仍有短暂供电俗称“延时断电”从几秒到几分钟不等。广播方式只处理系统层面的断电车辆电源断开和手机检测到断电之间还有差别。更可靠的保障是硬件方案使用带超级电容的车载充电器它能在断电后为手机多续几秒的电。软件层面能做的是尽量缩短切片文件时长事故时最后一段录制的最大损失时间不会超过切片时长。我最终把切片定为1分钟兼顾文件大小和断电窗口。3.4 前台上部显示确保用户可见行车记录仪的监控界面应该是一个常驻的、信息丰富的界面。我的界面包含录制时间、事件状态、GPS信号、速度、录像分辨率、存储剩余空间。顶部用小圆点秒表显示正在录制的状态这个设计借鉴了运动相机的界面逻辑用户一眼能看出来系统是否在工作。4. GPS、加速度传感器与附加能力4.1 GPS数据记录与回放行车记录仪的证据价值很大程度来自位置和速度信息。我在服务里启动LocationManager的requestLocationUpdates每2秒请求一次GPS信息。位置信息两条线并行使用一条是叠加到视频画面上的速度/坐标字符串另一条是独立生成一个NMEA格式的log文件和视频保存在同一目录下。这样事后看视频时可以通过时间戳把位置信息同步到视频上即使画面没有叠加信息也有后处理的空间。叠加坐标的简单实现是通过TextView直接画在预览界面上再用MediaRecorder.setCustomInfo写入视频元数据。不过setCustomInfo是API 30新增的接口兼容性有限。我的做法是给预览Surface套一个自定义OverlayView直接绘制文本到画面上录制出来的视频自带数据。4.2 加速度碰撞检测与灵敏度调参碰撞检测依赖SensorManager.TYPE_ACCELEROMETER。加速度传感器返回X、Y、Z三个轴的数据单位是m/s²重力加速度约9.8 m/s²。算法逻辑非常直观计算三轴加速度的合成模值减去重力加速度后判断是否超过阈值。Override public void onSensorChanged(SensorEvent event) { float x event.values[0]; float y event.values[1]; float z event.values[2]; double magnitude Math.sqrt(x * x y * y z * z); double delta magnitude - SensorManager.GRAVITY_EARTH; if (Math.abs(delta) 2.5 * SensorManager.GRAVITY_EARTH) { triggerEventLock(); } }阈值设多少合适我试过1.5倍、2.0倍、2.5倍。2.5倍约24.5 m/s²起步这样过减速带、压井盖这些轻微振动不会误触发真正追尾和剐蹭都能捕获。如果你停车环境经常被旁边的车开车门碰到可以调到2.0倍提高灵敏度但误报也会相应增加。这里还有一个小细节传感器事件在主线程回调时会阻塞UI需要在SensorManager.registerListener时指定SENSOR_DELAY_UI以上的频率即可不要用SENSOR_DELAY_FASTEST否则耗电会上升。实际项目中我设置在SENSOR_DELAY_GAME大约20ms一次回调对碰撞检测完全足够。4.3 存储容量估算与循环清理算法很多人忽略存储容量预算直接用默认设置结果录一天就满了。我按4Mbps码率算视频每秒约500KB加上音频约12KB/s总计约512KB/s。一分钟约30MB一小时约1.8GB。假设手机剩余可用空间为25GB能连续录制约13小时。如果每天通勤2小时循环覆盖完全够用。清理算法需要注意一个边界条件正在录制的当前文件不能删。否则MediaRecorder会在文件被删除后继续写入而找不到路径导致录制失败。我的保护逻辑是记录当前正在写入的文件名清理时排除它同时排除EVENT文件。这里我还遇到一个文件系统的问题Android的FAT32格式文件系统单文件上限是4GB而MediaRecorder在接近4GB边界时可能有未知行为。我的切片策略是1分钟一个文件单个文件约30MB完全规避了这个风险。5. 常见问题排查与调试实录5.1 MP4文件损坏3秒黑文件问题这个问题的表现是录像文件存在但是播放器打不开或者只能看到前3秒画面。我在项目初期经常遇到排查了很久最终定位到两个原因。第一个原因是录制过程中Activity或服务被系统回收MediaRecorder没有正常执行stop()文件没有写入moov boxMP4的索引信息。解决方法是加一个最终的写入兜底在MediaRecorder.start()之后的任何异常路径都try-finally执行stop()。第二个原因和setMaxDuration有关。如果录制时间到达上限后自动停止但新文件还没准备好旧文件的收尾就会被跳过。这个场景正是我前面说到的为了切片稳定改用setNextOutputFile的原因。最后还有一个很隐蔽的点不要在onPause中立即调用stop()。由于Activity的onStop和onPause在系统断电时可能晚于或早于文件落盘时机我用一个RecorderStatus枚举状态机来管理只有状态处于“RECORDING”时才允许执行stop其他状态一律忽略避免重复stop导致的崩溃。5.2 后台录制等机型差异问题手机方案最头疼的就是厂商ROM的差异。常见问题包括小米手机锁屏后应用被清理华为P/Mate系列白名单机制一加的电池优化默认设置为“智能限制”OPPO/vivo的“自启动”权限未开启等。这些系统在默认状态下会严重影响后台录制。实际项目中我做了三件防御性工作。第一在首次启动的引导页明确提示用户去系统设置里关闭电池优化、开启自启动、锁定后台任务。第二在代码里通过PowerManager.isIgnoringBatteryOptimizations检查并引导用户跳到设置页。第三即使进程被杀死在前台服务使用START_STICKY拉起后再通过ActivityManager.getRunningAppProcesses判断当前是否存活如果被杀则从最近一个完整的文件继续录制而不是从头开始。5.3 文件导出与Android分区存储将录像导出到电脑或者传给交警需要保证文件可被外部访问。Android 10以后WRITE_EXTERNAL_STORAGE权限已经失效不能直接往公共存储目录随便写文件。我的实现方案是把录像保存在应用专属外部目录getExternalFilesDir(Environment.DIRECTORY_MOVIES)再用FileProvider共享provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider这样在录像回放界面点导出时通过FileProvider.getUriForFile生成content:// URI配合Intent.FLAG_GRANT_READ_URI_PERMISSION分享到微信、邮件或网盘。很多人遇到“文件能看但分享不了”的问题基本都是忘记加grantUriPermissions导致的。5.4 画面花屏、噪声和摄像头失效排查还有一类问题在车载场景更容易出现高温下摄像头模组或摄像头驱动异常导致画面花屏、偏色、黑屏。这类问题常见原因有三个一是摄像头镜头脏污尤其前挡风玻璃处灰尘多现象是画面整体模糊或局部污点清洁即可二是手机温度过高触发了图像质量下降或摄像头被系统关闭保护这个无法绕过只能通过降低录制分辨率、关掉屏幕亮度来缓解三是硬件老化导致的对焦马达卡死表现为画面长时间无法合焦。软件层面能做的排查是看Camera2的CaptureCallback返回的错误码如果是ERROR_CAMERA_IN_USE或ERROR_CAMERA_DISABLED说明摄像头被其它应用占用或系统策略禁用。6. 写在最后这个项目前后迭代了差不多三周从第一版“能录像”到最终“能长期稳定运行”中间大量时间不是花在功能实现上而是花在适配和保活上。做这类车载Android项目我的体会是功能层面的代码量其实不大真正决定成败的是对系统资源管理、生命周期边界和异常路径的处理。如果后续想继续扩展有几个方向值得一试把录像上传到NAS或云存储实现停车后的远程查看接入三方OBD盒子把车速、转速真正从CAN总线上取下来叠加到画面或者把传感器数据做成更完整的事故分析报告碰撞时自动保存前后各30秒的关键片段。这套基于手机摄像头的方案虽然跟专业硬件记录仪比还有差距但胜在灵活、可定制、成本可控作为个人项目和车载应用原型已经足够实用了。最后再分享一个小技巧如果你打算上车实际测试一定准备一个带电量显示的车充一边录制一边观察温度和电流变化这个过程中暴露出来的续航和散热问题是任何模拟器都测不出来的。本文还有配套的精品资源点击获取