Android 6.0设备Google Mobile Vision(OCR)应用闪光灯功能崩溃排查
你的闪光灯切换功能在Android 6.0设备上崩溃,核心原因集中在反射的不兼容性和运行时权限处理缺失两个方面,结合崩溃日志和代码细节,具体分析如下:
一、崩溃的直接原因
从崩溃栈可以看到,空指针异常发生在field.get(camSource)这一行(CamActivity.java:412),主要有两种可能:
camSource对象未初始化(为null),导致反射调用时抛出异常;- Google Mobile Vision的
CameraSource在Android 6.0版本的内部实现发生了变化,不再包含Camera类型的私有字段,反射获取不到有效对象。
二、深层原因拆解
1. 反射依赖未公开的内部实现,兼容性极差
你通过反射CameraSource的私有字段来获取Camera实例的方式,完全依赖Google未公开的内部代码结构。而Android 6.0及以上版本中,Google可能为了适配新的Camera2 API,修改了CameraSource的内部逻辑,移除了直接持有Camera对象的设计,导致反射找不到目标字段,或获取到null值,最终引发崩溃。
2. Android 6.0运行时权限未正确处理
Android 6.0(API 23)引入了运行时权限机制,CAMERA属于危险权限,必须在运行时向用户申请并获得授权后,才能正常使用相机功能。如果你的应用没有处理这一步:
- 用户拒绝权限后,
CameraSource无法初始化相机实例,camSource内部的相机对象为null; - 极端情况下
camSource本身也会是null,直接触发反射时的空指针异常。
3. 代码缺乏安全检查
你的代码假设一定能找到Camera类型的字段,且camSource和相机实例一定不为null,但没有任何容错处理,一旦出现异常情况就直接崩溃。
三、解决方案
方案1:放弃反射,使用CameraSource的公开API(推荐)
Google Mobile Vision的CameraSource.Builder其实提供了setFlashMode方法来控制闪光灯,无需依赖反射:
// 初始化CameraSource时设置闪光灯模式 CameraSource cameraSource = new CameraSource.Builder(context, detector) .setFlashMode(Camera.Parameters.FLASH_MODE_OFF) // 默认关闭 .build(); // 切换闪光灯时,重新构建CameraSource(或重启相机) public void changeFlashStatus(View v) { if (camSource == null) return; try { camSource.stop(); String flashMode = isFlash ? Camera.Parameters.FLASH_MODE_OFF : Camera.Parameters.FLASH_MODE_TORCH; camSource = new CameraSource.Builder(this, detector) .setFlashMode(flashMode) // 保留其他原有配置 .setRequestedPreviewSize(640, 480) .build(); camSource.start(surfaceView.getHolder()); isFlash = !isFlash; flash_btn.setImageResource(isFlash ? R.drawable.ic_action_flash_on : R.drawable.ic_action_flash_off); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "闪光灯切换失败", Toast.LENGTH_SHORT).show(); } }
这种方式基于公开API,兼容性和稳定性远高于反射。
方案2:正确处理Android 6.0+的运行时权限
无论使用哪种方式,都必须先确保相机权限已授权:
private static final int REQUEST_CAMERA_PERMISSION = 100; // 检查并申请相机权限 private void checkCameraPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); } else { // 权限已授予,初始化相机 initCameraSource(); } } // 处理权限申请结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_CAMERA_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { initCameraSource(); } else { Toast.makeText(this, "需要相机权限才能使用闪光灯功能", Toast.LENGTH_SHORT).show(); } } }
在onCreate或相机功能启动前调用checkCameraPermission(),确保权限到位。
方案3:改进反射逻辑(仅作临时兼容,不推荐)
如果必须继续使用反射,需要添加多重安全检查:
public void changeFlashStatus(View v) { // 先检查camSource是否为空 if (camSource == null) { Toast.makeText(this, "相机未初始化", Toast.LENGTH_SHORT).show(); return; } Camera camera = null; Field[] declaredFields = CameraSource.class.getDeclaredFields(); for (Field field : declaredFields) { // 用equals避免类加载器导致的类型不匹配 if (Camera.class.equals(field.getType())) { field.setAccessible(true); try { camera = (Camera) field.get(camSource); if (camera != null) { Camera.Parameters params = camera.getParameters(); // 检查设备是否支持闪光灯 if (!params.getSupportedFlashModes().contains(Camera.Parameters.FLASH_MODE_TORCH)) { Toast.makeText(this, "当前设备不支持闪光灯", Toast.LENGTH_SHORT).show(); return; } // 切换闪光灯模式 if (!isFlash) { params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); flash_btn.setImageResource(R.drawable.ic_action_flash_on); } else { params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); flash_btn.setImageResource(R.drawable.ic_action_flash_off); } camera.setParameters(params); isFlash = !isFlash; } else { Toast.makeText(this, "相机实例为空", Toast.LENGTH_SHORT).show(); } } catch (IllegalAccessException e) { e.printStackTrace(); Toast.makeText(this, "无法访问相机实例", Toast.LENGTH_SHORT).show(); } break; } } // 未找到Camera字段的情况 if (camera == null) { Toast.makeText(this, "无法控制闪光灯", Toast.LENGTH_SHORT).show(); } }
总结
最稳定的方案是方案1+方案2:使用CameraSource的公开API控制闪光灯,并严格处理运行时权限。反射方式因为依赖私有实现,随时可能因Google的版本更新失效,不建议长期使用。如果需要更灵活的相机控制,也可以考虑直接集成Camera2 API来替代Google Mobile Vision的CameraSource。
内容的提问来源于stack exchange,提问作者Loving Android

