一、问题背景

在安卓开发里,做相机应用程序的时候,相机预览出现黑屏和回显错位的问题经常会碰到。这些问题要是处理不好,会让用户体验大打折扣。下面咱们就来详细说说调用相机应用程序编程接口时会遇到的坑,以及怎么去规避这些问题。

二、应用场景

2.1 拍照应用

现在的拍照软件功能越来越多,比如美颜、滤镜啥的。但要是相机预览黑屏或者回显错位,用户根本没法好好拍照,拍出来的照片也可能是歪的或者啥都看不到。像现在很火的一些美颜相机应用,用户在拍照前肯定要先看到清晰准确的预览画面,不然就没法调整姿势和表情。

2.2 视频录制应用

在录制视频的时候,相机预览的准确性就更重要了。要是黑屏或者回显错位,录出来的视频可能就没法用。比如一些短视频拍摄软件,用户在录制过程中需要实时看到画面,要是画面有问题,那录制出来的视频质量就会很差。

2.3 扫码应用

扫码的时候,相机要快速准确地识别二维码或者条形码。如果相机预览黑屏,就根本没法扫码;要是回显错位,可能会导致扫码失败或者识别错误。像我们平时用的支付扫码、乘车扫码等应用,都对相机预览的准确性有很高的要求。

三、常见坑点分析

3.1 权限问题

在安卓系统里,使用相机需要相应的权限。如果没有正确申请相机权限,相机就没法正常工作,预览就会出现黑屏。

// Java示例代码
// 检查相机权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
    // 如果没有权限,请求相机权限
    ActivityCompat.requestPermissions(this,
            new String[]{Manifest.permission.CAMERA},
            CAMERA_PERMISSION_REQUEST_CODE);
}

这段代码的意思是,先检查是否有相机权限,如果没有就请求权限。要是权限没申请下来,相机就用不了。

3.2 相机实例管理不当

在使用相机的时候,要正确管理相机实例。如果相机实例没有正确打开或者关闭,就可能导致黑屏或者回显错位。

// Java示例代码
// 打开相机
Camera camera = Camera.open();
if (camera == null) {
    // 相机打开失败
    Log.e(TAG, "Failed to open camera");
} else {
    // 配置相机参数
    Camera.Parameters parameters = camera.getParameters();
    // 设置预览尺寸
    parameters.setPreviewSize(640, 480);
    camera.setParameters(parameters);
    // 设置预览显示
    try {
        camera.setPreviewDisplay(surfaceHolder);
        camera.startPreview();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 关闭相机
if (camera != null) {
    camera.stopPreview();
    camera.release();
    camera = null;
}

这里面,如果相机打开失败,就会出现黑屏;如果相机关闭的时候没处理好,下次再打开可能也会有问题。

3.3 预览尺寸不匹配

相机的预览尺寸和显示界面的尺寸不匹配,就会导致回显错位。

// Java示例代码
// 获取相机支持的预览尺寸
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();
// 选择合适的预览尺寸
Camera.Size previewSize = getOptimalPreviewSize(supportedSizes, width, height);
parameters.setPreviewSize(previewSize.width, previewSize.height);
camera.setParameters(parameters);

这里的 getOptimalPreviewSize 方法就是用来选择合适的预览尺寸的。如果选择的尺寸不合适,就会出现回显错位的问题。

3.4 线程问题

在安卓开发中,相机操作需要在合适的线程中进行。如果在主线程中进行耗时的相机操作,可能会导致界面卡顿,甚至黑屏。

// Java示例代码
// 在子线程中进行相机操作
new Thread(new Runnable() {
    @Override
    public void run() {
        // 打开相机
        Camera camera = Camera.open();
        if (camera != null) {
            // 配置相机参数
            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(640, 480);
            camera.setParameters(parameters);
            // 设置预览显示
            try {
                camera.setPreviewDisplay(surfaceHolder);
                camera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}).start();

这里把相机操作放在子线程里,避免了主线程的卡顿。

四、规避方法

4.1 权限申请与检查

在应用启动的时候,就要检查相机权限。如果没有权限,要及时请求权限。

// Java示例代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 检查相机权限
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        // 如果没有权限,请求相机权限
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.CAMERA},
                CAMERA_PERMISSION_REQUEST_CODE);
    } else {
        // 权限已经授予,初始化相机
        initCamera();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode,
        @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限授予,初始化相机
            initCamera();
        } else {
            // 权限被拒绝,提示用户
            Toast.makeText(this, "相机权限被拒绝,无法使用相机", Toast.LENGTH_SHORT).show();
        }
    }
}

这里在 onCreate 方法里检查权限,如果没有权限就请求;在 onRequestPermissionsResult 方法里处理权限请求的结果。

4.2 正确管理相机实例

在使用相机的时候,要确保相机实例正确打开和关闭。

// Java示例代码
private Camera camera;

// 打开相机
private void openCamera() {
    if (camera == null) {
        try {
            camera = Camera.open();
            // 配置相机参数
            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(640, 480);
            camera.setParameters(parameters);
            // 设置预览显示
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 关闭相机
private void closeCamera() {
    if (camera != null) {
        camera.stopPreview();
        camera.release();
        camera = null;
    }
}

这里定义了 openCameracloseCamera 方法,分别用来打开和关闭相机。

4.3 匹配预览尺寸

要根据显示界面的尺寸,选择合适的相机预览尺寸。

// Java示例代码
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) w / h;
    if (sizes == null) return null;

    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    // Try to find an size match aspect ratio and size
    for (Camera.Size size : sizes) {
        double ratio = (double) size.width / size.height;
        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
        if (Math.abs(size.height - targetHeight) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.height - targetHeight);
        }
    }

    // Cannot find the one match the aspect ratio, ignore the requirement
    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }
    return optimalSize;
}

这个 getOptimalPreviewSize 方法就是用来选择合适的预览尺寸的。

4.4 合理处理线程问题

把耗时的相机操作放在子线程里进行,避免主线程卡顿。

// Java示例代码
// 在子线程中进行相机操作
new Thread(new Runnable() {
    @Override
    public void run() {
        // 打开相机
        openCamera();
    }
}).start();

这里把 openCamera 方法放在子线程里执行。

五、技术优缺点

5.1 优点

  • 灵活性高:安卓软件开发工具包的相机应用程序编程接口提供了很多功能,可以根据不同的需求进行定制。比如可以设置不同的预览尺寸、拍照模式等。
  • 兼容性好:大部分安卓设备都支持这些接口,能够在不同的设备上正常运行。

5.2 缺点

  • 调用复杂:相机接口的调用需要处理很多细节,比如权限管理、相机实例管理等,容易出现问题。
  • 性能问题:如果处理不当,可能会导致界面卡顿、黑屏等性能问题。

六、注意事项

6.1 权限管理

在请求权限的时候,要给用户说明为什么需要这个权限,避免用户拒绝。同时,要处理好权限被拒绝的情况,给用户提示。

6.2 相机实例管理

在相机使用完之后,一定要及时释放相机实例,避免资源占用。

6.3 预览尺寸选择

要根据不同的设备和显示界面,选择合适的预览尺寸,避免回显错位。

6.4 线程安全

在进行相机操作的时候,要注意线程安全,避免出现并发问题。

七、文章总结

在安卓开发中,相机预览出现黑屏和回显错位的问题主要是由于权限问题、相机实例管理不当、预览尺寸不匹配和线程问题等原因造成的。我们可以通过正确申请权限、合理管理相机实例、匹配预览尺寸和处理线程问题等方法来规避这些问题。同时,要注意权限管理、相机实例释放、预览尺寸选择和线程安全等事项。掌握这些方法和注意事项,就能开发出稳定、可靠的相机应用程序。