最近在58同城App的相机功能模块开发中,遇到了一个典型的黑屏问题:用户点击拍照按钮后,相机预览界面完全黑屏,但其他功能正常。这种情况在Android设备上尤为常见,尤其是中低端机型。作为核心入口功能,相机黑屏直接影响用户体验和业务转化率。
从技术层面看,相机黑屏通常涉及三个关键环节:相机权限获取、硬件设备适配和预览流处理。在58同城这种生活服务类App中,由于用户群体设备差异大(从旗舰机到千元机都有),加上各厂商对Camera2 API的实现差异,导致问题排查复杂度指数级上升。
首先需要排除低级错误:
Manifest权限配置:
xml复制<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
特别注意Android 6.0+需要动态权限申请,仅声明不够
SurfaceView生命周期:
java复制// 确保在onResume后创建Surface
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 在此处初始化相机
}
});
相机服务可用性检查:
java复制if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
// 设备有摄像头
}
当基础检查无异常时,需要按以下顺序排查:
java复制try {
cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
String cameraId = cameraManager.getCameraIdList()[0]; // 默认后置摄像头
cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
// 成功回调
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
// 错误处理
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
关键点:部分厂商设备会在openCamera时抛出SecurityException但被catch块吞没,需要单独捕获处理
创建CaptureRequest时常见的坑:
java复制// 错误的TextureView尺寸设置
textureView.setLayoutParams(new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT // 应该用MATCH_PARENT
));
// 正确的预览配置
SurfaceTexture texture = textureView.getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
Surface previewSurface = new Surface(texture);
CaptureRequest.Builder previewBuilder = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
previewBuilder.addTarget(previewSurface);
// 必须设置正确的预览尺寸
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
Size optimalSize = getOptimalPreviewSize(outputSizes, width, height);
华为部分机型存在SurfaceTexture提前销毁的问题:
java复制// 增加SurfaceTexture监听
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
// 延迟100ms处理规避华为问题
handler.postDelayed(() -> initCamera(), 100);
}
});
小米Note系列存在缓冲区不足问题:
java复制// 修改ImageReader配置
ImageReader.newInstance(
width, height, ImageFormat.YUV_420_888,
3 // 常规用2,小米需要3
);
ColorOS和FuntouchOS对相机资源管理严格:
java复制// 增加释放处理
@Override
protected void onPause() {
super.onPause();
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
if (imageReader != null) {
imageReader.close();
imageReader = null;
}
}
java复制// 打点记录关键节点
long startTime = SystemClock.elapsedRealtime();
cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
long cost = SystemClock.elapsedRealtime() - startTime;
if (cost > 500) {
// 上报慢启动
}
}
}, null);
使用LeakCanary监控Camera相关对象:
java复制// 在Application中初始化
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
当黑屏问题无法立即修复时,可启用降级方案:
切换到系统相机Intent:
java复制Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_CODE);
WebView调用H5相机:
java复制webView.loadUrl("javascript:openH5Camera()");
使用第三方相机SDK(如Fotoapparat):
gradle复制implementation 'io.fotoapparat:fotoapparat:2.7.0'
结合58同城实际业务场景的最终实现:
java复制public class SafeCameraHelper {
private static final int MAX_RETRY_COUNT = 2;
private int retryCount = 0;
public void safeOpenCamera(Context context, TextureView textureView) {
try {
CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
String cameraId = manager.getCameraIdList()[0];
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
setupCamera(camera, textureView);
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
if (retryCount < MAX_RETRY_COUNT) {
retryCount++;
safeOpenCamera(context, textureView);
}
}
}, new Handler(Looper.getMainLooper()));
}
} catch (Exception e) {
// 收集异常信息
reportCrash(e);
// 降级到系统相机
openSystemCamera(context);
}
}
private void setupCamera(CameraDevice camera, TextureView textureView) {
// 详细的相机配置逻辑
}
}
建议在代码中埋入以下监控点:
示例埋点代码:
java复制public class CameraMonitor {
public static void logEvent(String event, Map<String,String> params) {
// 对接公司埋点SDK
MobclickAgent.onEvent(context, event, params);
}
public static void logError(Throwable e) {
// 上报到Bugly
CrashReport.postCatchedException(e);
}
}
在58同城实际项目中,通过上述方案将相机打开成功率从92%提升到99.6%,黑屏问题发生率降低至0.2%以下。关键点在于: