欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android camera2 实现相机预览界面(2)

程序员文章站 2022-06-02 15:54:26
...

@[TOC](android camera2 实现相机预览界面(2))

前言:

在上篇 android camera2 实现相机预览界面 给出了最简单的实现。

因为是最简单的实现,所以只是走通了 camera2 预览的api 调用流程。但是里面确实存在一些问题。下面就已知的问题做一个说明,并在这里给出解决方案。

android camera2 实现相机预览界面 存在的问题:

  1. 资源没有释放,在Activity#onStop,Activity#onDestory 里面都能看的系统打印的异常信息。不过也还好,系统只是打印了异常信息,并没有让这个进程崩溃。
  2. 预览界面看的的画面被拉伸变形了。可能有的手机上看到的效果正好是正常的,但是只要换一个屏幕大小或者分配率不同的手机可能就能看到预览界面的这种拉伸变形。
  3. 首次安装及运行这个APP,会发现预览界面是黑屏,看不到任何东西。

针对以上问题,我进行了反思。
通过查各种资料,终于解决了以上3个问题。

对应的解决方案

  • 针对第一个问题,很好解决,网上也很容易搜到解决方案。既然是资源没释放,那就去释放资源就好了。
    关键代码如下:
mPreviewHandler.post(() -> {
            if (mSession != null) {
                try {
                    mSession.stopRepeating();
                    mSession.close();
                } catch (CameraAccessException e) {
                    LogUtils.error(TAG, "onStop:", e);
                }
                if (mCamera != null) {
                    mCamera.close();
                }
            }
        });
if (mPreviewHandler != null) {
            mPreviewHandler.postDelayed(() -> {
                mPreviewHandler.removeCallbacksAndMessages(null);
                if (mPreviewThread != null) {
                    mPreviewThread.quitSafely();
                }
            }, DELAY_MILLIS);
        }
mMainHandler.removeCallbacksAndMessages(null);       

由于是同时解决了以上3个问题,所以代码整体上修改幅度比较大。这里的代码不能直接复制到上篇的源码当中。
老铁们不要着急,末尾会给出完成代码地址。

  • 对第二个问题,预览界面的变形,有点麻烦。我在网上搜了很多的资料,大多数都说的是大同小异,但是没起到作用。

最终,我通过反复阅读 camera2 的官方示例代码 camera-samples/Camera2Basic/, 终于找到了解决方案。

为什么是反复阅读?

  1. 对 kotlin 协程 不熟悉,但是里面使用了这个东西,导致本来是应该在回调里面实现的东西,它直接通过返回值给出,然后写成类似同步的代码了。
  2. 这个变形问题,除了需要改变 SurfaView, Surface 的尺寸之外,还需要注意一个调用顺序的问题。

我这里先给出正确的步骤:

  1. 先去获取对应的 camera id , characteristic 这些东西;
  2. 然后去调用 surface.holder.addCallback(callback);
  3. 然后在 callback#surfaceCreated() 设置 SurfaceView 的尺寸相关的数据;
  4. 然后在 callback#surfaceCreated() 里面去打开相机;
  5. 然后在 onOpened() 里面去调用 camera#createCaptureSession();
  6. 然后在session#onConfiged里面去调用 session.setRepeatingRequest(...);

这里最关键的是3,4 这两个步骤。而且顺序不能反过来

我尝试过,在 onOpend 里面才去调用 surface.holder.addCallback(callback),然后在 callback#surfaceCreated() 设置 SurfaceView 的尺寸相关的数据。
但是这样做不行!其实设置的 尺寸数据都没有变。但是这样做就是会变形。而按照上面说的正确的步骤去实现就不变形。

关于这个顺序的问题,没有找到相关的解释。官方示例代码里面的实现是正确的,但是里面也没有说明顺序的问题。

遗留的困惑:为什么要按照这个顺序去调用才不会变形?

好吧,先不管这个困惑了,先看一下最后一个问题怎么解决。

  • 首次安装运行,预览界面是黑屏。

这个问题是因为首次运行的时候会弹一个授权提醒的对话框,然后手动点击授权这样才完成了授权。但是下次启动的时候因为已经授权过了。就不会出这个对话框了。
为什么出这个对话框跟不出会导致界面显示的不同呢?因为生命周期不同了。

~~ 首次的时候,是在onRequestPermissionsResult 里面去调用 surfaceView.holder.addCallbck, 然后看 log 能发现,不会去走 callback#surfaceCreated 这个回调了。猜测是因为 addCallback的时机太晚了。应该直接在 onCreate 里面调用,不管授权的问题。
~~ 第二次启动:因为之前已经授权过了,所以 addCallback 是在 onCreate 里面被调用。

我本来准备验证一下这个猜想的。但是借鉴 camera2 的官方示例代码 camera-samples/Camera2Basic/的实现,我采用了类似的方式去实现。(也就没去验证这个猜测了。。。)
直接在 activity 里面去处理权限的问题,然后在授权成功之后,创建一个 fragment 去显示 surfaceview. 在 fragment#onViewCreaated 里面去给 surfaceView#addCallback(callback). [当然了,这样 activity 的布局里面就不使用 surfaceview了,在 fragment 的布局里面去使用 surfaceview].
这样,不论是首次运行还是二次运行,SurfaceView 总是在 fragment#onViewCreaated 里面去调用 addCallback(callback)的。

以上,就解决了上述的3个问题。

现在的代码目录差不多是下面这样的:

.
├── App.java
├── CameraLifecycle.java 
├── PreviewActivity.java  # 主界面,处理权限问题,授权后就加载 fragment
├── PreviewFragment.java # 相机预览界面,处理相机预览的问题。
├── utils
│   └── LogUtils.java
└── view
    ├── NormalView.java
    ├── SmartSizeKit.java # 处理 surface view 变形的问题 
    └── SmartSurfaceView.java  # 处理 surface view 变形的问题

由于代码量比较大,就不粘贴在文章里面了,下面给出下载地址。

完整的代码

camera2preview