CameraX 是 Google 为了简化 Android 的 camera 开发而提供的一个库. 这个库包含在 Jetpack 中. 在 2019 Google IO 大会 上发布了 alpha 版本, 并且在今年发布了 beta 版本.
本文介绍使用 CameraX 来预览摄像头图像, 拍照并分析来自相机的图像流

新建项目
打开 Android Studio 新建一个 Android 项目. (语言选择 Kotlin)
添加 Gradle 依赖
打开 build.gradle(Module: app) 文件, 添加 CameraX 的依赖:
1 | implementation fileTree(dir: "libs", include: ["*.jar"]) |
CameraX 需要一些 Java 8 的方法, 所以在 buildTypes 里要设置编译参数:
1 | compileOptions { |
修改完成后点击同步, 就可以在 app 中调用 CameraX 的依赖了.
创建布局
先创建一个 values/strings.xml 文件:
1 | <resources> |
然后打开 layout/activity_main.xml, 删除其中的 <Text/> 标签, 加上一个 Button 和一个 androidx.camera.view.PreviewView(预览摄像头画面)
修改后代码如下:
1 |
|
设置 MainActivity.kt
用下面的骨架代码替换 MainActivity.kt 原有代码:
1 | import androidx.appcompat.app.AppCompatActivity |
骨架包括导入语句, 将要实例化的变量, 将要实现的函数和常量. onCreate() 已经实现了检查相机权限, 启动相机功能, 按钮的监听onClickListener(), 并实现 outputDirectory 和 cameraExecutor. 目前相机也将无法工作, 需要在之后实现那些方法.
现在的程序运行后可以看到:
获得摄像头访问权限
在调用摄像头前, 需要获得摄像头访问权限
声明权限
首先打开 AndroidManifest.xml 然后把下面的代码粘贴在 application 标签前面:
1 | <uses-feature android:name="android.hardware.camera.any" /> |
第一行使用 android.hardware.camera.any 可确保该设备具有摄像头, 而 .any 则意味着它可以是前置摄像头或后置摄像头.如果你没有使用 .any, 马尔如果你在没有后置摄像头的设备上运行程序就不会工作
第二行才是真正 app 添加了访问该摄像机的权限
判断权限申请状态
首先修改 allPermissionsGranted() 方法, 在骨架代码中这个方法默认返回 false, 重写这个方法:
1 | private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { |
在 MainActivity.kt 中重写 onRequestPermissionsResult 来获取权限请求的结果:
1 | override fun onRequestPermissionsResult( |
上面的代码首先使用:
1 | if (requestCode == REQUEST_CODE_PERMISSIONS) { |
调用上面修改的 allPermissionsGranted() 方法来检查请求代码是否正确, 如果不正确则忽略. 如果正确则检查用户是否授予了权限.
如果授权则启动摄像头:
1
2
3if (allPermissionsGranted()) {
startCamera()
}如果未授予权限就用
Tosat通知用户未授予权限:1
2
3
4
5
6else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
现在重新运行程序, 首次打开的时候会在应用内请求访问摄像头的权限:
显示摄像头预览图像
在得到访问摄像头的权限后, 会调用 startCamera() 方法来启动摄像头, 这里在 startCamera() 方法实现获取摄像头实例并把摄像头预览图像显示出来:
1 | private fun startCamera() { |
首先创建一个 ProcessCameraProvider 的实例:
1 | val cameraProviderFuture = ProcessCameraProvider.getInstance(this) |
这用于绑定摄像机的生命周期. CameraX 库接管了摄像头的生命周期, 因此使用的时候不需要关心打开和关闭相机.
然后在 cameraProviderFuture 添加一个监听:
第一个参数是 Runnable {}, 第二个参数是 ContextCompat.getMainExecutor(). ContextCompat.getMainExecutor() 将会返回一个在主线程上运行的 Executor
1 | cameraProviderFuture.addListener(Runnable {}, ContextCompat.getMainExecutor(this)) |
在上面地一个参数 Runnable 中, 添加 ProcessCameraProvider, 用于把摄像头的生命周期绑定到应用程序的 LifecycleOwner:
1 | val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() |
上面的代码已经实现了托管摄像头的生命周期, 接下来要实现摄像头画面预览就只需要实例化一个 Preview 对象, 并绑定到摄像头的生命周期内即可. 首先初始化一个 Preview 对象:
1 | preview = Preview.Builder().build() |
然后创建一个 CameraSelector 对象,然后使用它的 CameraSelector.Builder.requireLensFacing 方法调用你想访问的摄像头(CameraSelector.LENS_FACING_BACK 表示后置摄像头)
1 | val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() |
最后编写一个 try-catch 代码块. 在该代码块内, 先使用 unbindAll 确保没有任何内容绑定到 cameraProvider, 然后将前面的 cameraSelector 和预览对象 unbindAll 绑定到 cameraProvider. 再将 viewFinder 的 SurfaceProvider 设置为 preview 的 SurfaceProvider:
1 | try { |
运行程序, 你就可以看到摄像头捕获的预览画面:
实现图像捕获的功能
在骨架代码中, 预留了一个按钮用于捕获图片. 在按钮点击后会运行 takePhoto() 方法, 所以只需要在该方法实现图像捕获的业务逻辑即可.
实现 takePhoto() 方法
首先获取 ImageCapture 对象, 如果获取不到则直接返回:
1 | val imageCapture = imageCapture ?: return |
然后创建一个 File 对象用于指定储存一会捕获到的图片的位置和文件名:
1 | val photoFile = File( |
创建一个 OutputFileOptions 对象用于制定输出. 这里需要将输出保存在我们刚刚创建的文件中, 因此刚才创建的 photoFile:
1 | val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() |
最后就可以调用 imageCapture 对象的 takePicture() 方法. 传入 outputOptions, 执行程序 {...} 和保存图像的 callback 方法:
1 | imageCapture.takePicture( |
如果图像捕获失败或保存图像捕获失败:
1 | override fun onError(exc: ImageCaptureException) { |
如果图像捕获成功, 则照片保存到之前创建的文件中, 并打印保存路径:
1 | override fun onImageSaved(output: ImageCapture.OutputFileResults) { |
最后 takePhoto() 完整的代码如下:
1 | private fun takePhoto() { |
创建 ImageCapture 对象并绑定到摄像头生命周期
前面图像捕获的功能用到了 ImageCapture, 所以之前的 Preview 一样, 要用相同的步骤把 ImageCapture 也绑定到生命周期:
在 startCamera() 方法的创建 Preview 代码下添加创建 ImageCapture 的代码:
1 | imageCapture = ImageCapture.Builder() |
然后在 cameraProvider.bindToLifecycle 中把上面的创建的 ImageCapture 实例绑定到摄像头生命周期上:
1 | camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) |
现在运行程序, 点击按钮就会保存捕获图片, 并且你可以在本地图库找到保存的图片.
实现图像分析功能
上面介绍了 CameraX 库进行摄像头预览和摄像头图片捕获的功能, 最后一个图像分析的功能和上面过程类似. 也是创建一个图像分析的对象, 然后绑定到摄像头生命周期里.
添加平均亮度分析类
首先在 MainActivity 里添加一个内部类 LuminosityAnalyzer 用于分析图像的平均亮度:
1 | private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer { |
创建 ImageAnalysis 对象并绑定到摄像头生命周期
图像分析对象的绑定方法和画面预览, 图像捕获对象绑定方法一样, 都是首先实例化一个对象. 在 startCamera() 方法中, 在实例化 ImageCapture 对象下面添加实例化 ImageAnnalysis 对象的代码:
1 | imageAnalyzer = ImageAnalysis.Builder() |
实例化 ImageAnnalysis 对象的时候, 传入了一个 LuminosityAnalyzer 对象用于分析i并打印图片平均亮度的结果.
最后在 cameraProvider.bindToLifecycle 方法里添加实例化的 ImageAnalysis 对象:
1 | camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalyzer) |
运行程序, 可以看到 logcat 中输出图片分析的结果:


