使用CameraSource切换闪光灯在部分Android设备触发NullPointerException崩溃
Hey,我来帮你拆解这个闪光灯控制的崩溃问题,以及给出靠谱的修复方案:
问题根源分析
从你提供的崩溃日志来看,NullPointerException直接触发在Field.get(camSource)这一行,结合代码和三星Note8的Android 7.1环境,主要有两个核心原因:
camSource对象为空:当用户点击闪光灯按钮时,CameraSource可能还没初始化完成,或者已经在Activity生命周期(比如onPause)中被释放了。这时候调用field.get(null)会直接抛出空指针,因为实例字段的反射get方法必须传入非空的对象。CameraSource内部实现的设备差异:Google Mobile Vision的
CameraSource在不同Android版本或厂商定制系统中,内部结构可能有变化。比如在Note8的7.1系统中,即使找到了Camera类型的字段,该字段对应的相机实例可能还未被初始化,或者系统对相机的管理逻辑和其他设备不同,导致反射获取的对象不可靠。
另外,你当前的代码完全没有对camSource做前置空判断,这是导致崩溃的直接导火索。
修复方案
方案1:先做空判断+完善异常处理(快速修复)
在方法开头先检查camSource是否有效,同时给反射过程加上更严谨的异常处理,避免崩溃扩散:
public void changeFlashStatus(View v) { // 第一步:先确认相机源是否可用 if (camSource == null) { Toast.makeText(this, "相机未准备就绪", Toast.LENGTH_SHORT).show(); return; } Field[] declaredFields = CameraSource.class.getDeclaredFields(); if (declaredFields == null) return; Camera camera = null; for (Field field : declaredFields) { if (field.getType() != Camera.class) continue; field.setAccessible(true); try { camera = (Camera) field.get(camSource); if (camera == null) { Toast.makeText(this, "无法获取相机控制权限", Toast.LENGTH_SHORT).show(); break; } Camera.Parameters params = camera.getParameters(); // 先检查设备是否支持对应的闪光灯模式 List<String> supportedModes = params.getSupportedFlashModes(); if (!isFlash) { if (supportedModes != null && supportedModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) { params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); flash_btn.setImageResource(R.drawable.ic_action_flash_on); isFlash = true; } else { Toast.makeText(this, "设备不支持常亮闪光灯", Toast.LENGTH_SHORT).show(); } } else { params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); flash_btn.setImageResource(R.drawable.ic_action_flash_off); isFlash = false; } camera.setParameters(params); } catch (IllegalAccessException e) { e.printStackTrace(); Toast.makeText(this, "闪光灯控制失败", Toast.LENGTH_SHORT).show(); } finally { // 恢复字段访问权限,符合代码规范 field.setAccessible(false); } break; } }
方案2:管理相机状态,避免无效调用(从根源避免)
在Activity中维护相机的就绪状态,只有当相机完全启动后才允许用户操作闪光灯,同时在生命周期中及时更新状态:
private boolean isCameraReady = false; // 启动CameraSource的代码块 try { camSource.start(surfaceView.getHolder()); isCameraReady = true; } catch (IOException e) { e.printStackTrace(); isCameraReady = false; } // 在changeFlashStatus中增加状态判断 public void changeFlashStatus(View v) { if (!isCameraReady || camSource == null) { Toast.makeText(this, "相机未准备就绪", Toast.LENGTH_SHORT).show(); return; } // 后面的闪光灯逻辑... } // 生命周期中更新状态 @Override protected void onPause() { super.onPause(); if (camSource != null) { camSource.stop(); isCameraReady = false; } } @Override protected void onDestroy() { super.onDestroy(); if (camSource != null) { camSource.release(); camSource = null; isCameraReady = false; } }
方案3:替换反射,用更稳定的方式(长期最优)
反射依赖CameraSource的内部实现,一旦Google更新库或者厂商修改系统就容易出问题。如果你还在使用旧的Mobile Vision,建议迁移到Google ML Kit(Mobile Vision的继任者),它提供了更完善的相机控制API,不需要依赖反射。如果暂时无法迁移,也可以考虑自己管理相机实例,而不是依赖CameraSource的内部对象,这样能完全控制闪光灯逻辑。
内容的提问来源于stack exchange,提问作者Loving Android

