14. 闪光灯控制:检测闪光灯可用性、设置闪光灯模式(ON/OFF/AUTO/TORCH)、拍照时闪光灯同步
闪光灯控制,听起来好像就是「开灯关灯」那么简单。但实际做起来,坑还真不少。
我记得刚接手一个扫码项目时,产品经理说「闪光灯不就是调一下API吗?」。结果呢?有的手机在低电量下直接不支持闪光灯,有的手机TORCH模式和拍照闪光完全是两套逻辑。嗯,今天我们就来把这些事情彻底理清楚。
14.1 检测闪光灯可用性
你想想看,如果手机压根没有闪光灯,或者闪光灯被系统禁用了,你直接去调用,那肯定要出问题。所以第一步,永远是检测。
在Android中,检测闪光灯是否可用,主要靠 CameraManager 的 getCameraCharacteristics() 方法。关键字段是 FLASH_INFO_AVAILABLE。
// 检测闪光灯是否可用
fun isFlashAvailable(cameraId: String): Boolean {
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val flashAvailable = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
return flashAvailable ?: false
}
这里有个细节:FLASH_INFO_AVAILABLE 返回的是 Boolean 类型,但可能为 null。所以一定要做空安全处理。我在项目中就遇到过某款低端机,这个字段直接返回 null,结果没做判断,App 直接崩了。
14.2 闪光灯模式:ON / OFF / AUTO / TORCH
Android 的闪光灯模式,说白了就两种场景:
- 拍照闪光:按下快门那一瞬间闪一下。对应
ON、OFF、AUTO。 - 持续照明:像手电筒一样一直亮着。对应
TORCH。
这两套逻辑是完全分开的。我刚开始做的时候,以为设置 FLASH_MODE_TORCH 就能让拍照时也亮着,结果拍出来的照片一片漆黑。后来才明白,TORCH 和拍照闪光根本是两回事。
14.2.1 设置拍照闪光模式
拍照闪光模式是通过 CaptureRequest 的 FLASH_MODE 字段控制的。它有三个值:
| 模式 | 值 | 说明 |
|---|---|---|
| OFF | 0 | 关闭闪光灯,即使环境再暗也不闪 |
| SINGLE | 1 | 强制闪光,每次拍照都闪 |
| TORCH | 2 | 持续亮灯,但注意:这不会触发拍照闪光 |
等等,你可能发现了——AUTO 模式去哪了?
其实 AUTO 模式不是通过 FLASH_MODE 设置的,而是通过 CONTROL_AE_MODE(自动曝光模式)来控制的。具体来说:
// 设置 AUTO 闪光模式
val requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
// 设置强制闪光模式
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE)
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
// 设置关闭闪光模式
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF)
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
FLASH_MODE_SINGLE,那不管环境多亮,它都会闪。反之,如果你设了 FLASH_MODE_OFF,就算环境再暗,它也不闪。
14.2.2 设置 TORCH 模式
TORCH 模式就是手电筒模式。它和拍照闪光互不干扰。设置方式很简单:
// 开启 TORCH
fun turnOnTorch(cameraId: String) {
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
cameraManager.setTorchMode(cameraId, true)
}
// 关闭 TORCH
fun turnOffTorch(cameraId: String) {
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
cameraManager.setTorchMode(cameraId, false)
}
这里有个坑:setTorchMode() 是异步操作。你调用后,闪光灯不会立刻亮起。如果你紧接着就去拍照,可能灯还没亮。我曾经在项目中就因为这个原因,拍出来的照片一半亮一半暗。解决方案是监听 TorchCallback,等回调确认灯亮了再拍照。
14.3 拍照时闪光灯同步
这是最容易被忽视的地方。很多人以为设置了闪光模式,拍照时就会自动闪。但实际开发中,你需要手动控制闪光灯的触发时机。
为什么?因为 CaptureRequest 只是告诉相机「你该闪了」,但相机什么时候闪,取决于你的请求提交时机。如果你在预览流中设置了 FLASH_MODE_SINGLE,然后立刻提交拍照请求,闪光灯可能还没准备好。
正确的做法是:
- 先设置好闪光模式(ON / OFF / AUTO)
- 等待至少一帧预览数据(确保设置生效)
- 再提交拍照请求
// 拍照时闪光灯同步示例
fun takePhotoWithFlash() {
// 1. 设置闪光模式为强制闪光
previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE)
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
previewSession.setRepeatingRequest(previewRequestBuilder.build(), null, null)
// 2. 等待一帧(实际项目中建议用回调或 Handler 延迟)
Handler(Looper.getMainLooper()).postDelayed({
// 3. 提交拍照请求
captureSession.capture(captureRequestBuilder.build(), captureCallback, null)
}, 100) // 100ms 的延迟通常足够
}
你可能会问:100ms 够吗?说实话,这个值因手机而异。我测试过一些机型,有的 50ms 就够了,有的需要 200ms。我个人习惯是先用 100ms,如果发现闪光不同步,再适当增加。
CameraCaptureSession.CaptureCallback 的 onCaptureCompleted() 回调来判断设置是否生效。
14.4 知识体系总览
下面这张图,把闪光灯控制的整个流程串起来了。你可以对照着看,心里会更有底。
14.5 避坑指南
最后,分享几个我踩过的坑,希望能帮你少走弯路。
- 不要同时操作多个 CameraManager 实例:我曾经在同一个页面中创建了两个 CameraManager 实例,结果一个开灯一个关灯,闪光灯疯狂闪烁。后来发现,CameraManager 应该是单例的。
- TORCH 和拍照闪光不要混用:如果你开启了 TORCH,然后又设置了 FLASH_MODE_SINGLE,拍照时闪光灯可能不会按预期工作。建议先关 TORCH,再拍照,拍完再开 TORCH。
- 低电量下闪光灯可能被禁用:有些手机在电量低于 15% 时会自动禁用闪光灯。检测可用性时,别忘了监听
ACTION_BATTERY_LOW广播。 - 模拟器上闪光灯永远不可用:这个不用我多说了吧?真机调试才是王道。
好了,闪光灯控制这块就讲到这里。内容不算多,但每个点都挺关键的。你只要把检测、设置、同步这三步走顺了,基本就不会出大问题。