什么是CameraX(快乐星球)

       在 Android 应用中要实现 Camera 功能还是比较困难的,为了保证在各品牌手机设备上的兼容性、响应速度等体验细节,Camera 应用的开发者往往需要花很大的时间和精力进行测试,甚至需要手动在数百种不同设备上进行测试。CameraX 正是为解决这个痛点而诞生的。另外,CameraX 基于 Camera2 API 实现,它极大地简化了在 minSdk 21 及以上版本的实现过程。这篇文章为大家准备了CameraX的使用。
       个人使用过后感觉比Camera和Camera2 更加简单易用,且兼容性更好,不会出现常见的聚焦失败等问题。同样后面有完整代码。

有什么知识?

了解如何添加CameraX依赖项。
了解如何在活动中显示相机预览。(预览用例)
了解拍照后并将其保存到存储中。(ImageCapture用例)
了解如何实时分析相机中的帧。(ImageAnalysis用例)

需要什么?

Android设备。
Android Studio的模拟器也可以使用
我们建议使用R及更高版本。图像分析用例不适用于低于R的任何对象。
支持的最低API级别为21。
Android Studio 3.6或更高版本。

步骤

1、构建项目这里不说了
2、添加Gradle依赖
def camerax_version = "1.0.0-beta03"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha10"

使用CameraX中可能会用到Java8的方法,因此我们需要相应地设置编译选项。在该android块的末尾,紧接着buildTypes,添加以下内容:

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
3、XML布局

PreviewView内部是surface实现的 是一个TextureView

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/camera_capture_button"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="50dp"
        android:scaleType="fitCenter"
        android:text="Take Photo"
        android:elevation="2dp" />

    <!--内部是surface实现的 是一个TextureView-->
    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
4、申请权限

我们在AndroidManifest.xml中设置:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera.any" />

CameraXCapture.Java

private final String[] PERMISSIONS = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

OnCreate中

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
        }
5、开启Camera和预览
public class CameraXCapture extends AppCompatActivity{

    private String TAG = "CameraXBasic";
    private String FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS";

    private Preview preview = null;
    private ImageCapture imageCapture;
    //分析
    private ImageAnalysis imageAnalysis;
    private Camera camera = null;

    private File outputDirectory;
    private ExecutorService cameraExecutor;
    private PreviewView viewFinder;

    private final String[] PERMISSIONS = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camerax_capture);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
        }
        startCamera();
        viewFinder = findViewById(R.id.viewFinder);
        // Setup the listener for take photo button
        findViewById(R.id.camera_capture_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //takePhoto();
            }
        });
        outputDirectory = getOutputDirectory();

        cameraExecutor = Executors.newSingleThreadExecutor();
    }

    /**
     * 开启预览
     */
    private void startCamera() {
        //创建的实例ProcessCameraProvider。这用于将摄像机的生命周期绑定到生命周期所有者。
        // 由于CameraX具有生命周期感知功能,因此您不必担心打开和关闭相机。
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
        //侦听器添加到中cameraProviderFuture。添加一个Runnable作为一个参数,
        // 稍后我们将对其进行填充。添加作为第二个参数,这将返回一个在主线程上运行的。
        // ContextCompat.getMainExecutor()Executor
        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {
                    // Used to bind the lifecycle of cameras to the lifecycle owner
                    //添加ProcessCameraProvider,用于将相机的生命周期绑定到应用程序进程中的LifecycleOwner
                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    //Preview
                    //初始化您的Preview对象。
                    preview = new Preview.Builder().build(); 
                    // select cake camera
                    //一个CameraSelector对象,然后使用该 CameraSelector.Builder.requireLensFacing方法传递您喜欢的镜头。
                    CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT).build();
                    // Unbind use cases before rebinding
                    cameraProvider.unbind();
                    //Bind Use cases to camera
                    //然后将cameraSelector和预览对象绑定到cameraProvider。
                    // 将viewFinder的Surface提供程序附加到preview用例
                    camera = cameraProvider.bindToLifecycle(CameraXCapture.this, cameraSelector, preview, imageCapture, imageAnalysis);
                    preview.setSurfaceProvider(viewFinder.createSurfaceProvider(camera.getCameraInfo()));
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, ContextCompat.getMainExecutor(this));
    }

6、完整代码带takePhoto拍照和实时数据帧数据

运行后,点击按钮可以看到提示,保存照片的路径。

查看命令行窗口logcat可以看到:(随时捕获的帧数据)

android 打开相机demo android camerax_生命周期

public class CameraXCapture extends AppCompatActivity{

    private String TAG = "CameraXBasic";
    private String FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS";

    private Preview preview = null;
    private ImageCapture imageCapture;
    //分析
    private ImageAnalysis imageAnalysis;
    private Camera camera = null;

    private File outputDirectory;
    private ExecutorService cameraExecutor;
    private PreviewView viewFinder;

    private final String[] PERMISSIONS = new String[]{
            Manifest.permission.CAMERA,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camerax_capture);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, PERMISSIONS, 1);
        }
        startCamera();
        viewFinder = findViewById(R.id.viewFinder);
        // Setup the listener for take photo button
        findViewById(R.id.camera_capture_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                takePhoto();
            }
        });
        outputDirectory = getOutputDirectory();

        cameraExecutor = Executors.newSingleThreadExecutor();
    }

    /**
     * 开启预览
     */
    private void startCamera() {
        //创建的实例ProcessCameraProvider。这用于将摄像机的生命周期绑定到生命周期所有者。
        // 由于CameraX具有生命周期感知功能,因此您不必担心打开和关闭相机。
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
        //侦听器添加到中cameraProviderFuture。添加一个Runnable作为一个参数,
        // 稍后我们将对其进行填充。添加作为第二个参数,这将返回一个在主线程上运行的。
        // ContextCompat.getMainExecutor()Executor
        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {
                    // Used to bind the lifecycle of cameras to the lifecycle owner
                    //添加ProcessCameraProvider,用于将相机的生命周期绑定到应用程序进程中的LifecycleOwner
                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    //Preview
                    //初始化您的Preview对象。
                    preview = new Preview.Builder().build();
                    //创建imageCapture
                    imageCapture = new ImageCapture.Builder().build();
                    //创建imageAnalyzer
                    imageAnalysis = new ImageAnalysis.Builder().build();
                    imageAnalysis.setAnalyzer(cameraExecutor, new LuminosityAnalyzer());
                    // select cake camera
                    //一个CameraSelector对象,然后使用该 CameraSelector.Builder.requireLensFacing方法传递您喜欢的镜头。
                    CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT).build();
                    // Unbind use cases before rebinding
                    cameraProvider.unbind();
                    //Bind Use cases to camera
                    //然后将cameraSelector和预览对象绑定到cameraProvider。
                    // 将viewFinder的Surface提供程序附加到preview用例
                    camera = cameraProvider.bindToLifecycle(CameraXCapture.this, cameraSelector, preview, imageCapture, imageAnalysis);
                    preview.setSurfaceProvider(viewFinder.createSurfaceProvider(camera.getCameraInfo()));
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, ContextCompat.getMainExecutor(this));
    }

    /**
     * 拍照
     */
    private void takePhoto() {
        // Create timestamped output file to hold the image
        File photoFile = new File(outputDirectory, new SimpleDateFormat(FILENAME_FORMAT, Locale.US).format(System.currentTimeMillis()) + ".jpg");
        // Create output options object which contains file + metadata
        ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(photoFile).build();
        // Setup image capture listener which is triggered after photo has
        // been taken
        imageCapture.takePicture(
                outputOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        Uri uri = Uri.fromFile(photoFile);
                        Toast.makeText(CameraXCapture.this, uri.toString(), Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onError(@NonNull ImageCaptureException exception) {
                        Log.e(TAG, "Photo capture failed:die");
                    }
                }
        );
    }

    private File getOutputDirectory(){
        File file = new File(Environment.getExternalStorageDirectory(), "camerax");
        if (!file.exists()){
            file.mkdirs();
        }
        return file;
    }

    private class LuminosityAnalyzer implements ImageAnalysis.Analyzer{

        @Override
        public void analyze(@NonNull ImageProxy image) {
            Log.e(TAG, "analyze: ...");
            image.close();
        }
    }
}