3、Camera2 预览实战:配置 TextureView、开启相机、请求预览流、处理生命周期

在上一节中,我们了解了 Camera2 的核心架构与关键类。本节将进入实战环节,手把手带你实现一个完整的 Camera2 预览功能。我们将从 UI 组件 TextureView 的配置开始,逐步完成相机开启、预览流请求以及生命周期管理的全流程。

3.1 配置 TextureView

TextureView 是 Android 中用于显示相机预览画面的核心控件。与 SurfaceView 不同,它支持硬件加速和动画变换,更适合与 Camera2 的 SurfaceTexture 配合使用。

步骤 1:在布局文件中添加 TextureView

<!-- activity_camera.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/texture_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

步骤 2:在 Activity 中初始化并设置监听器

public class CameraActivity extends AppCompatActivity {

    private TextureView textureView;
    private CameraManager cameraManager;
    private String cameraId;
    private CameraDevice cameraDevice;
    private CameraCaptureSession captureSession;
    private CaptureRequest.Builder previewRequestBuilder;
    private CaptureRequest previewRequest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        textureView = findViewById(R.id.texture_view);
        cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

        // 关键:TextureView 必须在 SurfaceTexture 就绪后才能操作相机
        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                // SurfaceTexture 已就绪,可以安全地打开相机
                openCamera();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                // 处理尺寸变化(例如屏幕旋转)
                configureTransform(width, height);
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                // 释放相机资源
                closeCamera();
                return true;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                // 每帧更新时回调(通常不需要处理)
            }
        });
    }
}

3.2 开启相机

开启相机需要先获取 CameraManager,然后通过 openCamera 方法异步打开。我们需要指定相机 ID(通常后置为 "0",前置为 "1")以及一个状态回调。

private void openCamera() {
    try {
        // 获取后置摄像头 ID
        cameraId = cameraManager.getCameraIdList()[0];

        // 配置相机特性(可选:设置输出尺寸等)
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        StreamConfigurationMap map = characteristics.get(
                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        // 这里可以获取支持的预览尺寸,选择最合适的

        // 打开相机,传入状态回调
        cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(CameraDevice camera) {
                cameraDevice = camera;
                // 相机已打开,开始创建预览会话
                createPreviewSession();
            }

            @Override
            public void onDisconnected(CameraDevice camera) {
                cameraDevice.close();
                cameraDevice = null;
            }

            @Override
            public void onError(CameraDevice camera, int error) {
                cameraDevice.close();
                cameraDevice = null;
                // 处理错误(例如相机被占用)
            }
        }, null); // 第三个参数是 Handler,null 表示使用主线程
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

3.3 请求预览流

预览流的核心是创建 CameraCaptureSession,并将 TextureViewSurfaceTexture 作为输出目标。

private void createPreviewSession() {
    try {
        // 获取 TextureView 的 SurfaceTexture
        SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
        // 设置默认缓冲区大小(通常设置为预览尺寸)
        surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());

        // 创建 Surface 对象,用于 Camera2 输出
        Surface surface = new Surface(surfaceTexture);

        // 创建预览请求(TEMPLATE_PREVIEW 是标准预览模板)
        previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        previewRequestBuilder.addTarget(surface);

        // 创建捕获会话
        cameraDevice.createCaptureSession(Arrays.asList(surface),
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        captureSession = session;
                        // 构建最终的预览请求
                        previewRequest = previewRequestBuilder.build();
                        // 开始重复请求(持续预览)
                        try {
                            captureSession.setRepeatingRequest(previewRequest,
                                    null, // 预览回调(可选)
                                    null  // Handler
                            );
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onConfigureFailed(CameraCaptureSession session) {
                        // 配置失败处理
                    }
                }, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

3.4 处理生命周期

Camera2 资源必须与 Activity/Fragment 的生命周期严格绑定,否则会导致内存泄漏或相机被占用。

核心原则:

  • onResume():如果 TextureView 已就绪,则打开相机。
  • onPause():释放相机资源(关闭会话和相机设备)。
  • onDestroy():确保所有资源被释放。
@Override
protected void onResume() {
    super.onResume();
    // 如果 TextureView 的 SurfaceTexture 已经就绪,直接打开相机
    if (textureView.isAvailable()) {
        openCamera();
    } else {
        // 否则等待 SurfaceTextureListener 回调
        textureView.setSurfaceTextureListener(surfaceTextureListener);
    }
}

@Override
protected void onPause() {
    closeCamera();
    super.onPause();
}

private void closeCamera() {
    if (captureSession != null) {
        captureSession.close();
        captureSession = null;
    }
    if (cameraDevice != null) {
        cameraDevice.close();
        cameraDevice = null;
    }
}

3.5 完整代码整合与注意事项

将上述代码整合到 CameraActivity 中,即可实现一个基本的 Camera2 预览。以下是几个关键注意事项:

注意事项 说明
权限声明 AndroidManifest.xml 中添加 <uses-permission android:name="android.permission.CAMERA" />,并在运行时请求权限(Android 6.0+)。
预览尺寸选择 通过 StreamConfigurationMap.getOutputSizes(SurfaceTexture.class) 获取支持的尺寸,选择与屏幕比例最接近的尺寸,避免变形。
线程模型 Camera2 的回调默认在主线程执行。如果需要在子线程处理,可以传入 Handler 参数。
屏幕旋转 onSurfaceTextureSizeChanged 中调用 configureTransform 方法,通过矩阵旋转校正预览方向。
错误处理 务必处理 CameraAccessException(相机被占用)和 SecurityException(权限不足)。

通过以上步骤,你已经成功搭建了一个完整的 Camera2 预览框架。下一节我们将深入讲解如何调整预览方向、选择最佳预览尺寸,以及如何实现前后摄像头的切换。