一、为什么我们需要CameraX?
如果你曾经尝试过在Android上开发相机功能,可能会觉得有点头疼。原生的Camera API虽然强大,但代码复杂,不同厂商的设备表现还不一致,适配起来就像在走钢丝。后来有了Camera2 API,功能更精细,但学习曲线也更陡峭,写个简单的预览都要一大堆代码。
这时候,CameraX就像一位救星出现了。它是Google推出的Jetpack组件库的一员,专门为了简化相机开发而生。你可以把它理解为一个“相机管家”。你只需要告诉它:“我想预览”、“我想拍照”或者“我想分析画面”,它就会帮你处理好背后所有繁琐的事情,比如生命周期管理、设备兼容性、相机操作线程等等。它基于Camera2,但提供了更简单、更一致的API,让我们能更专注于业务逻辑,而不是和设备碎片化做斗争。
简单来说,如果你想快速、稳定地为应用添加相机功能,CameraX是目前最值得推荐的选择。
二、搭建环境与基础准备
在开始写代码之前,我们得先把“厨房”准备好。首先,确保你的build.gradle文件里已经添加了必要的依赖。CameraX的版本更新较快,建议使用最新的稳定版。
技术栈:Android (Kotlin + CameraX)
// 在app模块的build.gradle文件中添加依赖
dependencies {
// CameraX 核心库
def camerax_version = "1.3.0"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// 如果你需要使用生命周期自动管理
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// 预览视图,这是实现预览的关键
implementation "androidx.camera:camera-view:1.3.0"
// 其他可能用到的扩展,比如视频拍摄
// implementation "androidx.camera:camera-video:${camerax_version}"
}
然后,别忘了在AndroidManifest.xml文件中声明相机权限。一个完整的相机应用通常需要这些权限:
<manifest ...>
<!-- 使用相机所需的权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 如果需要将照片保存到外部存储,还需要这个 -->
<uses-feature android:name="android.hardware.camera" android:required="true" />
<application ...>
...
</application>
</manifest>
权限的动态申请是Android开发的基础课,这里我们假设你已经处理好运行时权限请求的逻辑。准备好这些,我们的舞台就搭好了。
三、实现相机预览:让画面动起来
相机预览是用户最直观的感受,也是所有相机操作的基础。在CameraX中,我们使用Preview用例来实现它。这个过程就像搭积木:配置预览用例 -> 绑定到相机生命周期 -> 将画面显示到屏幕上。
技术栈:Android (Kotlin + CameraX)
class CameraPreviewActivity : AppCompatActivity() {
// 1. 声明关键组件
private lateinit var previewView: PreviewView // 用于显示预览画面的视图
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera_preview)
// 2. 初始化预览视图
previewView = findViewById(R.id.preview_view)
// 3. 获取相机提供者的未来对象(异步操作)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
// 4. 添加监听器,当相机提供者准备就绪后执行绑定
cameraProviderFuture?.addListener(Runnable {
// 获取相机提供者实例
val cameraProvider = cameraProviderFuture?.get() ?: return@Runnable
// 5. 创建并配置预览用例
val preview = Preview.Builder()
.build()
.also {
// 设置SurfaceProvider,将预览画面输出到previewView
it.setSurfaceProvider(previewView.surfaceProvider)
}
// 6. 选择后置摄像头作为默认摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// 7. 解除所有已绑定用例,准备重新绑定
cameraProvider.unbindAll()
// 8. 将用例绑定到当前Activity的生命周期
// 这行代码是核心:相机生命周期将自动与Activity同步(启动、停止、销毁)
cameraProvider.bindToLifecycle(
this, // 生命周期所有者
cameraSelector, // 摄像头选择器
preview // 要绑定的用例(这里只有预览)
)
} catch (exc: Exception) {
Log.e("CameraX", "用例绑定失败", exc)
}
}, ContextCompat.getMainExecutor(this)) // 在主线程执行监听器
}
}
对应的布局文件activity_camera_preview.xml非常简单:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- CameraX提供的专用预览视图,自动处理画面拉伸和旋转 -->
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
运行这段代码,你应该就能看到后置摄像头的实时画面了。CameraX的PreviewView会自动处理屏幕旋转和不同尺寸的适配,省去了我们很多计算工作。这里的关键是bindToLifecycle()方法,它建立了相机与UI生命周期的关联,当界面不可见时,相机会自动释放,避免资源浪费和潜在错误。
四、实现拍照功能:捕捉精彩瞬间
有了预览,接下来就是拍照了。在CameraX中,拍照功能由ImageCapture用例负责。我们可以把它和预览用例一起绑定到相机上,让相机同时服务于预览和拍照。
技术栈:Android (Kotlin + CameraX)
class CameraCaptureActivity : AppCompatActivity() {
private lateinit var previewView: PreviewView
private lateinit var btnCapture: Button
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
private var imageCapture: ImageCapture? = null // 新增:拍照用例
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera_capture)
previewView = findViewById(R.id.preview_view)
btnCapture = findViewById(R.id.btn_capture)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture?.addListener(Runnable {
val cameraProvider = cameraProviderFuture?.get() ?: return@Runnable
// 1. 创建预览用例(和之前一样)
val preview = Preview.Builder()
.build()
.also { it.setSurfaceProvider(previewView.surfaceProvider) }
// 2. 创建拍照用例,并配置高质量输出
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // 模式:最小化延迟
.setTargetRotation(previewView.display.rotation) // 设置目标旋转,与预览一致
.build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
// 3. 将预览和拍照两个用例同时绑定到生命周期
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageCapture // 绑定拍照用例
)
} catch (exc: Exception) {
Log.e("CameraX", "用例绑定失败", exc)
}
}, ContextCompat.getMainExecutor(this))
// 4. 为拍照按钮设置点击事件
btnCapture.setOnClickListener {
takePhoto()
}
}
private fun takePhoto() {
// 获取拍照用例实例,确保已初始化
val imageCapture = this.imageCapture ?: return
// 5. 创建存储照片的元数据
val name = "JPEG_${System.currentTimeMillis()}.jpg"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
// 如果针对Android 10及以上,可以指定相对路径
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Demo")
}
}
// 6. 创建输出选项,指定将照片保存到MediaStore
val outputOptions = ImageCapture.OutputFileOptions.Builder(
contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
).build()
// 7. 执行拍照操作(这是一个异步操作)
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
// 拍照成功回调
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri
val msg = "照片保存成功: ${savedUri ?: "未知URI"}"
Toast.makeText(this@CameraCaptureActivity, msg, Toast.LENGTH_SHORT).show()
Log.d("CameraX", msg)
}
// 拍照失败回调
override fun onError(exception: ImageCaptureException) {
val msg = "拍照失败: ${exception.message}"
Toast.makeText(this@CameraPreviewActivity, msg, Toast.LENGTH_SHORT).show()
Log.e("CameraX", msg, exception)
}
}
)
}
}
在这个例子中,我们创建了一个ImageCapture用例,并通过bindToLifecycle将它和Preview用例一起绑定。这意味着相机可以同时处理预览流和拍照请求。当用户点击按钮时,takePhoto()方法被调用,它配置了照片的输出位置(这里是系统的相册目录),然后异步执行拍照。拍照完成后,无论成功或失败,都会在对应的回调中通知我们。
你可以根据需要修改输出选项,比如保存到应用私有目录、自定义文件名等。ImageCapture还支持内存中回调(takePicture(Executor, OnImageCapturedCallback)),让你直接获取到图像字节数据进行处理,这在需要上传或即时识别的场景非常有用。
五、应对常见核心问题与进阶技巧
在实际开发中,你可能会遇到一些典型问题。这里分享几个常见场景的解决方案。
1. 预览画面拉伸或变形
这通常是因为预览视图的宽高比与相机传感器输出不匹配。PreviewView提供了几种缩放类型来应对:
previewView.scaleType = PreviewView.ScaleType.FILL_CENTER // 填充视图,可能裁剪画面
// 或者
previewView.scaleType = PreviewView.ScaleType.FIT_CENTER // 适应视图,可能留黑边
通常,FIT_CENTER能保证画面完整,而FILL_CENTER能保证视图被填满。你可以根据UI设计来选择。
2. 前后摄像头切换
切换摄像头本质上是更换CameraSelector并重新绑定。
技术栈:Android (Kotlin + CameraX)
// 假设有一个切换按钮 btnSwitch
private var isBackCamera = true // 标记当前是否为后置摄像头
btnSwitch.setOnClickListener {
isBackCamera = !isBackCamera
switchCamera()
}
private fun switchCamera() {
val cameraProvider = cameraProviderFuture?.get() ?: return
val newSelector = if (isBackCamera) {
CameraSelector.DEFAULT_BACK_CAMERA
} else {
CameraSelector.DEFAULT_FRONT_CAMERA
}
// 重新配置并绑定用例
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
imageCapture = ImageCapture.Builder().build() // 重建拍照用例以应用新的摄像头参数
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, newSelector, preview, imageCapture)
} catch (e: Exception) {
Log.e("CameraX", "摄像头切换失败", e)
}
}
3. 对焦与变焦
CameraX提供了简洁的API来控制这些功能。你可以通过CameraControl对象来操作。
// 在成功绑定相机后,可以通过camera对象获取CameraControl
// val camera = cameraProvider.bindToLifecycle(...) // bindToLifecycle会返回Camera对象
// val cameraControl = camera.cameraControl
// 设置线性对焦区域(例如,点击屏幕某点对焦)
// cameraControl.startFocusAndMetering(FocusMeteringAction.Builder(point, MeteringMode.AF).build())
// 设置变焦比例(1.0f为原始大小)
// cameraControl.setZoomRatio(2.0f)
在实际应用中,你可以在PreviewView上添加触摸监听,将触摸点坐标转换为对焦点。
六、应用场景、优缺点与注意事项
应用场景 CameraX非常适合需要快速集成稳定相机功能的应用,例如:
- 社交应用:用于拍摄并分享照片、短视频。
- 工具类应用:扫码、文档扫描、测量工具等。
- 教育类应用:在线作业拍照上传、实验记录等。
- 企业应用:工单拍照、现场巡检记录等。
在这些场景中,开发者更关注业务逻辑而非底层相机控制,CameraX的高层抽象能极大提升开发效率。
技术优缺点 优点:
- 简单易用:API设计直观,大幅减少了样板代码。
- 生命周期感知:自动管理相机开启和关闭,避免内存泄漏和错误。
- 设备兼容性:通过供应商扩展库,在不同厂商设备上提供一致体验,解决了碎片化难题。
- 用例组合:可以轻松组合预览、拍照、分析等多个用例,功能强大。
缺点:
- 灵活性相对受限:对于需要极精细控制相机参数(如手动对焦、长时间曝光、RAW拍摄)的高级专业应用,原生Camera2 API可能更合适。
- 新特性支持有延迟:CameraX需要时间适配Android和硬件厂商的最新相机特性。
注意事项
- 权限是前提:务必处理好运行时权限申请,并在
onRequestPermissionsResult中根据授权结果初始化或关闭相机。 - 后台限制:当应用退到后台,CameraX会自动暂停相机。从后台返回时,
PreviewView可能需要一点时间重新渲染画面,这是正常现象。 - 测试多设备:虽然CameraX解决了大部分兼容性问题,但仍建议在几种不同品牌、不同系统的真机上进行测试,特别是前后摄像头切换和闪光灯功能。
- 资源清理:虽然
bindToLifecycle能自动管理,但在Activity/Fragment销毁时,确保不再持有对PreviewView或ImageCapture等对象的引用是一个好习惯。
七、总结
通过本文的探索,我们可以看到CameraX将Android相机开发从一项复杂任务变成了一个相对愉快的过程。它通过“用例”这一核心概念,将预览、拍照、分析等常见需求模块化,让我们能够像搭积木一样构建相机功能。其强大的生命周期管理和设备兼容性处理,更是为我们扫清了开发路上的主要障碍。
从简单的预览到完整的拍照流程,再到摄像头切换等进阶控制,CameraX都提供了清晰而稳健的API。尽管它在超高级控制上有所取舍,但对于绝大多数应用场景来说,它提供的功能、稳定性和开发效率的提升是无可比拟的。
建议你在下一个需要相机功能的项目中尝试CameraX,从实现一个简单的预览开始,逐步添加拍照、闪光灯控制等功能,亲身体验它带来的便捷。相信它会成为你Android开发工具箱中得力的一员。
评论