概览
CameraX是一个Jetpack支持库,用来帮助开发者简化相机应用的开发和适配工作,它将一系列旧的Camera和新的Camera2的相机API进行统一适配,将两套设计迥异的相机API进行适配设计,并且作为androidX并作为Jetpack的一部分再发布,用来简化开发者开发相机功能时的大量重复适配的工作。最低可以适配到API Level 21。
由于加入到了Jetpack组件当中,它也增加了一部分功能用来绑定到LifecycleOwner等API来适配到生命周期接口的能力,使得开发者无需再重复编写生命周期相关的模板代码。
用例组件
CameraX内置了几个常用的用例,基类是UserCase
,从UserCase
中派生出了以下三种使用场景:
- 预览
Preview
:被用于显示预览,显示时会使用Surface展示预览,因此还有一个自定义View名为PreviewView
来内部选择性使用SurfaceView
或者TextureView
来展现Surface的预览内容。 - 图像分析
ImageAnalyze
:ImageAnalyze
提供了一个接口ImageAnalysis.Analyzer
用于在预览的同时可以获取图像缓冲区数据用于分析,比如机器学习、计算机视觉与图形学处理等等。 - 图片拍摄
ImageCapture
:ImageCapture
提供了一个接口并在发生图片保存动作时进行回调。
首次使用
我们需要对build.gradle文件进行一定的修改:
我们用到了Kotlin语言,我们需要引入Kotlin语言包,
语言版本:
ext.kotlin_version = '1.5.21'
ext.java_version = JavaVersion.VERSION_1_8
添加Kotlin语言标准库和android支持:
dependencies {
// Kotlin lang
implementation 'androidx.core:core-ktx:1.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
}
我们需要引入CameraX相关的包
dependencies {
// CameraX 核心库
def camerax_version = '1.0.1'
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 扩展包
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle 生命周期框架支持
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View 用于预览的View
implementation 'androidx.camera:camera-view:1.0.0-alpha27'
}
预览用例
仅仅对于相机的预览来说,我们用到的类组件由以下几个类即可,不需要使用到Camera类。
Step 1:
首先我们先布局一个可预览的PreviewView。这个View是已经在androidx.camera中预置的自定义View:
<FrameLayout
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>
由于这个View已经包含了SurfaceView或TextureView,我们无需再考虑处理这部分的逻辑。
Step 2:
我们通过ProcessCameraProvider.getInstance(this)
得到一个线程池的一个可等待对象,这个类型是ListenableFuture<ProcessCameraProvider>
,
lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
然后在这个延迟等待对象中执行代码,并且运行在主线程Looper的Handler中。
cameraProviderFuture.addListener({
}, ContextCompat.getMainExecutor(this))
Step 3:
我们获取ProcessCameraProvider
,用于获取Camera
并且绑定Surface到PreviewView上。
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
//在这里绑定Preview
bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(this))
bindPreview
的函数定义如下:
fun bindPreview(cameraProvider: ProcessCameraProvider) {
var preview: Preview = Preview.Builder().build()
var cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
val previewView = findViewById<PreviewView>(R.id.previewView)
preview.setSurfaceProvider(previewView.surfaceProvider)
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
}
拆解一下这个函数每一句话的作用:
构建一个Preview用例
var preview: Preview = Preview.Builder().build()
选择摄像头
CameraSelector.LENS_FACING_BACK
表示请求的镜头朝向是后置的,
CameraSelector.LENS_FACING_BACK
表示请求的镜头朝向是前置的。
var cameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
绑定PreviewView
Preview要显示预览内容必须将其与PreviewView中的SurfaceView或Texture的Surface绑定。
Preview.setSurfaceProvider(SurfaceProvider)
方法承担了这个最主要的工作。
val previewView = findViewById<PreviewView>(R.id.previewView)
preview.setSurfaceProvider(previewView.surfaceProvider)
将相机对象绑定到生命周期接口上
最后一步通过ProcessCameraProvider
将PreviewView预览、Preview用例、CameraSelector相机选择对象
统合并且绑定。
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
ProcessCameraProvider.bindToLifecycle()的最后一个参数是可变长度参数,可以传入多个UseCase的实现类,比如同时传入Preview的用例和ImageCapture的用例,实现预览并拍照的功能。如果加入ImageAnalyze还可以对缓冲区进行分析和其他操作。
一个DemoActivity的代码
这样就可以通过CameraX简化并且自动化选择相机参数显示相机预览,代码十分精简:
class DemoActivity : AppCompatActivity() {
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_demo)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
//在这里绑定Preview
bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(this))
}
fun bindPreview(cameraProvider: ProcessCameraProvider) {
var preview: Preview = Preview.Builder().build()
var cameraSelector: CameraSelector =
CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
val previewView = findViewById<PreviewView>(R.id.previewView)
preview.setSurfaceProvider(previewView.surfaceProvider)
cameraProvider.bindToLifecycle(this, cameraSelector, preview)
}
}
然而其内部判断是SurfaceView还是TextureView呢?PreviewView类中给出了一段代码
static boolean shouldUseTextureView(@NonNull SurfaceRequest surfaceRequest,
@NonNull final ImplementationMode implementationMode) {
// TODO(b/159127402): use TextureView if target rotation is not display rotation.
boolean isLegacyDevice = surfaceRequest.getCamera().getCameraInfoInternal()
.getImplementationType().equals(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
boolean hasSurfaceViewQuirk = DeviceQuirks.get(SurfaceViewStretchedQuirk.class) != null;
if (surfaceRequest.isRGBA8888Required() || Build.VERSION.SDK_INT <= 24 || isLegacyDevice
|| hasSurfaceViewQuirk) {
// Force to use TextureView when the device is running android 7.0 and below, legacy
// level, RGBA8888 is required or SurfaceView has quirks.
return true;
}
switch (implementationMode) {
case COMPATIBLE:
return true;
case PERFORMANCE:
return false;
default:
throw new IllegalArgumentException(
"Invalid implementation mode: " + implementationMode);
}
}
看得出来其给出了4个条件,只要符合其一就使用TextureView,并且通过可以通过手动设定模式来强制选择。
我们看一下这四个条件:
- 1、如果这个设备是较老的设备,只要判定在Android 7.0以下;
- 2、如果摄像头支持级别是旧的级别;
- 3、如果摄像头请求要求的色彩空间是RGB8888时;
- 4、设备厂商的兼容性问题
除此以外都使用SurfaceView。因此我们可以看出来,Android团队是多么偏爱SurfaceView。
当对PreviewView选择了特定实现模式是COMPATIBLE
时,也是使用TextureView,选择实现模式是PERFORMANCE
时,使用SurfaceView
,并且使得以上4个条件失效。