Android CameraX 打开摄像头预览

Android CameraX 打开摄像头预览

目标很简单,用CameraX打开摄像头预览,实时显示界面上。看看CameraX有没有Google说的那么好用。先按最简单的来,把预览显示出来。

引入依赖#

模块gradle的一些配置,使用的Android SDK版本为31,启用了databinding

apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'android {    compileSdkVersion 31    buildToolsVersion "31.0.0"    defaultConfig {        minSdkVersion 21        targetSdkVersion 31    }    dataBinding {        enabled = true    }}

引入CameraX依赖(CameraX 核心库是用camera2实现的),目前主要用1.1.0-alpha11版本

dependencies {    implementation "androidx.camera:camera-core:1.1.0-alpha11"    implementation "androidx.camera:camera-camera2:1.1.0-alpha11"    implementation "androidx.camera:camera-lifecycle:1.1.0-alpha11"    implementation "androidx.camera:camera-view:1.0.0-alpha31"    implementation "androidx.camera:camera-extensions:1.0.0-alpha31"}

使用1.0.2版本的CameraX核心库会报错,找不到getOrCreateInstance方法

??? bug "NoSuchMethodError getOrCreateInstance"

```logCrashHandler: In thread: Thread[main,5,main]    UncaughtException detected: java.lang.NoSuchMethodError: No static method getOrCreateInstance(Landroid/content/Context;)Lcom/google/common/util/concurrent/ListenableFuture; in class Landroidx/camera/core/CameraX; or its super classes (declaration of 'androidx.camera.core.CameraX' appears in /data/app/com.rustfisher.tutorial2020-1/base.apk)    at androidx.camera.lifecycle.ProcessCameraProvider.getInstance(ProcessCameraProvider.java:149)    at com.rustfisher.tutorial2020.camera.SimplePreviewXAct.onCreate(SimplePreviewXAct.java:36)    at android.app.Activity.performCreate(Activity.java:6161)    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1112)    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2507)    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2640)    at android.app.ActivityThread.access$800(ActivityThread.java:182)    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1493)    at android.os.Handler.dispatchMessage(Handler.java:111)    at android.os.Looper.loop(Looper.java:194)    at android.app.ActivityThread.main(ActivityThread.java:5682)    at java.lang.reflect.Method.invoke(Native Method)    at java.lang.reflect.Method.invoke(Method.java:372)    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:963)    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:758)```

权限#

需要动态申请android.permission.CAMERA权限

<uses-permission android:name="android.permission.CAMERA" />

本文略过动态申请权限的地方

界面#

CameraX为开发者贴心地准备了androidx.camera.view.PreviewView

把它放在一个FrameLayout里,如下的act_simple_preivew_x.layout

<?xml version="1.0" encoding="utf-8"?><layout>    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"        android:id="@+id/container"        android:layout_width="match_parent"        android:layout_height="match_parent">        <androidx.camera.view.PreviewView            android:id="@+id/previewView"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </FrameLayout></layout>

开启预览#

在activity中开启相机预览

// SimplePreviewXAct.javaimport android.os.Bundle;import androidx.annotation.NonNull;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;import androidx.camera.core.Camera;import androidx.camera.core.CameraSelector;import androidx.camera.core.Preview;import androidx.camera.lifecycle.ProcessCameraProvider;import androidx.core.content.ContextCompat;import androidx.databinding.DataBindingUtil;import androidx.lifecycle.LifecycleOwner;import com.google.common.util.concurrent.ListenableFuture;// import com.rustfisher.tutorial2020.R;// import com.rustfisher.tutorial2020.databinding.ActSimplePreivewXBinding;import java.util.concurrent.ExecutionException;public class SimplePreviewXAct extends AppCompatActivity {    private ActSimplePreivewXBinding mBinding;    private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mBinding = DataBindingUtil.setContentView(this, R.layout.act_simple_preivew_x);        cameraProviderFuture = ProcessCameraProvider.getInstance(this);        cameraProviderFuture.addListener(() -> {            try {                ProcessCameraProvider cameraProvider = cameraProviderFuture.get();                bindPreview(cameraProvider);            } catch (ExecutionException | InterruptedException e) {                // 这里不用处理            }        }, ContextCompat.getMainExecutor(this));    }    void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {        Preview preview = new Preview.Builder().build();        CameraSelector cameraSelector = new CameraSelector.Builder()                .requireLensFacing(CameraSelector.LENS_FACING_BACK)                .build();        preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider());        Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);    }}

注意我们这里使用的是androidx.appcompat.app.AppCompatActivity

为了获得ProcessCameraProvider,用ProcessCameraProvider.getInstance方法拿到一个cameraProviderFuture
cameraProviderFuture完成后取出ProcessCameraProvider(cameraProvider)。

要开启预览,通过Preview.Builder构建一个Preview。用CameraSelector来选择后置摄像头。
Preview的SurfaceProvider由layout中的androidx.camera.view.PreviewView提供。

cameraProvider.bindToLifecycle绑定上后,启动摄像头预览

运行测试#

运行到手机上,打开这个Activity就可以看到摄像头预览。图像宽高比正常,没有拉伸现象。

  • 荣耀 EMUI 3.1 Lite,Android 5.1 运行正常
  • Redmi 9A,MIUI 12.5.1稳定版,Android 10 运行正常
  • 一加5,H2OS 10.0.3,Android 10 运行正常

增加开关#

在layout里加2个按钮,控制相机开关

<?xml version="1.0" encoding="utf-8"?><layout>    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical">        <LinearLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@color/colorPrimaryDark"            android:gravity="center"            android:orientation="horizontal"            android:padding="4dp">            <Button                android:id="@+id/start"                style="@style/NormalBtn"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="打开" />            <Button                android:id="@+id/end"                style="@style/NormalBtn"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_marginStart="12dp"                android:text="关闭" />        </LinearLayout>        <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"            android:id="@+id/container"            android:layout_width="match_parent"            android:layout_height="match_parent">            <androidx.camera.view.PreviewView                android:id="@+id/previewView"                android:layout_width="match_parent"                android:layout_height="match_parent" />        </FrameLayout>    </LinearLayout></layout>

根layout换成LinearLayout

修改bindPreview方法,先检查传入的ProcessCameraProvider是否为空

private void bindPreview(ProcessCameraProvider cameraProvider) {    if (cameraProvider == null) {        Toast.makeText(getApplicationContext(), "没获取到相机", Toast.LENGTH_SHORT).show();        return;    }    Preview preview = new Preview.Builder().build();    CameraSelector cameraSelector = new CameraSelector.Builder()            .requireLensFacing(CameraSelector.LENS_FACING_BACK)            .build();    preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider());    Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);}

修改后的activity部分代码

import android.os.Bundle;import android.widget.Toast;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;import androidx.camera.core.Camera;import androidx.camera.core.CameraSelector;import androidx.camera.core.Preview;import androidx.camera.lifecycle.ProcessCameraProvider;import androidx.core.content.ContextCompat;import androidx.databinding.DataBindingUtil;import com.google.common.util.concurrent.ListenableFuture;// import com.rustfisher.tutorial2020.R;// import com.rustfisher.tutorial2020.databinding.ActSimplePreivewXBinding;import java.util.concurrent.ExecutionException;public class SimplePreviewXAct extends AppCompatActivity {    private ActSimplePreivewXBinding mBinding;    private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;    private ProcessCameraProvider mCameraProvider;    private boolean mRunning = false;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mBinding = DataBindingUtil.setContentView(this, R.layout.act_simple_preivew_x);        mCameraProviderFuture = ProcessCameraProvider.getInstance(this);        mCameraProviderFuture.addListener(() -> {            try {                mCameraProvider = mCameraProviderFuture.get();            } catch (ExecutionException | InterruptedException e) {                // 这里不用处理            }        }, ContextCompat.getMainExecutor(this));        mBinding.start.setOnClickListener(v -> {            if (mCameraProvider != null && !mRunning) {                bindPreview(mCameraProvider);            }        });        mBinding.end.setOnClickListener(v -> {            mCameraProvider.unbindAll();            mRunning = false;        });    }    private void bindPreview(ProcessCameraProvider cameraProvider) {        if (cameraProvider == null) {            Toast.makeText(getApplicationContext(), "没获取到相机", Toast.LENGTH_SHORT).show();            return;        }        Preview preview = new Preview.Builder().build();        CameraSelector cameraSelector = new CameraSelector.Builder()                .requireLensFacing(CameraSelector.LENS_FACING_BACK)                .build();        preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider());        Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);        mRunning = true;    }}

拿到mCameraProvider后不要立刻绑定生命周期。

如果要开启预览,则调用bindPreview(mCameraProvider)。记录一下现在相机已经开启预览mRunning = true

如果要停止预览,则解绑生命周期mCameraProvider.unbindAll()。这个方法需要在主线程调用。

运行起来后,可以用按钮来控制相机预览的开关。相比之前,PreviewView的高度变小了一点(让了点位置给按钮)。
视频宽高比例正常,没有被拉伸。默认的配置下,还有自动对焦的功能

小结#

从简单的打开相机预览来看,CameraX简化了开发者的工作。提供了PreviewView,开发者不需要自定义SurfaceView或者TextureView。实时预览中,相机能够自动对焦。本文用的是1.1.0-alpha11,而CameraX还在发展之中。

参考#

  • camerax实现预览 - developer.android.com
  • https://developer.android.com/jetpack/androidx/releases/camera
  • NoSuchMethodError - stackoverflow

原文链接CameraX 打开摄像头预览

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部