第1章:CameraX快速上手——添加依赖、配置PreviewView、实现Preview用例、实现ImageCapture用例
大家好,我是你们的老朋友。今天咱们来聊聊CameraX这个库。说实话,我第一次接触Android摄像头开发时,被Camera1那套回调机制折腾得够呛。后来Google推出了CameraX,我才觉得「嗯,这才是人干的事」。
CameraX说白了就是一个官方封好的摄像头工具包。它帮我们处理了生命周期、设备兼容性这些烦人的事情。你想想看,如果每个手机型号都要单独适配,那项目就别想上线了。
核心思想:CameraX = 摄像头能力 + 生命周期感知 + 设备兼容性。三者缺一不可。
1.1 添加依赖
咱们先从最基础的开始。在项目的 build.gradle(模块级别)里加上这几行:
dependencies {
// CameraX 核心库
def camerax_version = "1.3.1"
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:${camerax_version}"
}
这里我习惯把版本号单独拎出来,方便以后统一升级。记得同步一下Gradle,不然会报错。
小提示:如果你用的是Kotlin DSL,写法稍微有点不同,但核心依赖是一样的。我个人建议用最新稳定版,别追beta版,容易踩坑。
1.2 配置PreviewView
接下来是界面部分。我们需要一个 PreviewView 来显示摄像头画面。它本质上是一个特殊的View,专门用来渲染摄像头数据流。
在布局文件里这样写:
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
就这么简单?对,就这么简单。你不需要关心SurfaceView还是TextureView,CameraX帮你选好了。我曾经在项目里手动切换这两种View,那叫一个痛苦。现在好了,一个PreviewView全搞定。
1.3 实现Preview用例
现在我们来写核心代码。先获取一个 ProcessCameraProvider,它是CameraX的入口:
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 创建Preview用例
val preview = Preview.Builder().build()
// 把PreviewView的surface provider传进去
preview.setSurfaceProvider(previewView.surfaceProvider)
// 绑定到生命周期
val camera = cameraProvider.bindToLifecycle(
this, // LifecycleOwner
CameraSelector.DEFAULT_BACK_CAMERA, // 后置摄像头
preview // 预览用例
)
}, ContextCompat.getMainExecutor(this))
这里有个关键点:bindToLifecycle 会把摄像头和Activity/Fragment的生命周期绑定。页面可见时自动打开摄像头,不可见时自动释放。你不用再写 onResume 和 onPause 了,省心不少。
注意:记得在AndroidManifest.xml里声明摄像头权限:
<uses-permission android:name="android.permission.CAMERA" />
运行时也要动态申请权限,不然会直接崩溃。我刚开始就忘了这茬,调试了半天。
1.4 实现ImageCapture用例
光能看还不够,咱们还得能拍照。ImageCapture就是干这个的:
// 创建ImageCapture用例
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 绑定到生命周期(和Preview一起)
cameraProvider.bindToLifecycle(
this,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture // 多个用例用逗号隔开
)
// 拍照
imageCapture.takePicture(
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 处理拍到的图片
// image.image 就是原始的YUV_420_888数据
}
override fun onError(exception: ImageCaptureException) {
// 处理错误
}
}
)
看到没?拍照和预览可以同时绑定。CameraX会自动协调这两个用例,不会冲突。我以前用Camera2的时候,拍照和预览要手动切换,稍不注意就黑屏了。
避坑指南:我曾经在 onCaptureSuccess 里直接保存图片,结果发现图片方向不对。后来加了 setTargetRotation 才解决。建议拍照前设置一下屏幕旋转:
val imageCapture = ImageCapture.Builder()
.setTargetRotation(previewView.display.rotation)
.build()
1.5 知识体系总览
为了让你更直观地理解CameraX的架构,我画了一张图:
从图里可以看出来,CameraX位于应用和底层Camera2之间。它把复杂的Camera2 API封装成了三个简单的用例:预览、拍照、分析。你只需要告诉它「我要干什么」,剩下的交给它处理。
1.6 完整示例代码
最后,我把上面所有代码整合成一个完整的Activity。你直接复制就能跑:
class CameraActivity : AppCompatActivity() {
private lateinit var previewView: PreviewView
private var imageCapture: ImageCapture? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
previewView = findViewById(R.id.previewView)
// 请求权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
} else {
startCamera()
}
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetRotation(previewView.display.rotation)
.build()
cameraProvider.bindToLifecycle(
this,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture
)
}, ContextCompat.getMainExecutor(this))
}
fun takePhoto() {
imageCapture?.takePicture(
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 保存或处理图片
}
override fun onError(exception: ImageCaptureException) {
Log.e("Camera", "拍照失败", exception)
}
}
)
}
}
嗯,到这里CameraX的基础用法就讲完了。你可能会觉得「就这么点东西?」对,就这么点。CameraX的设计初衷就是让摄像头开发变得简单。你不需要理解复杂的管道、会话、请求,只需要关注业务逻辑。
下一章我们会深入ImageAnalysis用例,看看怎么实时分析每一帧画面。不过那是后话了,先把今天的内容消化掉。
公众号:蓝海资料掘金营,微信deep3321