HarmonyOS技术精讲-Camera Kit(相机服务)第7篇:闪光灯控制技术
《HarmonyOS技术精讲-Camera Kit(相机服务)第7篇:闪光灯控制技术》

闪光灯控制,没那么简单
HarmonyOS NEXT 相机的闪光灯控制,官方文档写得很清楚:通过 setFlashMode 接口设置 FlashMode 枚举值。但实际开发中,很多人第一次实现这个功能时,会遇到几个很隐蔽的问题。
比如,在切换闪光灯模式后,照片的曝光效果并没有按照预期改变;又比如,相机启动时默认的闪光灯状态和UI显示不一致。这些问题不是API本身的问题,而是状态同步和生命周期管理的细节没有处理好。
这篇文章会从最基础的 FlashMode 枚举讲起,直接给出一个完整的闪光灯模式切换组件,然后重点分析开发中容易踩的几个坑。代码改完就能用,不在理论上纠结。
闪光灯模式到底控制什么
FlashMode 枚举定义了四种模式:
| 枚举值 | 说明 | 典型场景 |
|---|---|---|
FLASH_MODE_CLOSE |
关闭闪光灯 | 光线充足环境、静物拍摄 |
FLASH_MODE_OPEN |
强制开启闪光灯 | 暗光环境、补光需求 |
FLASH_MODE_AUTO |
自动模式,相机根据环境光线判断 | 日常随手拍 |
FLASH_MODE_ALWAYS_OPEN |
常亮模式,持续发光 | 视频补光、微距拍摄 |
重点注意: FLASH_MODE_ALWAYS_OPEN 是常亮模式,不是“一直闪”。它在拍照或录视频过程中会持续发光,适用于视频场景。FLASH_MODE_OPEN 则是在拍照瞬间闪光。
这个组件要解决的核心问题是:在相机启动后,用户能实时切换闪光灯模式,并且UI显示和实际闪光灯状态保持一致。
环境说明
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上
目标设备:HarmonyOS NEXT 手机
核心实现:完整可运行的闪光灯控制组件
1. 相机初始化与闪光灯模式设置
这一段代码完成相机初始化和获取闪光灯能力。闪光灯控制依赖于 Flash 对象,而 Flash 对象又需要通过 CameraInput 的 FlashMode 属性来获取。
// FlashController.ets
import { camera } from '@kit.CameraKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
export class FlashController {
private cameraManager: camera.CameraManager | null = null;
private cameraInput: camera.CameraInput | null = null;
private flash: camera.Flash | null = null;
private context: common.Context;
constructor(context: common.Context) {
this.context = context;
}
// 初始化相机资源,获取闪光灯控制对象
async initCamera(): Promise<void> {
try {
// 获取CameraManager
this.cameraManager = camera.getCameraManager(this.context);
if (!this.cameraManager) {
console.error('FlashController: 获取CameraManager失败');
return;
}
// 获取相机列表,这里使用后置摄像头作为示例
const cameras: Array<camera.CameraDevice> = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
console.error('FlashController: 未找到相机设备');
return;
}
// 选择一个后置摄像头
const cameraDevice = cameras.find((device: camera.CameraDevice) =>
device.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK
);
if (!cameraDevice) {
console.error('FlashController: 未找到后置摄像头');
return;
}
// 创建相机输入流
this.cameraInput = this.cameraManager.createCameraInput(cameraDevice);
if (!this.cameraInput) {
console.error('FlashController: 创建CameraInput失败');
return;
}
// 打开相机
await this.cameraInput.open();
// 获取闪光灯控制对象
this.flash = this.cameraInput.getFlash();
if (!this.flash) {
console.error('FlashController: 该相机不支持闪光灯');
return;
}
console.info('FlashController: 相机初始化完成,闪光灯可用');
} catch (error) {
let err = error as BusinessError;
console.error(`FlashController: 初始化相机失败, code: ${err.code}, message: ${err.message}`);
}
}
// 设置闪光灯模式
async setFlashMode(mode: camera.FlashMode): Promise<boolean> {
if (!this.flash) {
console.error('FlashController: 闪光灯对象未初始化');
return false;
}
try {
await this.flash.setFlashMode(mode);
console.info(`FlashController: 闪光灯模式设置为 ${mode}`);
return true;
} catch (error) {
let err = error as BusinessError;
console.error(`FlashController: 设置闪光灯模式失败, code: ${err.code}, message: ${err.message}`);
return false;
}
}
// 获取当前闪光灯模式
async getCurrentFlashMode(): Promise<camera.FlashMode | null> {
if (!this.flash) {
console.error('FlashController: 闪光灯对象未初始化');
return null;
}
try {
const mode: camera.FlashMode = await this.flash.getFlashMode();
console.info(`FlashController: 当前闪光灯模式为 ${mode}`);
return mode;
} catch (error) {
let err = error as BusinessError;
console.error(`FlashController: 获取闪光灯模式失败, code: ${err.code}, message: ${err.message}`);
return null;
}
}
// 释放相机资源
release(): void {
try {
this.cameraInput?.close();
this.cameraManager = null;
this.cameraInput = null;
this.flash = null;
console.info('FlashController: 相机资源已释放');
} catch (error) {
let err = error as BusinessError;
console.error(`FlashController: 释放资源失败, code: ${err.code}, message: ${err.message}`);
}
}
}
关键点:
getFlash()方法需要先打开CameraInput才能调用,否则会报错。- 闪光灯模式设置是异步操作,需要用
await等待完成。 - 如果相机不支持闪光灯,
getFlash()返回null,需要做容错处理。
2. 闪光灯模式切换UI组件
这一部分提供完整的ArkUI界面,包含一个按钮组,点击后切换闪光灯模式,并显示当前模式。
// FlashModeUI.ets
import { camera } from '@kit.CameraKit';
import { FlashController } from './FlashController';
@Entry
@Component
struct FlashModePage {
@State currentMode: number = camera.FlashMode.FLASH_MODE_CLOSE;
private flashController: FlashController = new FlashController(getContext());
aboutToAppear(): void {
this.initCamera();
}
aboutToDisappear(): void {
this.flashController.release();
}
async initCamera(): Promise<void> {
await this.flashController.initCamera();
// 初始化完成后,获取当前闪光灯模式,同步UI
const mode: camera.FlashMode | null = await this.flashController.getCurrentFlashMode();
if (mode !== null) {
this.currentMode = mode;
}
}
build() {
Column() {
// 模式选择按钮组
Row() {
this.createModeButton('关闭', camera.FlashMode.FLASH_MODE_CLOSE)
this.createModeButton('开启', camera.FlashMode.FLASH_MODE_OPEN)
this.createModeButton('自动', camera.FlashMode.FLASH_MODE_AUTO)
this.createModeButton('常亮', camera.FlashMode.FLASH_MODE_ALWAYS_OPEN)
}
.width('100%')
.height(80)
.padding({ left: 10, right: 10 })
// 当前模式提示
Text(`当前闪光灯模式: ${this.getModeText(this.currentMode)}`)
.fontSize(16)
.margin({ top: 20 })
.textAlign(TextAlign.Center)
.width('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 创建单个模式按钮
@Builder
createModeButton(label: string, mode: camera.FlashMode) {
Button(label)
.width(60)
.height(60)
.margin({ left: 5, right: 5 })
.backgroundColor(this.currentMode === mode ? '#007AFF' : '#8E8E93')
.fontColor(Color.White)
.fontSize(14)
.borderRadius(30)
.onClick(() => {
this.switchFlashMode(mode);
})
}
// 切换闪光灯模式
async switchFlashMode(mode: camera.FlashMode): Promise<void> {
const success: boolean = await this.flashController.setFlashMode(mode);
if (success) {
this.currentMode = mode;
console.info(`UI: 闪光灯模式已切换为 ${mode}`);
} else {
console.error('UI: 切换闪光灯模式失败');
// 可以在这里弹出提示
}
}
// 将模式枚举转换为可读文本
getModeText(mode: number): string {
switch (mode) {
case camera.FlashMode.FLASH_MODE_CLOSE:
return '关闭';
case camera.FlashMode.FLASH_MODE_OPEN:
return '开启';
case camera.FlashMode.FLASH_MODE_AUTO:
return '自动';
case camera.FlashMode.FLASH_MODE_ALWAYS_OPEN:
return '常亮';
default:
return '未知';
}
}
}
关键点:
aboutToAppear中初始化相机,aboutToDisappear中释放资源,这是标准的生命周期管理。- 点击按钮后,先调用
setFlashMode,成功后再更新UI状态currentMode,避免UI与实际状态不一致。 - 按钮的激活态通过
currentMode比较来控制,不需要单独维护一个状态数组。
常见问题与解决方案
问题1:切换闪光灯模式后,相机预览画面无变化
现象:
调用 setFlashMode 返回成功,但相机预览画面中闪光灯没有亮起,拍照时也没有闪光效果。
原因:
这个问题与API的设计有关。setFlashMode 只是设置了闪光灯的“意图”,但实际是否生效取决于相机的当前会话配置。如果相机会话没有正确配置或者处于非活动状态,设置的模式不会立即生效。
在官方文档的实现路径中,闪光灯模式需要在创建 PhotoSession 或 VideoSession 后、开启会话前进行配置。如果不在正确的时机调用,设置会被忽略。
解决方案:
确保在 CameraInput 打开并且 Session 创建成功后,再调用 setFlashMode。推荐在相机初始化流程中同步完成闪光灯模式的配置。
// 在初始化相机后的配置阶段设置闪光灯模式
async configureSession(): Promise<void> {
// 创建并配置Session...
// 设置闪光灯模式
await this.flash?.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);
// 开始会话
// ...
}
如果用户动态切换模式,需要确保相机会话处于“已配置”状态,调用 setFlashMode 才会立刻生效。在 PhotoSession 或 VideoSession 对象上,也有一个 setFlashMode 方法,两者必须保持一致。
问题2:页面退出后,再次进入时闪光灯状态未恢复
现象:
应用第一次打开时,闪光灯默认是关闭状态。用户切换到“开启”模式。退出页面再回来,UI显示的是“关闭”,但实际闪光灯可能还是开启状态,或者初始化后状态出现混乱。
原因:
这个问题由两部分组成:
- UI状态丢失:
@State在页面销毁后不会持久化,再次进入时currentMode会恢复为初始值。 - 相机状态不一致: 相机资源在
aboutToDisappear中被释放,但设备硬件可能保留了上次的设置。重新初始化时,getCurrentFlashMode返回的是硬件当前状态,可能与UI期望的不一致。
解决方案:
在 aboutToAppear 中强制从硬件读取状态,并更新UI。不要依赖上次设置的值。
async initCamera(): Promise<void> {
await this.flashController.initCamera();
// 强制从硬件读取当前闪光灯模式
const mode: camera.FlashMode | null = await this.flashController.getCurrentFlashMode();
if (mode !== null) {
this.currentMode = mode; // 以硬件状态为准
} else {
// 如果获取失败,使用默认值
this.currentMode = camera.FlashMode.FLASH_MODE_CLOSE;
}
}
这种做法确保了每次页面显示时,UI状态和相机状态完全同步。用户在任何地方修改了闪光灯模式,回到页面都能正确显示。
最佳实践
-
不要依赖默认状态。 相机启动后,
getCurrentFlashMode返回的值不一定是你期望的。在UI初始化时,强制读取一次硬件状态。 -
UI更新必须在API调用成功之后。 很多同学习惯先改UI再调用API,这样做在API调用失败时会导致UI与状态不一致。始终遵循“调用API -> 检查结果 -> 更新UI”的顺序。
-
生命周期管理要彻底。
CameraInput的open和close必须成对出现。如果在aboutToAppear中open,一定要在对应的aboutToDisappear中close。漏掉close会导致相机资源一直被占用,影响其他应用使用。 -
异步边界要清晰。
setFlashMode是异步的,不要在循环中连续调用多次。如果需要连续切换,应该等到上一次操作完成后再进行下一次。
完整Demo入口
// Index.ets
import { FlashModePage } from './FlashModeUI';
@Entry
@Component
struct Index {
build() {
Column() {
FlashModePage()
}
.width('100%')
.height('100%')
}
}
FAQ
Q:为什么在模拟器上切换闪光灯模式没有任何效果?
A:模拟器不支持真实的相机硬件,闪光灯控制依赖于设备硬件。setFlashMode 调用在模拟器上会成功返回,但不会产生物理效果。所有与闪光灯相关的功能必须在真机上测试。
Q:为什么页面返回后,闪光灯模式没有恢复成关闭状态?
A:相机硬件状态在页面销毁后不会自动重置。如果你希望用户离开页面时关闭闪光灯,需要在 aboutToDisappear 中显式调用 setFlashMode(FLASH_MODE_CLOSE),然后再释放相机资源。但这取决于业务需求,不是必须的行为。
Q:为什么第一次授权相机权限成功,第二次打开应用时授权弹窗又弹出来了?
A:HarmonyOS的权限授权是持久化的,正常情况下不会重复弹窗。如果每次都弹窗,说明你的应用没有正确保存或检查权限状态。检查 requestPermissionsFromUser 的调用时机,确保只在权限未授权时调用。另外,某些开发者选项可能会重置权限状态,这种属于特殊调试环境。
更多推荐



所有评论(0)