In my previous article about CameraX I covered the challenges we are facing by using oldest Camera APIs and what are the advantages of using this new API.
Use-case-driven approach is one of the most important advantages of using CameraX. UseCase is an abstract class and currently it supports 3 implementations, and each use case is fully independent. We could use only one of them or we could combine them.
Preview Use Case
Use a surface to display a a live feed of the camera.
For each use case first of all we will check the implementation steps and after that the assigned code for each step.
Step 1: add gradle dependencies
Step 2: permission handling
- Adding android.hardware.camera.any makes sure that the device has a camera. Specifying .any means that it can be a front camera or a back camera.
- If we use android.hardware.camera without .any, it will not work if you have a device without a back camera, such as most Chromebooks. The second line adds the permission to access that camera.
Step 3: add PreviewView in a layout
- Since beta release, PreviewView is the recommended way of implementing camera preview.
- If you want to use your own surface (TextureView, for example), then you must implement your own surface provider as well as making sure that you handle sizing and orientation appropriately, which can be tricky.
What’s happening behind the scenes?
- PreviewView is a custom view that displays the camera feed for CameraX’s preview use case.
- PreviewView also extends the FrameLayout which we all know that is a ViewGroup.
- The default implementation is SurfaceView.
- But if the camera device is running in backward compatibility mode, in code it means that devices have a supported camera hardware level CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, the actual implementation mode will be TextureView.
For fine control when CameraX gets initialized, we can implement the CameraXConfig.Provider interface in our Application class.
Step 4: create an instance of the ProcessCameraProvider
- ProcessCameraProvider is a singleton which can be used to bind the lifecycle of camera to any LifecycleOwner within an application’s process.
- Only a single process camera provider can exist within a process, and it can be retrieved with getInstance(Context).
- It help us to not worry about opening and closing the camera since CameraX is lifecycle aware.
- cameraExecutor is an Executor that runs on the main thread.
- Add a listener to the cameraProviderFuture that contains 2 arguments: a Runnable and the executor.
Step 5: select Camera and bind it to the lifecycle
- This code will be added in the Runnable
- Also it is recommended to create a try block and inside of it to add this code. This code can fail, for example if the app is no longer in focus.
- Unbind use cases before rebinding.
- It is recommended that we bind our use cases to the lifecycle via CameraX in a single call and using the same lifecycle owner.
- By providing all the use cases at once, we give CameraX the opportunity to find a compromise such that all use cases can run.
Capture Use Case
It used to save high-quality images.
Step 1: create ImageCapture reference
- If we want to have more control when an image is captured we could add to the builder different features like photo capture optimizations, flash mode, aspect ratio or target resolution.
- Capture mode: if not set the default value is ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
- Flash mode: if not set the default value is ImageCapture.FLASH_MODE_OFF
- Aspect ratio: if not set, resolutions with aspect ratio 4:3 will be considered in higher priority.
- setTargetResolution: if not set, the largest available resolution will be selected to use.
- setTargetName (debug purpose): if not set, the target name will default to a unique name automatically generated with the class canonical name and random UUID
Step 2: add orientation event listener
Step 3: image file management
- ImageCapture.OutputFileOptions give us options for saving the captured image
- The builder of this class is used to configure the save location that can be either:
- a File (like in the code sample)
- a MediaStore or
- an OutputStream.
Step 4: call takePicture()
- This method captures the image and saves it to a file provided as an argument (the first one)
- The second argument represents the executor in which the callback methods will be run
- The callback will be called only once for every invocation of this method and it contains the implementation of 2 methods
- onImageSaved: called when an image has been successfully saved
- onError: called when an error occurs while trying to save the image
Step 5: update the call to bind lifecycle
The Image Capture use case is designed to capture high definition and high quality photos and provides auto white balance, auto exposure and auto focus (3A) functions, as well as simple manual camera control. The caller is responsible for deciding how to use the captured image:
- takePicture(Executor, OnImageCapturedCallback): provides an in-memory buffer of the captured image.
- takePicture(OutputFileOptions, Executor, OnImageSavedCallback): saves the captured image to the provided file location.
Enjoy and feel free to leave a comment if something is not clear or if you have questions. And if you like it please share !
Thank you for reading!