信息发布→ 登录 注册 退出

Android CameraX 打开摄像头预览功能

发布时间:2026-01-11

点击量:
目录
  • 引入依赖
  • 权限
  • 界面
  • 开启预览
    • 运行测试
  • 增加开关
    • 小结

      目标很简单,用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"

      ```log
      CrashHandler: 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.tutorial2025-1/base.apk)
          at androidx.camera.lifecycle.ProcessCameraProvider.getInstance(ProcessCameraProvider.java:149)
          at com.rustfisher.tutorial2025.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.java
      import 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.tutorial2025.R;
      // import com.rustfisher.tutorial2025.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.tutorial2025.R;
      // import com.rustfisher.tutorial2025.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 打开摄像头预览

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!